169 lines
7.4 KiB
C++
169 lines
7.4 KiB
C++
|
|
#include <gtest/gtest.h>
|
||
|
|
#include "vde/brep/brep.h"
|
||
|
|
#include "vde/brep/modeling.h"
|
||
|
|
#include "vde/brep/draft_analysis.h"
|
||
|
|
#include <cmath>
|
||
|
|
|
||
|
|
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<int>(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);
|
||
|
|
}
|