#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 }