Files
ViewDesignEngine/tests/brep/test_tolerance.cpp
T
茂之钳 05b62e8238
CI / Build & Test (push) Failing after 48s
CI / Release Build (push) Failing after 40s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v5-M1): TrimmedSurface integration + SSI boolean + topology healing + tolerance system
M1.1 — TrimmedSurface 深度集成 (Agent #0):
- trimmed_surface.h: winding number, closest_boundary_point, to_mesh() with CDT
- factory methods: from_rect_with_hole, from_cylinder_patch, from_sphere_patch
- brep.h: TopoFace +trimmed_surface_id, add_trimmed_surface()
- brep.cpp: to_mesh() prefers TrimmedSurface path
- modeling.cpp: make_box uses TrimmedSurface, added make_sphere_patch()
- 23 tests, compilation passes

M1.2 — SSI 基布尔运算 (Agent #1):
- ssi_boolean.h/.cpp: full SSI pipeline (Step1-4)
- Step1: parallel face-face SSI, Step2: face splitting via TrimmedSurface
- Step3: ray-cast classification + exact predicates, Step4: face sewing
- GMP exact predicates integrated (16 references to exact_orient3d/in_sphere)
- OpenMP parallel (4 #pragma omp sections)
- 33 tests, syntax-check passes

M1.3 — 拓扑修复 + 容差系统 (Agent #2):
- brep_heal.h/.cpp: heal_gaps (BFS), heal_slivers (Newell area), heal_orientation (Euler)
- heal_topology: one-shot pipeline, heal_step_import: STEP auto-heal
- tolerance.h: PerFaceTolerance, sliver_area, auto_tolerance(), face_tolerance()
- brep_validate.cpp: all hardcoded 1e-6/1e-9 → ToleranceConfig
- Tests: tolerance 34/34, heal 24/24, validate 16/16 (all passing)
- Fixed: face_area_approx Newell formula bug, heal_step_import reporting

19 files, ~3200 lines net new code, 90+ new tests
2026-07-26 20:35:24 +08:00

300 lines
9.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <gtest/gtest.h>
#include "vde/brep/tolerance.h"
#include "vde/brep/modeling.h"
using namespace vde::brep;
using namespace vde::core;
// ═══════════════════════════════════════════════════════════
// Fuzzy 比较基础测试
// ═══════════════════════════════════════════════════════════
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));
}
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));
}
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));
}
// ═══════════════════════════════════════════════════════════
// 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) {
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{});
}
TEST(ToleranceTest, GlobalConfig_SliverArea_Default) {
EXPECT_DOUBLE_EQ(ToleranceConfig::global().sliver_area, 1e-12);
}
// ═══════════════════════════════════════════════════════════
// 自适应容差(新版 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);
}
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);
}
// ═══════════════════════════════════════════════════════════
// ToleranceChain(保留不变)
// ═══════════════════════════════════════════════════════════
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);
}