1db1a83c5d
- analyze_wall_thickness(): samples surface points, computes min distance to vertices/centroids on other faces (no mesh tessellation needed) - WallThicknessResult: min/max/avg, thin_threshold, thick_threshold - 3 new tests, 29/29 passing
254 lines
9.4 KiB
C++
254 lines
9.4 KiB
C++
#include <gtest/gtest.h>
|
||
#include "vde/brep/brep.h"
|
||
#include "vde/brep/modeling.h"
|
||
#include "vde/brep/measure.h"
|
||
#include "vde/brep/assembly.h"
|
||
#include <cmath>
|
||
|
||
using namespace vde::brep;
|
||
using namespace vde::core;
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// Volume measurements
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(MeasureTest, Volume_UnitBox) {
|
||
auto box = make_box(2, 2, 2);
|
||
double vol = volume(box);
|
||
EXPECT_NEAR(vol, 8.0, 0.2);
|
||
}
|
||
|
||
TEST(MeasureTest, Volume_LargeBox) {
|
||
auto box = make_box(10, 5, 3);
|
||
double vol = volume(box);
|
||
EXPECT_NEAR(vol, 150.0, 1.0);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// Surface area measurements
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(MeasureTest, SurfaceArea_UnitBox) {
|
||
auto box = make_box(2, 2, 2);
|
||
double area = surface_area(box);
|
||
EXPECT_NEAR(area, 24.0, 0.5);
|
||
}
|
||
|
||
TEST(MeasureTest, SurfaceArea_LargeBox) {
|
||
auto box = make_box(10, 5, 3);
|
||
double area = surface_area(box);
|
||
double expected = 2.0 * (10*5 + 10*3 + 5*3);
|
||
EXPECT_NEAR(area, expected, 2.0);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// Centroid measurements
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(MeasureTest, Centroid_BoxAtOrigin) {
|
||
auto box = make_box(2, 2, 2);
|
||
auto c = centroid(box);
|
||
EXPECT_NEAR(c.x(), 0.0, 0.01);
|
||
EXPECT_NEAR(c.y(), 0.0, 0.01);
|
||
EXPECT_NEAR(c.z(), 0.0, 0.01);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// Distance measurements
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(MeasureTest, Distance_SeparatedBoxes) {
|
||
auto box1 = make_box(1, 1, 1);
|
||
auto box2 = make_box(1, 1, 1);
|
||
double d = distance(box1, box2);
|
||
EXPECT_NEAR(d, 0.0, 0.1);
|
||
}
|
||
|
||
TEST(MeasureTest, Distance_OverlappingBoxes) {
|
||
auto box1 = make_box(2, 2, 2);
|
||
auto box2 = make_box(2, 2, 2);
|
||
double d = distance(box1, box2);
|
||
EXPECT_NEAR(d, 0.0, 0.1);
|
||
}
|
||
|
||
TEST(MeasureTest, Volume_NonNegative) {
|
||
auto box = make_box(2, 2, 2);
|
||
EXPECT_GE(volume(box), 0.0);
|
||
}
|
||
|
||
TEST(MeasureTest, SurfaceArea_NonNegative) {
|
||
auto box = make_box(2, 2, 2);
|
||
EXPECT_GE(surface_area(box), 0.0);
|
||
}
|
||
|
||
TEST(MeasureTest, Distance_NonNegative) {
|
||
auto box1 = make_box(1, 1, 1);
|
||
auto box2 = make_box(1, 1, 1);
|
||
EXPECT_GE(distance(box1, box2), 0.0);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// Mass properties
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(MassPropertiesTest, Box_Volume) {
|
||
auto box = make_box(2, 2, 2);
|
||
auto mp = mass_properties(box);
|
||
EXPECT_NEAR(mp.volume, 8.0, 1.0);
|
||
}
|
||
|
||
TEST(MassPropertiesTest, Box_CentroidAtOrigin) {
|
||
auto box = make_box(2, 2, 2);
|
||
auto mp = mass_properties(box);
|
||
EXPECT_NEAR(mp.centroid.x(), 0.0, 0.3);
|
||
EXPECT_NEAR(mp.centroid.y(), 0.0, 0.3);
|
||
EXPECT_NEAR(mp.centroid.z(), 0.0, 0.3);
|
||
}
|
||
|
||
TEST(MassPropertiesTest, Box_InertiaPositive) {
|
||
auto box = make_box(2, 2, 2);
|
||
auto mp = mass_properties(box);
|
||
EXPECT_GT(mp.inertia(0,0), 0.0);
|
||
EXPECT_GT(mp.inertia(1,1), 0.0);
|
||
EXPECT_GT(mp.inertia(2,2), 0.0);
|
||
}
|
||
|
||
TEST(MassPropertiesTest, Box_GyrationRadii) {
|
||
auto box = make_box(2, 2, 2);
|
||
auto mp = mass_properties(box);
|
||
EXPECT_GT(mp.gyration_radii.x(), 0.0);
|
||
EXPECT_GT(mp.gyration_radii.y(), 0.0);
|
||
EXPECT_GT(mp.gyration_radii.z(), 0.0);
|
||
}
|
||
|
||
TEST(MassPropertiesTest, EmptyBody) {
|
||
BrepModel empty;
|
||
auto mp = mass_properties(empty);
|
||
EXPECT_NEAR(mp.volume, 0.0, 1e-10);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// Clearance (gap analysis)
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(ClearanceTest, SeparatedBoxes) {
|
||
auto box1 = make_box(1, 1, 1);
|
||
auto box2 = make_box(1, 1, 1);
|
||
double c = clearance(box1, box2);
|
||
EXPECT_GE(c, 0.0);
|
||
}
|
||
|
||
TEST(ClearanceTest, OverlappingBoxes) {
|
||
auto box = make_box(2, 2, 2);
|
||
EXPECT_NEAR(clearance(box, box), 0.0, 1e-6);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// Assembly mass properties
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(AssemblyMassTest, SinglePart) {
|
||
Assembly assy("test");
|
||
assy.root.add_part("box", make_box(2, 2, 2));
|
||
auto mp = assembly_mass_properties(assy.root);
|
||
EXPECT_NEAR(mp.volume, 8.0, 1.0);
|
||
}
|
||
|
||
TEST(AssemblyMassTest, TwoPartsCombined) {
|
||
Assembly assy("test");
|
||
assy.root.add_part("box1", make_box(2, 2, 2));
|
||
assy.root.add_part("box2", make_box(2, 2, 2));
|
||
auto mp = assembly_mass_properties(assy.root);
|
||
EXPECT_NEAR(mp.volume, 16.0, 2.0);
|
||
}
|
||
|
||
TEST(AssemblyMassTest, NestedAssembly) {
|
||
Assembly assy("test");
|
||
auto* sub = assy.root.add_subassembly("sub");
|
||
sub->add_part("box", make_box(2, 2, 2));
|
||
assy.root.add_part("box2", make_box(2, 2, 2));
|
||
auto mp = assembly_mass_properties(assy.root);
|
||
EXPECT_NEAR(mp.volume, 16.0, 2.0);
|
||
}
|
||
|
||
TEST(AssemblyMassTest, EmptyAssembly) {
|
||
Assembly assy("test");
|
||
auto mp = assembly_mass_properties(assy.root);
|
||
EXPECT_NEAR(mp.volume, 0.0, 1e-10);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// BOM (Bill of Materials)
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(BomTest, SinglePart) {
|
||
Assembly assy("test");
|
||
assy.root.add_part("bracket", make_box(2, 2, 2));
|
||
auto bom = generate_bom(assy.root);
|
||
ASSERT_EQ(bom.size(), 1u);
|
||
EXPECT_EQ(bom[0].name, "bracket");
|
||
EXPECT_EQ(bom[0].quantity, 1);
|
||
}
|
||
|
||
TEST(BomTest, MultipleSameParts) {
|
||
Assembly assy("test");
|
||
assy.root.add_part("bolt", make_box(1, 1, 1));
|
||
assy.root.add_part("bolt", make_box(1, 1, 1));
|
||
assy.root.add_part("bolt", make_box(1, 1, 1));
|
||
auto bom = generate_bom(assy.root);
|
||
ASSERT_EQ(bom.size(), 1u);
|
||
EXPECT_EQ(bom[0].quantity, 3);
|
||
}
|
||
|
||
TEST(BomTest, DifferentParts) {
|
||
Assembly assy("test");
|
||
assy.root.add_part("base", make_box(4, 4, 1));
|
||
assy.root.add_part("pillar", make_cylinder(1, 8));
|
||
assy.root.add_part("top", make_box(4, 4, 1));
|
||
auto bom = generate_bom(assy.root);
|
||
ASSERT_EQ(bom.size(), 3u);
|
||
}
|
||
|
||
TEST(BomTest, NestedBom) {
|
||
Assembly assy("test");
|
||
auto* sub = assy.root.add_subassembly("sub_assy");
|
||
sub->add_part("pin", make_cylinder(0.5, 2));
|
||
sub->add_part("pin", make_cylinder(0.5, 2));
|
||
assy.root.add_part("frame", make_box(5, 5, 1));
|
||
auto bom = generate_bom(assy.root);
|
||
EXPECT_GE(bom.size(), 2u);
|
||
}
|
||
|
||
TEST(BomTest, EmptyAssembly) {
|
||
Assembly assy("test");
|
||
auto bom = generate_bom(assy.root);
|
||
EXPECT_TRUE(bom.empty());
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// Wall thickness analysis
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(WallThicknessTest, Box) {
|
||
auto box = make_box(4, 4, 4); // 4×4×4 box
|
||
auto result = analyze_wall_thickness(box, 3);
|
||
// For a 4×4×4 box, wall thickness ~4.0 (opposite face distance)
|
||
EXPECT_GT(result.min_thickness, 0.0);
|
||
EXPECT_GT(result.max_thickness, 0.0);
|
||
EXPECT_GT(result.avg_thickness, 0.0);
|
||
}
|
||
|
||
TEST(WallThicknessTest, EmptyBody) {
|
||
BrepModel empty;
|
||
auto result = analyze_wall_thickness(empty);
|
||
EXPECT_NEAR(result.min_thickness, 0.0, 1e-10);
|
||
}
|
||
|
||
TEST(WallThicknessTest, ThinWallDetection) {
|
||
// 4×4×0.5 thin plate
|
||
auto plate = make_box(4, 4, 0.5);
|
||
auto result = analyze_wall_thickness(plate, 3);
|
||
EXPECT_GT(result.min_thickness, 0.0);
|
||
EXPECT_LT(result.min_thickness, 1.0); // thin
|
||
}
|