#include #include "vde/brep/brep.h" #include "vde/brep/modeling.h" #include "vde/brep/direct_modeling.h" #include "vde/brep/brep_validate.h" #include "vde/core/transform.h" #include using namespace vde::brep; using namespace vde::core; using namespace vde::curves; // ═══════════════════════════════════════════════════════════ // tweak_face — 面偏移测试 // ═══════════════════════════════════════════════════════════ TEST(DirectModelingTest, TweakFace_BoxTopOffset) { auto box = make_box(2, 2, 2); auto result = tweak_face(box, 0, 0.5); EXPECT_TRUE(result.success) << "tweak_face should succeed for box top face"; EXPECT_GT(result.body.num_faces(), 0u); // After tweak, model should still be valid auto val = validate(result.body); EXPECT_TRUE(val.valid) << "Offset model should still be valid"; } TEST(DirectModelingTest, TweakFace_BoxInwardOffset) { auto box = make_box(2, 2, 2); auto result = tweak_face(box, 0, -0.3); EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, TweakFace_ZeroDelta) { auto box = make_box(2, 2, 2); auto result = tweak_face(box, 0, 0.0); EXPECT_TRUE(result.success); // Zero delta should produce valid model (essentially unchanged) auto val = validate(result.body); EXPECT_TRUE(val.valid); EXPECT_EQ(result.body.num_faces(), box.num_faces()); } TEST(DirectModelingTest, TweakFace_InvalidFaceId) { auto box = make_box(2, 2, 2); auto result = tweak_face(box, 99, 0.5); EXPECT_FALSE(result.success); EXPECT_FALSE(result.errors.empty()); } TEST(DirectModelingTest, TweakFace_CylinderCap) { auto cyl = make_cylinder(2, 4, 32); auto result = tweak_face(cyl, 0, 0.5); EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } // ═══════════════════════════════════════════════════════════ // move_face — 面移动测试 // ═══════════════════════════════════════════════════════════ TEST(DirectModelingTest, MoveFace_Translate) { auto box = make_box(2, 2, 2); auto T = translate(Vector3D(0.5, 0, 0)); auto result = move_face(box, 0, T); EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, MoveFace_Rotate) { auto box = make_box(2, 2, 2); // Rotate around Z axis by small angle auto T = rotate_z(M_PI / 12); auto result = move_face(box, 0, T); // Small rotation should succeed EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, MoveFace_TranslateAndRotate) { auto box = make_box(2, 2, 2); auto T = translate(Vector3D(0.2, 0.1, 0)) * rotate_z(0.1); auto result = move_face(box, 0, T); EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, MoveFace_LargeRotationFails) { auto box = make_box(2, 2, 2); // Large rotation should cause normal flip and be rejected auto T = rotate_z(2.0); // ~115 degrees auto result = move_face(box, 0, T); // May succeed or fail depending on detection, but should not crash // At minimum the result body should exist EXPECT_GE(result.body.num_faces(), 0u); } TEST(DirectModelingTest, MoveFace_InvalidFaceId) { auto box = make_box(2, 2, 2); auto T = translate(Vector3D(0.5, 0, 0)); auto result = move_face(box, 99, T); EXPECT_FALSE(result.success); EXPECT_FALSE(result.errors.empty()); } // ═══════════════════════════════════════════════════════════ // replace_face — 面替换测试 // ═══════════════════════════════════════════════════════════ TEST(DirectModelingTest, ReplaceFace_WithSameSurface) { auto box = make_box(2, 2, 2); // Replace face 0 with its own surface (should succeed) const auto& orig_face = box.face(0); const auto& orig_surf = box.surface(orig_face.surface_id); auto result = replace_face(box, 0, orig_surf); EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, ReplaceFace_WithCurvedSurface) { auto box = make_box(2, 2, 2); // Create a slightly curved surface matching the top face boundary // Top face of box(2,2,2) has corners: (-1,-1,1), (1,-1,1), (1,1,1), (-1,1,1) std::vector> grid = { {Point3D(-1, -1, 1.0), Point3D(-1, 1, 1.0)}, {Point3D( 1, -1, 1.2), Point3D( 1, 1, 1.2)} // curved up in middle }; NurbsSurface curved(grid, {0,0,1,1}, {0,0,1,1}, {}, 1, 1); auto result = replace_face(box, 0, curved); EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, ReplaceFace_InvalidFaceId) { auto box = make_box(2, 2, 2); const auto& orig_face = box.face(0); const auto& orig_surf = box.surface(orig_face.surface_id); auto result = replace_face(box, 99, orig_surf); EXPECT_FALSE(result.success); } // ═══════════════════════════════════════════════════════════ // push_pull — 推拉测试 // ═══════════════════════════════════════════════════════════ TEST(DirectModelingTest, PushPull_ExtrudePositive) { auto box = make_box(2, 2, 2); auto result = push_pull(box, 0, 1.0); EXPECT_TRUE(result.success); // Extrusion should add side faces EXPECT_GT(result.new_faces, 0); EXPECT_GT(result.body.num_faces(), box.num_faces()); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, PushPull_CutNegative) { auto box = make_box(2, 2, 2); auto result = push_pull(box, 0, -0.3); EXPECT_TRUE(result.success); // Cut should not add new faces, but reshape existing ones auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, PushPull_ZeroDistance) { auto box = make_box(2, 2, 2); auto result = push_pull(box, 0, 0.0); // Zero distance push-pull: model unchanged (side faces degenerate) EXPECT_GE(result.body.num_faces(), box.num_faces()); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, PushPull_InvalidFaceId) { auto box = make_box(2, 2, 2); auto result = push_pull(box, 99, 1.0); EXPECT_FALSE(result.success); } // ═══════════════════════════════════════════════════════════ // offset_face — 面等距偏移测试 // ═══════════════════════════════════════════════════════════ TEST(DirectModelingTest, OffsetFace_Basic) { auto box = make_box(2, 2, 2); auto result = offset_face(box, 0, 0.3); EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, OffsetFace_Inward) { auto box = make_box(2, 2, 2); auto result = offset_face(box, 0, -0.2); EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } // ═══════════════════════════════════════════════════════════ // Validation after operations — 操作后模型验证 // ═══════════════════════════════════════════════════════════ TEST(DirectModelingTest, AllOps_PreserveValidation) { auto box = make_box(2, 2, 2); // tweak_face auto r1 = tweak_face(box, 0, 0.3); EXPECT_TRUE(validate(r1.body).valid); // move_face auto r2 = move_face(box, 0, translate(Vector3D(0.1, 0, 0))); EXPECT_TRUE(validate(r2.body).valid); // push_pull auto r3 = push_pull(box, 0, 0.5); EXPECT_TRUE(validate(r3.body).valid); // offset_face auto r4 = offset_face(box, 0, 0.2); EXPECT_TRUE(validate(r4.body).valid); } // ═══════════════════════════════════════════════════════════ // 退化情况 — 非法位置测试 // ═══════════════════════════════════════════════════════════ TEST(DirectModelingTest, Degenerate_NegativeFaceId) { auto box = make_box(2, 2, 2); auto result = tweak_face(box, -1, 0.5); EXPECT_FALSE(result.success); } TEST(DirectModelingTest, Degenerate_MoveIntoSelfIntersection) { auto box = make_box(2, 2, 2); // Move a face far enough to flip the normal → should be detected auto T = rotate_x(M_PI); // 180° rotation auto result = move_face(box, 0, T); // Should at minimum produce a body (even if invalid) EXPECT_GE(result.body.num_faces(), 0u); } TEST(DirectModelingTest, Degenerate_ExcessivePushPull) { auto box = make_box(2, 2, 2); // Push-pull with very large negative distance (cut through) auto result = push_pull(box, 0, -5.0); EXPECT_TRUE(result.success || !result.errors.empty()); // Should not crash even with excessive distance } // ═══════════════════════════════════════════════════════════ // fill_hole — 填孔测试 (v5.5) // ═══════════════════════════════════════════════════════════ TEST(DirectModelingTest, FillHole_BoxFaceEdges) { // 使用 box 已有面的边作为 hole 边界来测试 fill_hole auto box = make_box(2, 2, 2); auto face_edges = box.face_edges(0); auto result = fill_hole(box, face_edges); EXPECT_TRUE(result.success) << "fill_hole should succeed with valid edge loop"; EXPECT_GT(result.affected_faces, 0); // 应返回新增面的 face_id EXPECT_GT(result.body.num_faces(), box.num_faces()); // 新增了一个面 } TEST(DirectModelingTest, FillHole_TooFewEdges) { auto box = make_box(2, 2, 2); // 少于 3 条边不能构成面 std::vector two_edges = {0, 1}; auto result = fill_hole(box, two_edges); EXPECT_FALSE(result.success); EXPECT_FALSE(result.errors.empty()); } TEST(DirectModelingTest, FillHole_NonClosedLoop) { auto box = make_box(2, 2, 2); // 取不连续的边,无法形成封闭环 std::vector non_closed = {0, 5, 10}; auto result = fill_hole(box, non_closed); EXPECT_FALSE(result.success); EXPECT_FALSE(result.errors.empty()); } TEST(DirectModelingTest, FillHole_InvalidEdgeId) { auto box = make_box(2, 2, 2); // 包含不存在的边 ID std::vector bad_edges = {0, 999, 1, 2}; auto result = fill_hole(box, bad_edges); EXPECT_FALSE(result.success); EXPECT_FALSE(result.errors.empty()); } TEST(DirectModelingTest, FillHole_TriangleHole) { // 用三条边(三角形 hole)测试 auto box = make_box(2, 2, 2); // 取某个面的前 3 条边组成三角形环 auto face_edges = box.face_edges(0); ASSERT_GE(face_edges.size(), 3u); std::vector tri_loop = {face_edges[0], face_edges[1], face_edges[2]}; // 检查这 3 条边是否首尾相连 const auto& b = box; int v00 = b.edge(tri_loop[0]).v_start; int v01 = b.edge(tri_loop[0]).v_end; int v10 = b.edge(tri_loop[1]).v_start; int v11 = b.edge(tri_loop[1]).v_end; int v20 = b.edge(tri_loop[2]).v_start; int v21 = b.edge(tri_loop[2]).v_end; if (v01 == v10 && v11 == v20 && v21 == v00) { auto result = fill_hole(box, tri_loop); // 即使可能不完全封闭整个面,也不应崩溃 EXPECT_GE(result.body.num_faces(), 0u); } // 如果不能构成环则跳过 } // ═══════════════════════════════════════════════════════════ // pull_up_to_face — 拉伸面到目标面测试 (v5.5) // ═══════════════════════════════════════════════════════════ TEST(DirectModelingTest, PullUpFace_Basic) { // Box: 面 0 (顶面, z=1) → 面 2 (底面, z=-1),拉到底面 auto box = make_box(2, 2, 2); // 面 0 和面 2 是相对面 auto result = pull_up_to_face(box, 0, 2); EXPECT_TRUE(result.success) << "pull_up_to_face should succeed"; EXPECT_GT(result.body.num_faces(), 0u); } TEST(DirectModelingTest, PullUpFace_OppositeFace) { // 将 box 的一个侧面拉到对面 auto box = make_box(2, 2, 2); // 面 3 和面 4 是相对的侧面 (x 方向) auto result = pull_up_to_face(box, 3, 4); EXPECT_TRUE(result.success); auto val = validate(result.body); EXPECT_TRUE(val.valid); } TEST(DirectModelingTest, PullUpFace_InvalidFaceId) { auto box = make_box(2, 2, 2); auto result = pull_up_to_face(box, 99, 0); EXPECT_FALSE(result.success); EXPECT_FALSE(result.errors.empty()); } TEST(DirectModelingTest, PullUpFace_SameFace) { auto box = make_box(2, 2, 2); auto result = pull_up_to_face(box, 0, 0); EXPECT_FALSE(result.success); EXPECT_FALSE(result.errors.empty()); } TEST(DirectModelingTest, PullUpFace_InvalidTarget) { auto box = make_box(2, 2, 2); auto result = pull_up_to_face(box, 0, 999); EXPECT_FALSE(result.success); EXPECT_FALSE(result.errors.empty()); }