Files
茂之钳 1db1a83c5d
CI / Build & Test (push) Failing after 24s
CI / Release Build (push) Failing after 25s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v4.3): wall thickness analysis — centroid-offset sampling
- 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
2026-07-25 07:23:28 +00:00

254 lines
9.4 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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
}