2026-07-25 04:07:24 +00:00
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
#include "vde/brep/tolerance.h"
|
|
|
|
|
|
#include "vde/brep/modeling.h"
|
|
|
|
|
|
|
|
|
|
|
|
using namespace vde::brep;
|
|
|
|
|
|
using namespace vde::core;
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// Fuzzy 比较基础测试
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-07-25 04:07:24 +00:00
|
|
|
|
TEST(ToleranceTest, FuzzyEqual_Exact) {
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_equal(1.0, 1.0));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, FuzzyEqual_WithinTol) {
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_equal(1.0, 1.0 + 1e-10));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, FuzzyEqual_OutsideTol) {
|
|
|
|
|
|
EXPECT_FALSE(fuzzy_equal(1.0, 1.0 + 1e-3, 1e-6));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, FuzzyZero) {
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_zero(0.0));
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_zero(1e-10));
|
|
|
|
|
|
EXPECT_FALSE(fuzzy_zero(1e-3, 1e-6));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, FuzzyVec) {
|
|
|
|
|
|
Vector3D a(1, 2, 3);
|
|
|
|
|
|
Vector3D b(1 + 1e-10, 2, 3 - 1e-10);
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_equal_vec(a, b, 1e-8));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, FuzzyPoint) {
|
|
|
|
|
|
Point3D a(0, 0, 0);
|
|
|
|
|
|
Point3D b(1e-10, 0, 0);
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_equal_point(a, b, 1e-8));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, FuzzyParallel) {
|
|
|
|
|
|
Vector3D a(1, 0, 0);
|
|
|
|
|
|
Vector3D b(1, 1e-11, 0);
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_parallel(a, b));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, FuzzyPerpendicular) {
|
|
|
|
|
|
Vector3D a(1, 0, 0);
|
|
|
|
|
|
Vector3D b(0, 1, 0);
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_perpendicular(a, b));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
TEST(ToleranceTest, FuzzyGTE) {
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_gte(1.0, 1.0 - 1e-10));
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_gte(1.0, 1.0));
|
|
|
|
|
|
EXPECT_FALSE(fuzzy_gte(1.0, 1.1, 1e-6));
|
2026-07-25 04:07:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
TEST(ToleranceTest, FuzzyLTE) {
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_lte(1.0, 1.0 + 1e-10));
|
|
|
|
|
|
EXPECT_TRUE(fuzzy_lte(1.0, 1.0));
|
|
|
|
|
|
EXPECT_FALSE(fuzzy_lte(1.0, 0.9, 1e-6));
|
2026-07-25 04:07:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// ToleranceConfig
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, ToleranceConfig_AllDefaultPositive) {
|
|
|
|
|
|
ToleranceConfig cfg;
|
|
|
|
|
|
EXPECT_GT(cfg.vertex_merge, 0);
|
|
|
|
|
|
EXPECT_GT(cfg.edge_merge, 0);
|
|
|
|
|
|
EXPECT_GT(cfg.boolean, 0);
|
|
|
|
|
|
EXPECT_GT(cfg.angular, 0);
|
|
|
|
|
|
EXPECT_GT(cfg.sliver_area, 0);
|
|
|
|
|
|
EXPECT_GT(cfg.point_on_surface, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, GlobalConfig_ReadWrite) {
|
2026-07-25 04:07:24 +00:00
|
|
|
|
auto& cfg = ToleranceConfig::global();
|
|
|
|
|
|
EXPECT_GT(cfg.vertex_merge, 0.0);
|
|
|
|
|
|
|
|
|
|
|
|
ToleranceConfig custom;
|
|
|
|
|
|
custom.vertex_merge = 1e-4;
|
|
|
|
|
|
ToleranceConfig::set_global(custom);
|
|
|
|
|
|
EXPECT_EQ(ToleranceConfig::global().vertex_merge, 1e-4);
|
|
|
|
|
|
|
|
|
|
|
|
// Reset
|
|
|
|
|
|
ToleranceConfig::set_global(ToleranceConfig{});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
TEST(ToleranceTest, GlobalConfig_SliverArea_Default) {
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(ToleranceConfig::global().sliver_area, 1e-12);
|
2026-07-25 04:07:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// 自适应容差(新版 auto_tolerance)
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, AutoTolerance_SmallModel_ReturnsTighter) {
|
|
|
|
|
|
AABB3D small_bb(Point3D(-0.5, -0.5, -0.5), Point3D(0.5, 0.5, 0.5));
|
|
|
|
|
|
ToleranceConfig cfg_small = auto_tolerance(small_bb, 100.0);
|
|
|
|
|
|
EXPECT_GT(cfg_small.vertex_merge, 0.0);
|
|
|
|
|
|
// 小模型容差 ≤ 默认值
|
|
|
|
|
|
EXPECT_LE(cfg_small.vertex_merge, ToleranceConfig{}.vertex_merge);
|
2026-07-25 04:07:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
TEST(ToleranceTest, AutoTolerance_LargeModel_ReturnsLooser) {
|
|
|
|
|
|
AABB3D large_bb(Point3D(-500, -500, -500), Point3D(500, 500, 500));
|
|
|
|
|
|
ToleranceConfig cfg_large = auto_tolerance(large_bb, 100.0);
|
|
|
|
|
|
EXPECT_GT(cfg_large.vertex_merge, 0.0);
|
|
|
|
|
|
// 大模型容差 ≥ 默认值
|
|
|
|
|
|
EXPECT_GE(cfg_large.vertex_merge, ToleranceConfig{}.vertex_merge);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, AutoTolerance_LargeVsSmall_Different) {
|
|
|
|
|
|
AABB3D small_bb(Point3D(-1, -1, -1), Point3D(1, 1, 1));
|
|
|
|
|
|
AABB3D large_bb(Point3D(-1000, -1000, -1000), Point3D(1000, 1000, 1000));
|
|
|
|
|
|
|
|
|
|
|
|
ToleranceConfig cfg_small = auto_tolerance(small_bb, 100.0);
|
|
|
|
|
|
ToleranceConfig cfg_large = auto_tolerance(large_bb, 100.0);
|
|
|
|
|
|
|
|
|
|
|
|
// 大模型容差应更大
|
|
|
|
|
|
EXPECT_GT(cfg_large.vertex_merge, cfg_small.vertex_merge);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, AutoTolerance_EmptyBounds_ReturnsDefault) {
|
|
|
|
|
|
AABB3D empty_bb;
|
|
|
|
|
|
ToleranceConfig cfg = auto_tolerance(empty_bb, 100.0);
|
|
|
|
|
|
// 空包围盒应返回默认容差
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(cfg.vertex_merge, ToleranceConfig{}.vertex_merge);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, AutoTolerance_Body_Box) {
|
|
|
|
|
|
auto box = make_box(100, 100, 100);
|
|
|
|
|
|
ToleranceConfig cfg = auto_tolerance(box, 100.0);
|
|
|
|
|
|
EXPECT_GT(cfg.vertex_merge, 0.0);
|
|
|
|
|
|
EXPECT_GT(cfg.sliver_area, 0.0);
|
|
|
|
|
|
EXPECT_GT(cfg.point_on_surface, 0.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, AutoTolerance_Body_SmallBox) {
|
|
|
|
|
|
auto box = make_box(1, 1, 1);
|
|
|
|
|
|
ToleranceConfig cfg = auto_tolerance(box, 100.0);
|
|
|
|
|
|
// 1mm 盒子 → 容差应较小
|
|
|
|
|
|
EXPECT_LE(cfg.vertex_merge, ToleranceConfig{}.vertex_merge);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceTest, ModelTolerance_Box) {
|
|
|
|
|
|
auto box = make_box(100, 100, 100);
|
|
|
|
|
|
double t = model_tolerance(box);
|
|
|
|
|
|
EXPECT_GT(t, 0.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// PerFaceTolerance — 每面独立容差
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(PerFaceToleranceTest, DefaultResolvesToGlobal) {
|
|
|
|
|
|
PerFaceTolerance pft;
|
|
|
|
|
|
auto resolved = pft.resolve(0);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(resolved.vertex_merge, ToleranceConfig::global().vertex_merge);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(PerFaceToleranceTest, OverrideResolvesToLocal) {
|
|
|
|
|
|
PerFaceTolerance pft;
|
|
|
|
|
|
ToleranceConfig local;
|
|
|
|
|
|
local.vertex_merge = 1e-3;
|
|
|
|
|
|
pft.set(5, local);
|
|
|
|
|
|
|
|
|
|
|
|
auto resolved = pft.resolve(5);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(resolved.vertex_merge, 1e-3);
|
|
|
|
|
|
|
|
|
|
|
|
// 未设置的面仍使用全局
|
|
|
|
|
|
auto resolved_default = pft.resolve(3);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(resolved_default.vertex_merge, ToleranceConfig::global().vertex_merge);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(PerFaceToleranceTest, RemoveClearsOverride) {
|
|
|
|
|
|
PerFaceTolerance pft;
|
|
|
|
|
|
ToleranceConfig local;
|
|
|
|
|
|
local.vertex_merge = 1e-3;
|
|
|
|
|
|
pft.set(1, local);
|
|
|
|
|
|
EXPECT_TRUE(pft.has_override(1));
|
|
|
|
|
|
|
|
|
|
|
|
pft.remove(1);
|
|
|
|
|
|
EXPECT_FALSE(pft.has_override(1));
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(pft.resolve(1).vertex_merge, ToleranceConfig::global().vertex_merge);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(PerFaceToleranceTest, ClearRemovesAll) {
|
|
|
|
|
|
PerFaceTolerance pft;
|
|
|
|
|
|
for (int i = 0; i < 5; ++i) {
|
|
|
|
|
|
pft.set(i, ToleranceConfig{});
|
|
|
|
|
|
}
|
|
|
|
|
|
EXPECT_EQ(pft.overrides().size(), 5u);
|
|
|
|
|
|
|
|
|
|
|
|
pft.clear();
|
|
|
|
|
|
EXPECT_EQ(pft.overrides().size(), 0u);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(PerFaceToleranceTest, OverridesAccess) {
|
|
|
|
|
|
PerFaceTolerance pft;
|
|
|
|
|
|
pft.set(10, ToleranceConfig{});
|
|
|
|
|
|
pft.set(20, ToleranceConfig{});
|
|
|
|
|
|
|
|
|
|
|
|
const auto& ov = pft.overrides();
|
|
|
|
|
|
ASSERT_EQ(ov.size(), 2u);
|
|
|
|
|
|
EXPECT_TRUE(ov.count(10) > 0);
|
|
|
|
|
|
EXPECT_TRUE(ov.count(20) > 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(PerFaceToleranceTest, FaceTolerance_WithPFT) {
|
|
|
|
|
|
auto box = make_box(2, 2, 2);
|
|
|
|
|
|
PerFaceTolerance pft;
|
|
|
|
|
|
ToleranceConfig local;
|
|
|
|
|
|
local.vertex_merge = 1e-8;
|
|
|
|
|
|
pft.set(0, local);
|
|
|
|
|
|
|
|
|
|
|
|
// 面 0 应用局部覆盖
|
|
|
|
|
|
auto cfg0 = face_tolerance(box, 0, &pft);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(cfg0.vertex_merge, 1e-8);
|
|
|
|
|
|
|
|
|
|
|
|
// 面 1 无覆盖,使用全局
|
|
|
|
|
|
auto cfg1 = face_tolerance(box, 1, &pft);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(cfg1.vertex_merge, ToleranceConfig::global().vertex_merge);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(PerFaceToleranceTest, FaceTolerance_NullPFT) {
|
|
|
|
|
|
auto box = make_box(2, 2, 2);
|
|
|
|
|
|
auto cfg = face_tolerance(box, 0, nullptr);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(cfg.vertex_merge, ToleranceConfig::global().vertex_merge);
|
2026-07-25 04:07:24 +00:00
|
|
|
|
}
|
2026-07-26 16:42:55 +08:00
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// ToleranceChain(保留不变)
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
2026-07-26 16:42:55 +08:00
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceChainTest, EmptyChain) {
|
|
|
|
|
|
ToleranceChain chain;
|
|
|
|
|
|
EXPECT_EQ(chain.depth(), 0u);
|
|
|
|
|
|
EXPECT_EQ(chain.cumulative(), 0.0);
|
|
|
|
|
|
EXPECT_EQ(chain.max_step(), 0.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceChainTest, SingleStep) {
|
|
|
|
|
|
ToleranceChain chain;
|
|
|
|
|
|
chain.push("intersect", 1e-6);
|
|
|
|
|
|
EXPECT_EQ(chain.depth(), 1u);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(chain.cumulative(), 1e-6);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(chain.max_step(), 1e-6);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceChainTest, MultiStepRSS) {
|
|
|
|
|
|
ToleranceChain chain;
|
|
|
|
|
|
chain.push("a", 3e-6);
|
|
|
|
|
|
chain.push("b", 4e-6);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(chain.cumulative(), 5e-6);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceChainTest, MaxStep) {
|
|
|
|
|
|
ToleranceChain chain;
|
|
|
|
|
|
chain.push("small", 1e-8);
|
|
|
|
|
|
chain.push("large", 1e-4);
|
|
|
|
|
|
chain.push("med", 1e-6);
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(chain.max_step(), 1e-4);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceChainTest, ClearResets) {
|
|
|
|
|
|
ToleranceChain chain;
|
|
|
|
|
|
chain.push("x", 1e-6);
|
|
|
|
|
|
chain.clear();
|
|
|
|
|
|
EXPECT_EQ(chain.depth(), 0u);
|
|
|
|
|
|
EXPECT_EQ(chain.cumulative(), 0.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceChainTest, StepsAccess) {
|
|
|
|
|
|
ToleranceChain chain;
|
|
|
|
|
|
chain.push("op1", 1e-9);
|
|
|
|
|
|
chain.push("op2", 2e-9);
|
|
|
|
|
|
auto& steps = chain.steps();
|
|
|
|
|
|
ASSERT_EQ(steps.size(), 2u);
|
|
|
|
|
|
EXPECT_EQ(steps[0].first, "op1");
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(steps[0].second, 1e-9);
|
|
|
|
|
|
EXPECT_EQ(steps[1].first, "op2");
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(steps[1].second, 2e-9);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(ToleranceChainTest, BooleanChainSimulation) {
|
|
|
|
|
|
ToleranceChain chain;
|
|
|
|
|
|
chain.push("intersect", 1e-6);
|
|
|
|
|
|
chain.push("split", 1e-6);
|
|
|
|
|
|
chain.push("classify", 1e-7);
|
|
|
|
|
|
chain.push("sew", 1e-5);
|
|
|
|
|
|
double cum = chain.cumulative();
|
|
|
|
|
|
EXPECT_GT(cum, 1e-5);
|
|
|
|
|
|
EXPECT_LT(cum, 1.5e-5);
|
|
|
|
|
|
}
|