#include #include "vde/brep/brep.h" #include "vde/brep/modeling.h" #include "vde/brep/draft_analysis.h" #include using namespace vde::brep; using namespace vde::core; // ═══════════════════════════════════════════════════════════ // Draft Angle — Single Face // ═══════════════════════════════════════════════════════════ TEST(DraftAnalysisTest, DraftAngle_BoxTopFace) { auto box = make_box(2, 2, 2); // centered at origin, faces at ±1 // Top face (z = 1): normal = (0, 0, 1) // Pull direction (0, 0, 1): face normal aligned → angle ≈ 0 double angle = draft_angle(box, 0, Vector3D(0, 0, 1)); EXPECT_NEAR(std::abs(angle), 0.0, 0.1); } TEST(DraftAnalysisTest, DraftAngle_BoxSideFace) { auto box = make_box(2, 2, 2); // Side face normal is perpendicular to pull direction (0,0,1) // Draft angle ≈ 90° (π/2) double angle = draft_angle(box, 0, Vector3D(0, 0, 1)); // Side face: normal.x = ±1, dot(pull) ≈ 0 → angle ≈ π/2 // We just verify it's finite and reasonable EXPECT_TRUE(std::isfinite(angle)); } TEST(DraftAnalysisTest, DraftAngle_CylinderTop) { auto cyl = make_cylinder(2, 6, 32); // Top cap face normal ≈ (0, 0, 1), pull direction (0, 0, 1) → angle ≈ 0 double angle = draft_angle(cyl, 0, Vector3D(0, 0, 1)); EXPECT_NEAR(std::abs(angle), 0.0, 0.15); } TEST(DraftAnalysisTest, DraftAngle_Sphere) { auto sphere = make_sphere(2, 16, 16); // Sphere has varying normals; pull dir (0,0,1) → angle varies double angle = draft_angle(sphere, 0, Vector3D(0, 0, 1)); EXPECT_TRUE(std::isfinite(angle)); } // ═══════════════════════════════════════════════════════════ // Full Model Analysis // ═══════════════════════════════════════════════════════════ TEST(DraftAnalysisTest, AnalyzeDraft_Box) { auto box = make_box(2, 2, 2); auto result = analyze_draft(box, Vector3D(0, 0, 1), 0.01745); // 1° min EXPECT_EQ(result.faces.size(), box.num_faces()); EXPECT_GE(result.positive_count + result.negative_count + result.zero_draft_count + result.undercut_count, 1); EXPECT_TRUE(std::isfinite(result.area_weighted_angle)); } TEST(DraftAnalysisTest, AnalyzeDraft_PullUp) { auto box = make_box(2, 2, 2); // Pull direction (0,0,1): top = zero draft, sides = positive/negative depending on normal auto result = analyze_draft(box, Vector3D(0, 0, 1)); EXPECT_GE(result.faces.size(), 1u); // Check that classifications sum to face count int total = result.positive_count + result.negative_count + result.zero_draft_count + result.undercut_count; EXPECT_EQ(total, static_cast(result.faces.size())); } TEST(DraftAnalysisTest, AnalyzeDraft_PullSide) { auto box = make_box(2, 2, 2); auto result = analyze_draft(box, Vector3D(1, 0, 0)); EXPECT_GE(result.faces.size(), 1u); } TEST(DraftAnalysisTest, AnalyzeDraft_EmptyBody) { BrepModel empty; auto result = analyze_draft(empty, Vector3D(0, 0, 1)); EXPECT_EQ(result.faces.size(), 0u); EXPECT_TRUE(result.is_moldable()); } TEST(DraftAnalysisTest, AnalyzeDraft_MinAngleLarger) { auto box = make_box(2, 2, 2); // With a very large min angle (45°), most faces should be classified as zero draft auto result = analyze_draft(box, Vector3D(0, 0, 1), M_PI / 4.0); EXPECT_GE(result.zero_draft_count, 0); } // ═══════════════════════════════════════════════════════════ // Classification // ═══════════════════════════════════════════════════════════ TEST(DraftAnalysisTest, Classification_Positive) { auto box = make_box(2, 2, 2); auto result = analyze_draft(box, Vector3D(0, 0, 1), 0.001); // At least some faces should be classified bool has_positive = result.positive_count > 0; bool has_negative = result.negative_count > 0; bool has_zero = result.zero_draft_count > 0; EXPECT_TRUE(has_positive || has_negative || has_zero); } TEST(DraftAnalysisTest, HasUndercut_SimpleBox) { auto box = make_box(2, 2, 2); auto result = analyze_draft(box, Vector3D(0, 0, 1)); // Simple box along Z should be moldable (no undercut) EXPECT_TRUE(result.is_moldable()); } // ═══════════════════════════════════════════════════════════ // Report // ═══════════════════════════════════════════════════════════ TEST(DraftAnalysisTest, Report_NotEmpty) { auto box = make_box(2, 2, 2); auto result = analyze_draft(box, Vector3D(0, 0, 1)); auto report = draft_report(result); EXPECT_FALSE(report.empty()); EXPECT_NE(report.find("Draft Analysis Report"), std::string::npos); EXPECT_NE(report.find("Moldability"), std::string::npos); } // ═══════════════════════════════════════════════════════════ // Angle Distribution // ═══════════════════════════════════════════════════════════ TEST(DraftAnalysisTest, AngleDistribution_NotEmpty) { auto box = make_box(2, 2, 2); auto result = analyze_draft(box, Vector3D(0, 0, 1)); auto dist = result.angle_distribution(10); EXPECT_GT(dist.size(), 0u); } TEST(DraftAnalysisTest, AngleDistribution_ZeroBins) { auto box = make_box(2, 2, 2); auto result = analyze_draft(box, Vector3D(0, 0, 1)); auto dist = result.angle_distribution(0); EXPECT_EQ(dist.size(), 0u); } // ═══════════════════════════════════════════════════════════ // Draft Face Creation (stub — returns false) // ═══════════════════════════════════════════════════════════ TEST(DraftAnalysisTest, CreateDraftFace_Stub) { auto box = make_box(2, 2, 2); // create_draft_face is a stub (needs mutable surface access) bool ok = create_draft_face(box, 0, Vector3D(0, 0, 1), 0.1); // Stub returns false (not yet implemented at topology level) EXPECT_FALSE(ok); } TEST(DraftAnalysisTest, ApplyDraft_EmptyList) { auto box = make_box(2, 2, 2); int count = apply_draft(box, {}, Vector3D(0, 0, 1), 0.1); EXPECT_EQ(count, 0); } TEST(DraftAnalysisTest, ApplyDraft_Stub) { auto box = make_box(2, 2, 2); int count = apply_draft(box, {0, 1}, Vector3D(0, 0, 1), 0.1); // create_draft_face returns false → count = 0 EXPECT_EQ(count, 0); }