#include #include "vde/brep/brep.h" #include "vde/brep/modeling.h" #include "vde/brep/brep_validate.h" #include "vde/brep/brep_heal.h" #include "vde/brep/tolerant_modeling.h" #include "vde/brep/tolerance.h" #include "vde/brep/ssi_boolean.h" #include "vde/brep/step_import.h" #include "vde/curves/nurbs_surface.h" #include "vde/core/point.h" using namespace vde::brep; using namespace vde::core; using namespace vde::curves; namespace { /// Create a simple planar NURBS surface on the XY plane NurbsSurface make_plane_surface() { std::vector> grid = { {Point3D(-1, -1, 0), Point3D(-1, 1, 0)}, {Point3D( 1, -1, 0), Point3D( 1, 1, 0)} }; return NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {}, 1, 1); } /// Create a simple triangular BrepModel BrepModel make_triangle(const Point3D& a, const Point3D& b, const Point3D& c) { BrepModel body; int v0 = body.add_vertex(a); int v1 = body.add_vertex(b); int v2 = body.add_vertex(c); int e0 = body.add_edge(v0, v1); int e1 = body.add_edge(v1, v2); int e2 = body.add_edge(v2, v0); int loop_id = body.add_loop({e0, e1, e2}); auto surf = make_plane_surface(); int sid = body.add_surface(surf); int fid = body.add_face(sid, {loop_id}); int shell = body.add_shell({fid}, false); body.add_body({shell}, "triangle"); return body; } /// Create a simple box using modeling API BrepModel make_test_box() { return make_box(2.0, 2.0, 2.0); } } // namespace // ═══════════════════════════════════════════════════════════ // 1. TolerantVertex / TolerantEdge 基本构造 // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, TolerantVertex_DefaultConstruction) { TolerantVertex tv; tv.id = 0; tv.point = Point3D(1, 2, 3); tv.tolerance = 1e-5; EXPECT_EQ(tv.id, 0); EXPECT_DOUBLE_EQ(tv.point.x(), 1.0); EXPECT_DOUBLE_EQ(tv.point.y(), 2.0); EXPECT_DOUBLE_EQ(tv.point.z(), 3.0); EXPECT_DOUBLE_EQ(tv.tolerance, 1e-5); EXPECT_EQ(tv.merged_from, 1); EXPECT_FALSE(tv.is_degenerate); } TEST(TolerantModelingTest, TolerantVertex_FromTopo) { TopoVertex topo; topo.id = 5; topo.point = Point3D(10, 20, 30); topo.tolerance = 1e-7; TolerantVertex tv = TolerantVertex::from_topo(topo); EXPECT_EQ(tv.id, 5); EXPECT_DOUBLE_EQ(tv.point.x(), 10.0); EXPECT_DOUBLE_EQ(tv.tolerance, 1e-7); } TEST(TolerantModelingTest, TolerantEdge_DefaultConstruction) { TolerantEdge te; te.id = 3; te.v_start = 1; te.v_end = 2; te.tolerance = 1e-6; te.is_tangent_contact = true; EXPECT_EQ(te.id, 3); EXPECT_EQ(te.v_start, 1); EXPECT_EQ(te.v_end, 2); EXPECT_FALSE(te.is_degenerate); EXPECT_FALSE(te.gap_flag); EXPECT_TRUE(te.is_tangent_contact); } TEST(TolerantModelingTest, TolerantEdge_FromTopo) { TopoEdge topo; topo.id = 7; topo.v_start = 0; topo.v_end = 1; TolerantEdge te = TolerantEdge::from_topo(topo); EXPECT_EQ(te.id, 7); EXPECT_EQ(te.v_start, 0); EXPECT_EQ(te.v_end, 1); } TEST(TolerantModelingTest, TolerantEdge_CheckDegenerate_True) { BrepModel body; int v0 = body.add_vertex(Point3D(0, 0, 0)); int v1 = body.add_vertex(Point3D(1e-8, 1e-8, 0)); // extremely short edge int e0 = body.add_edge(v0, v1); TolerantEdge te = TolerantEdge::from_topo(body.edge(e0)); te.tolerance = 1e-6; EXPECT_TRUE(te.check_degenerate(body)); } TEST(TolerantModelingTest, TolerantEdge_CheckDegenerate_False) { BrepModel body; int v0 = body.add_vertex(Point3D(0, 0, 0)); int v1 = body.add_vertex(Point3D(1, 0, 0)); int e0 = body.add_edge(v0, v1); TolerantEdge te = TolerantEdge::from_topo(body.edge(e0)); te.tolerance = 1e-6; EXPECT_FALSE(te.check_degenerate(body)); } // ═══════════════════════════════════════════════════════════ // 2. build_tolerant_vertices / build_tolerant_edges // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, BuildTolerantVertices) { BrepModel body; body.add_vertex(Point3D(0, 0, 0)); body.add_vertex(Point3D(1, 0, 0)); body.add_vertex(Point3D(0, 1, 0)); auto tvs = build_tolerant_vertices(body, 1e-5); EXPECT_EQ(tvs.size(), 3u); for (const auto& tv : tvs) { EXPECT_DOUBLE_EQ(tv.tolerance, 1e-5); EXPECT_EQ(tv.merged_from, 1); } } TEST(TolerantModelingTest, BuildTolerantEdges) { BrepModel body; int v0 = body.add_vertex(Point3D(0, 0, 0)); int v1 = body.add_vertex(Point3D(1, 0, 0)); int v2 = body.add_vertex(Point3D(0, 1, 0)); body.add_edge(v0, v1); body.add_edge(v1, v2); body.add_edge(v2, v0); auto tes = build_tolerant_edges(body); EXPECT_EQ(tes.size(), 3u); EXPECT_FALSE(tes[0].is_degenerate); // 1.0 length } // ═══════════════════════════════════════════════════════════ // 3. merge_within_tolerance // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, MergeWithinTolerance_NearCoincident) { BrepModel body; int v0 = body.add_vertex(Point3D(0, 0, 0)); int v1 = body.add_vertex(Point3D(1, 0, 0)); int v2 = body.add_vertex(Point3D(0, 1, 0)); int v_near = body.add_vertex(Point3D(1e-7, 1e-7, 1e-7)); int e0 = body.add_edge(v0, v1); int e1 = body.add_edge(v1, v2); int e2 = body.add_edge(v2, v_near); int loop_id = body.add_loop({e0, e1, e2}); auto surf = make_plane_surface(); int sid = body.add_surface(surf); body.add_face(sid, {loop_id}); size_t before = body.num_vertices(); int merged = merge_within_tolerance(body, 1e-5); EXPECT_GE(merged, 1); EXPECT_LT(body.num_vertices(), before); } TEST(TolerantModelingTest, MergeWithinTolerance_Empty) { BrepModel body; EXPECT_EQ(merge_within_tolerance(body), 0); } TEST(TolerantModelingTest, MergeWithinTolerance_SingleVertex) { BrepModel body; body.add_vertex(Point3D(0, 0, 0)); EXPECT_EQ(merge_within_tolerance(body), 0); } // ═══════════════════════════════════════════════════════════ // 4. gap_bridging // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, GapBridging_Empty) { BrepModel body; EXPECT_EQ(gap_bridging(body), 0); } TEST(TolerantModelingTest, GapBridging_SingleTriangle_NoGaps) { auto body = make_triangle(Point3D(0,0,0), Point3D(1,0,0), Point3D(0,1,0)); // A closed triangle has no free edges int bridged = gap_bridging(body, 1e-4); EXPECT_GE(bridged, 0); } // ═══════════════════════════════════════════════════════════ // 5. overlap_resolution // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, OverlapResolution_Empty) { BrepModel body; EXPECT_EQ(overlap_resolution(body), 0); } TEST(TolerantModelingTest, OverlapResolution_SingleFace) { auto body = make_triangle(Point3D(0,0,0), Point3D(1,0,0), Point3D(0,1,0)); EXPECT_EQ(overlap_resolution(body), 0); } // ═══════════════════════════════════════════════════════════ // 6. tolerant_boolean // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, TolerantBoolean_EmptyBodies) { BrepModel a, b; auto diag = tolerant_boolean(a, b, 0); // union EXPECT_TRUE(diag.success); EXPECT_EQ(diag.result.num_faces(), 0u); } TEST(TolerantModelingTest, TolerantBoolean_UnionOfDisjoint) { auto box1 = make_box(1, 1, 1); auto box2 = make_box(1, 1, 1); auto diag = tolerant_boolean(box1, box2, 0); // union // May succeed or have failures — check that diagnostic is populated EXPECT_FALSE(diag.report().empty()); } TEST(TolerantModelingTest, TolerantBoolean_InvalidOp) { auto box1 = make_box(1, 1, 1); auto box2 = make_box(1, 1, 1); auto diag = tolerant_boolean(box1, box2, 99); EXPECT_FALSE(diag.success); EXPECT_FALSE(diag.error_message.empty()); } // ═══════════════════════════════════════════════════════════ // 7. BooleanDiagnostic // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, BooleanDiagnostic_Default) { BooleanDiagnostic diag; EXPECT_FALSE(diag.success); EXPECT_FALSE(diag.has_failures()); EXPECT_FALSE(diag.report().empty()); } TEST(TolerantModelingTest, BooleanDiagnostic_HasFailures) { BooleanDiagnostic diag; EXPECT_FALSE(diag.has_failures()); FailedFaceInfo ffi; ffi.face_id = 3; ffi.reason = "Test failure"; diag.failed_faces.push_back(ffi); EXPECT_TRUE(diag.has_failures()); } TEST(TolerantModelingTest, BooleanDiagnostic_FailedFaceInfo) { FailedFaceInfo ffi; ffi.face_id = 42; ffi.reason = "Coplanar degeneracy"; ffi.related_face_id = 17; ffi.is_coplanar = true; ffi.suggested_tolerance = 1e-3; EXPECT_EQ(ffi.face_id, 42); EXPECT_EQ(ffi.related_face_id, 17); EXPECT_TRUE(ffi.is_coplanar); EXPECT_DOUBLE_EQ(ffi.suggested_tolerance, 1e-3); } // ═══════════════════════════════════════════════════════════ // 8. detect_coplanar_faces / detect_tangent_contact / detect_degenerate_edge // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, DetectCoplanarFaces_SameBody) { auto body = make_triangle(Point3D(0,0,0), Point3D(1,0,0), Point3D(0,1,0)); // Single face body → cannot check pair EXPECT_FALSE(detect_coplanar_faces(body, 0, 0)); } TEST(TolerantModelingTest, DetectDegenerateEdge_Valid) { BrepModel body; int v0 = body.add_vertex(Point3D(0, 0, 0)); int v1 = body.add_vertex(Point3D(1, 0, 0)); int e0 = body.add_edge(v0, v1); EXPECT_FALSE(detect_degenerate_edge(body, e0)); } TEST(TolerantModelingTest, DetectDegenerateEdge_Degenerate) { BrepModel body; int v0 = body.add_vertex(Point3D(0, 0, 0)); int v1 = body.add_vertex(Point3D(1e-8, 0, 0)); int e0 = body.add_edge(v0, v1); EXPECT_TRUE(detect_degenerate_edge(body, e0, 1e-5)); } // ═══════════════════════════════════════════════════════════ // 9. import_heal_pipeline // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, ImportHealPipeline_Empty) { auto bodies = import_heal_pipeline(""); EXPECT_TRUE(bodies.empty()); } TEST(TolerantModelingTest, ImportHealPipeline_InvalidStep) { auto bodies = import_heal_pipeline("garbage data"); EXPECT_TRUE(bodies.empty()); } TEST(TolerantModelingTest, ImportHealPipeline_ValidStepData) { // 测试最小合法 STEP 数据 std::string step_data = R"( ISO-10303-21; HEADER; FILE_DESCRIPTION(('Test'),'2;1'); FILE_SCHEMA(('CONFIG_CONTROL_DESIGN')); ENDSEC; DATA; #1=CARTESIAN_POINT('',(0.,0.,0.)); #2=CARTESIAN_POINT('',(1.,0.,0.)); #3=CARTESIAN_POINT('',(1.,1.,0.)); #4=CARTESIAN_POINT('',(0.,1.,0.)); ENDSEC; END-ISO-10303-21; )"; auto bodies = import_heal_pipeline(step_data); // 此数据没有完整拓扑结构,预期为空或部分结果 // 主要验证不崩溃 EXPECT_TRUE(bodies.empty() || !bodies.empty()); } // ═══════════════════════════════════════════════════════════ // 10. STEP import diagnostic // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, StepImportDiagnostic_AfterImport) { std::string step_data = R"( ISO-10303-21; HEADER; FILE_DESCRIPTION(('Test'),'2;1'); FILE_SCHEMA(('CONFIG_CONTROL_DESIGN')); ENDSEC; DATA; #1=CARTESIAN_POINT('',(0.,0.,0.)); ENDSEC; END-ISO-10303-21; )"; import_step_from_string(step_data); auto report = step_import_diagnostic(); // 应至少解析到 1 个实体 EXPECT_GE(report.total_entities, 1); } TEST(TolerantModelingTest, StepImportDiagnostic_ReportFormat) { std::string step_data = R"( ISO-10303-21; HEADER; FILE_DESCRIPTION(('Test'),'2;1'); FILE_SCHEMA(('CONFIG_CONTROL_DESIGN')); ENDSEC; DATA; ENDSEC; END-ISO-10303-21; )"; import_step_from_string(step_data); auto report = step_import_diagnostic(); auto str = report.report(); EXPECT_FALSE(str.empty()); EXPECT_TRUE(str.find("STEP Import Diagnostic") != std::string::npos); } // ═══════════════════════════════════════════════════════════ // 11. Damaged STEP recovery // ═══════════════════════════════════════════════════════════ TEST(TolerantModelingTest, StepImport_DamagedEntityRecovery) { // 包含损坏实体的 STEP 数据:不完整的实体定义 std::string step_data = R"( ISO-10303-21; HEADER; FILE_DESCRIPTION(('Damaged'),'2;1'); FILE_SCHEMA(('CONFIG_CONTROL_DESIGN')); ENDSEC; DATA; #10=INCOMPLETE_ENTITY #1=CARTESIAN_POINT('',(0.,0.,0.)); #2=CARTESIAN_POINT('',(1.,0.,0.)); ENDSEC; END-ISO-10303-21; )"; auto bodies = import_step_from_string(step_data); // 即使有损坏实体,仍应能解析 CARTESIAN_POINT auto report = step_import_diagnostic(); EXPECT_GE(report.skipped_damaged, 1); }