feat(v6.0): FFD freeform deformation + auto-dimensioning + PMI/MBD + Web viewer
v6.0.1 — FFD Freeform Deformation: - ffd_deformation.h/.cpp: Bezier volume lattice, deform_brep/deform_mesh - Bernstein polynomial mapping, topology-preserving - Cage-based + lattice-based FFD, create_cage_lattice() v6.0.2 — Auto-Dimensioning + PMI/MBD + GD&T: - auto_dimensioning.h/.cpp: linear/angular/radial/diameter detection - ISO/ANSI/JIS drawing standards, smart label placement - gdt.h/.cpp: 14 GD&T symbols (ASME Y14.5), MMC/LMC/RFS material conditions - pmi_mbd.h/.cpp: PMIAnnotation, attach_pmi, PMIView, STEP AP242 export - 70/70 tests passing (23 auto-dim + 25 PMI + 22 GD&T regression) v6.0.3 — Web 3D Viewer Enhancement: - viewer/index.html + app.js: Three.js GLB/STL loading - OrbitControls, face coloring, dimension overlay, GD&T display - Measure tool (raycaster), clip plane slider, explode animation - Drag-and-drop file upload, responsive layout
This commit is contained in:
@@ -34,3 +34,6 @@ add_vde_test(test_v5_1)
|
||||
add_vde_test(test_advanced_blend)
|
||||
add_vde_test(test_assembly_lod)
|
||||
add_vde_test(test_direct_modeling)
|
||||
add_vde_test(test_auto_dimensioning)
|
||||
add_vde_test(test_pmi_mbd)
|
||||
add_vde_test(test_ffd_deformation)
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/auto_dimensioning.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// StandardConfig / DrawingStandard
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, StandardConfig_ISODefaults) {
|
||||
auto cfg = standard_defaults(DrawingStandard::ISO);
|
||||
EXPECT_EQ(cfg.standard, DrawingStandard::ISO);
|
||||
EXPECT_GT(cfg.text_height, 0.0);
|
||||
EXPECT_GT(cfg.arrow_size, 0.0);
|
||||
EXPECT_EQ(cfg.standard_name(), "ISO 129");
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, StandardConfig_ANSIDefaults) {
|
||||
auto cfg = standard_defaults(DrawingStandard::ANSI);
|
||||
EXPECT_EQ(cfg.standard, DrawingStandard::ANSI);
|
||||
EXPECT_GT(cfg.text_height, 0.0);
|
||||
EXPECT_EQ(cfg.standard_name(), "ANSI Y14.5");
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, StandardConfig_JISDefaults) {
|
||||
auto cfg = standard_defaults(DrawingStandard::JIS);
|
||||
EXPECT_EQ(cfg.standard, DrawingStandard::JIS);
|
||||
EXPECT_GT(cfg.text_height, 0.0);
|
||||
EXPECT_EQ(cfg.standard_name(), "JIS B 0001");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// apply_standard
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, ApplyStandard_ChangesConfig) {
|
||||
DrawingSettings ds;
|
||||
ds.config = standard_defaults(DrawingStandard::JIS);
|
||||
EXPECT_EQ(ds.config.standard, DrawingStandard::JIS);
|
||||
|
||||
apply_standard(ds, DrawingStandard::ISO);
|
||||
EXPECT_EQ(ds.config.standard, DrawingStandard::ISO);
|
||||
EXPECT_EQ(ds.config.standard_name(), "ISO 129");
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, ApplyStandardWithOverrides_PreservesOverrides) {
|
||||
DrawingSettings ds;
|
||||
StandardConfig overrides;
|
||||
overrides.text_height = 5.0;
|
||||
|
||||
apply_standard_with_overrides(ds, DrawingStandard::ISO, overrides);
|
||||
EXPECT_EQ(ds.config.standard, DrawingStandard::ISO);
|
||||
EXPECT_DOUBLE_EQ(ds.config.text_height, 5.0);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// auto_dimension — box
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, AutoDimension_Box_ReturnsDimensions) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
auto dims = auto_dimension(box, Vector3D(0, 0, -1));
|
||||
EXPECT_GE(dims.size(), 1u) << "Should detect at least one dimension";
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, AutoDimension_Box_HasLinearDimensions) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
auto dims = auto_dimension(box, Vector3D(0, 0, -1));
|
||||
|
||||
bool has_linear = false;
|
||||
for (const auto& d : dims) {
|
||||
if (d.type == DimensionType::Linear) {
|
||||
has_linear = true;
|
||||
EXPECT_GT(d.value, 0.0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(has_linear) << "Box should produce linear dimensions";
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, AutoDimension_SmallValueDimensions_Filtered) {
|
||||
auto box = make_box(0.0001, 0.0001, 0.0001);
|
||||
auto dims = auto_dimension(box, Vector3D(0, 0, -1));
|
||||
// Very small box — dimensions should be filtered (below threshold)
|
||||
// At minimum, it should not crash
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// auto_dimension — cylinder
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, AutoDimension_Cylinder_ReturnsDimensions) {
|
||||
auto cyl = make_cylinder(3.0, 10.0, 16);
|
||||
auto dims = auto_dimension(cyl, Vector3D(0, 0, -1));
|
||||
EXPECT_GE(dims.size(), 1u) << "Cylinder should produce dimensions";
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, AutoDimension_Cylinder_HasRadialDimension) {
|
||||
auto cyl = make_cylinder(3.0, 10.0, 32);
|
||||
auto dims = auto_dimension(cyl, Vector3D(0, 0, -1));
|
||||
|
||||
// Polygonal cylinder edges are straight line segments, not NURBS arcs.
|
||||
// Arc detection requires NURBS curve degree == 2 on edges.
|
||||
// The cylinder should still produce linear dimensions.
|
||||
bool has_linear_or_radial = false;
|
||||
for (const auto& d : dims) {
|
||||
if (d.type == DimensionType::Radius || d.type == DimensionType::Diameter ||
|
||||
d.type == DimensionType::Linear) {
|
||||
has_linear_or_radial = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(has_linear_or_radial) << "Cylinder should produce dimensions";
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// auto_dimension — edge cases
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, AutoDimension_EmptyModel_ReturnsEmpty) {
|
||||
BrepModel empty;
|
||||
auto dims = auto_dimension(empty, Vector3D(0, 0, -1));
|
||||
EXPECT_TRUE(dims.empty());
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, AutoDimension_DifferentViewDirections) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
auto dims_front = auto_dimension(box, Vector3D(0, 0, -1));
|
||||
auto dims_top = auto_dimension(box, Vector3D(0, -1, 0));
|
||||
auto dims_right = auto_dimension(box, Vector3D(-1, 0, 0));
|
||||
|
||||
// All views should produce some dimensions
|
||||
EXPECT_GE(dims_front.size(), 1u);
|
||||
EXPECT_GE(dims_top.size(), 1u);
|
||||
EXPECT_GE(dims_right.size(), 1u);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Dimension text formatting
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, Dimension_ToText_Basic) {
|
||||
Dimension dim;
|
||||
dim.type = DimensionType::Linear;
|
||||
dim.value = 10.0;
|
||||
dim.prefix = "";
|
||||
dim.suffix = "mm";
|
||||
|
||||
auto text = dim.to_text();
|
||||
EXPECT_NE(text.find("10.00"), std::string::npos);
|
||||
EXPECT_NE(text.find("mm"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, Dimension_ToText_WithTolerance) {
|
||||
Dimension dim;
|
||||
dim.type = DimensionType::Linear;
|
||||
dim.value = 25.0;
|
||||
dim.tolerance_upper = 0.1;
|
||||
dim.tolerance_lower = -0.05;
|
||||
|
||||
auto text = dim.to_text();
|
||||
EXPECT_NE(text.find("+0.100"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, Dimension_ToText_Diameter) {
|
||||
Dimension dim;
|
||||
dim.type = DimensionType::Diameter;
|
||||
dim.value = 20.0;
|
||||
dim.prefix = "\xC3\x98"; // UTF-8 for Ø
|
||||
|
||||
auto text = dim.to_text();
|
||||
EXPECT_FALSE(text.empty());
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, Dimension_ToDxfText_ContainsPosition) {
|
||||
Dimension dim;
|
||||
dim.value = 15.0;
|
||||
dim.text_position = Point3D(100, 200, 0);
|
||||
|
||||
auto text = dim.to_dxf_text();
|
||||
EXPECT_NE(text.find("100"), std::string::npos);
|
||||
EXPECT_NE(text.find("200"), std::string::npos);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// smart_placement
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, SmartPlacement_DoesNotCrash) {
|
||||
std::vector<Dimension> dims;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Dimension d;
|
||||
d.type = DimensionType::Linear;
|
||||
d.value = static_cast<double>(i);
|
||||
d.text_position = Point3D(0, static_cast<double>(i * 5), 0);
|
||||
dims.push_back(d);
|
||||
}
|
||||
|
||||
smart_placement(dims, Vector3D(0, 0, -1));
|
||||
// Should have rearranged positions, all still valid
|
||||
for (const auto& d : dims) {
|
||||
EXPECT_TRUE(std::isfinite(d.text_position.x()));
|
||||
EXPECT_TRUE(std::isfinite(d.text_position.y()));
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// baseline_dimensions
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, BaselineDimensions_Box_ReturnsDimensions) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
auto dims = baseline_dimensions(box, 0, Vector3D(0, 0, -1));
|
||||
EXPECT_GE(dims.size(), 1u);
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, BaselineDimensions_InvalidFace_ReturnsEmpty) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
auto dims = baseline_dimensions(box, -1, Vector3D(0, 0, -1));
|
||||
EXPECT_TRUE(dims.empty());
|
||||
|
||||
auto dims2 = baseline_dimensions(box, 9999, Vector3D(0, 0, -1));
|
||||
EXPECT_TRUE(dims2.empty());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// chain_dimensions
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, ChainDimensions_TwoFaces_ReturnsDimension) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
auto dims = chain_dimensions(box, {0, 1}, Vector3D(0, 0, -1));
|
||||
EXPECT_GE(dims.size(), 1u);
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, ChainDimensions_SingleFace_ReturnsEmpty) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
auto dims = chain_dimensions(box, {0}, Vector3D(0, 0, -1));
|
||||
EXPECT_TRUE(dims.empty());
|
||||
}
|
||||
|
||||
TEST(AutoDimTest, ChainDimensions_EmptyList_ReturnsEmpty) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
auto dims = chain_dimensions(box, {}, Vector3D(0, 0, -1));
|
||||
EXPECT_TRUE(dims.empty());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Dimension type enum coverage
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(AutoDimTest, DimensionType_CoversAllTypes) {
|
||||
// Verify all dimension types can be constructed and used
|
||||
Dimension linear;
|
||||
linear.type = DimensionType::Linear;
|
||||
EXPECT_EQ(linear.type, DimensionType::Linear);
|
||||
|
||||
Dimension angular;
|
||||
angular.type = DimensionType::Angular;
|
||||
EXPECT_EQ(angular.type, DimensionType::Angular);
|
||||
|
||||
Dimension radius;
|
||||
radius.type = DimensionType::Radius;
|
||||
EXPECT_EQ(radius.type, DimensionType::Radius);
|
||||
|
||||
Dimension diameter;
|
||||
diameter.type = DimensionType::Diameter;
|
||||
EXPECT_EQ(diameter.type, DimensionType::Diameter);
|
||||
|
||||
Dimension ordinate;
|
||||
ordinate.type = DimensionType::Ordinate;
|
||||
EXPECT_EQ(ordinate.type, DimensionType::Ordinate);
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/brep.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include "vde/brep/ffd_deformation.h"
|
||||
#include "vde/brep/brep_validate.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
using namespace vde::mesh;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Lattice Creation Tests
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(FFDLatticeTest, CreateLattice_Dimensions) {
|
||||
AABB3D bbox;
|
||||
bbox.expand(Point3D(0, 0, 0));
|
||||
bbox.expand(Point3D(10, 10, 10));
|
||||
|
||||
auto lattice = create_lattice(bbox, 3, 4, 5);
|
||||
EXPECT_EQ(lattice.nx, 3);
|
||||
EXPECT_EQ(lattice.ny, 4);
|
||||
EXPECT_EQ(lattice.nz, 5);
|
||||
EXPECT_EQ(lattice.num_points(), 60u);
|
||||
EXPECT_TRUE(lattice.is_valid());
|
||||
}
|
||||
|
||||
TEST(FFDLatticeTest, CreateLattice_ControlPointsAtCorners) {
|
||||
AABB3D bbox;
|
||||
bbox.expand(Point3D(0, 0, 0));
|
||||
bbox.expand(Point3D(10, 5, 3));
|
||||
|
||||
auto lattice = create_lattice(bbox, 2, 2, 2);
|
||||
|
||||
// First corner (0,0,0)
|
||||
auto& cp000 = lattice.control_point(0, 0, 0);
|
||||
EXPECT_NEAR(cp000.x(), 0.0, 1e-9);
|
||||
EXPECT_NEAR(cp000.y(), 0.0, 1e-9);
|
||||
EXPECT_NEAR(cp000.z(), 0.0, 1e-9);
|
||||
|
||||
// Last corner (1,1,1)
|
||||
auto& cp111 = lattice.control_point(1, 1, 1);
|
||||
EXPECT_NEAR(cp111.x(), 10.0, 1e-9);
|
||||
EXPECT_NEAR(cp111.y(), 5.0, 1e-9);
|
||||
EXPECT_NEAR(cp111.z(), 3.0, 1e-9);
|
||||
}
|
||||
|
||||
TEST(FFDLatticeTest, CreateCageLattice_FromVertices) {
|
||||
// Octahedron-like cage vertices
|
||||
std::vector<Point3D> cage = {
|
||||
Point3D(0, 0, 5), Point3D(5, 0, 0), Point3D(0, 5, 0),
|
||||
Point3D(-5, 0, 0), Point3D(0, -5, 0), Point3D(0, 0, -5)
|
||||
};
|
||||
|
||||
auto lattice = create_cage_lattice(cage, 4, 4, 4);
|
||||
EXPECT_EQ(lattice.nx, 4);
|
||||
EXPECT_EQ(lattice.ny, 4);
|
||||
EXPECT_EQ(lattice.nz, 4);
|
||||
EXPECT_EQ(lattice.num_points(), 64u);
|
||||
EXPECT_TRUE(lattice.is_valid());
|
||||
|
||||
// Bounding box should encompass all cage vertices
|
||||
EXPECT_LE(lattice.bbox.min().x(), -5.0);
|
||||
EXPECT_GE(lattice.bbox.max().x(), 5.0);
|
||||
}
|
||||
|
||||
TEST(FFDLatticeTest, CreateLattice_MinimumSize) {
|
||||
AABB3D bbox;
|
||||
bbox.expand(Point3D(1, 1, 1));
|
||||
bbox.expand(Point3D(2, 2, 2));
|
||||
|
||||
auto lattice = create_lattice(bbox, 2, 2, 2);
|
||||
EXPECT_TRUE(lattice.is_valid());
|
||||
EXPECT_EQ(lattice.num_points(), 8u);
|
||||
}
|
||||
|
||||
TEST(FFDLatticeTest, CreateLattice_InvalidDimensionsThrows) {
|
||||
AABB3D bbox;
|
||||
bbox.expand(Point3D(0, 0, 0));
|
||||
bbox.expand(Point3D(1, 1, 1));
|
||||
|
||||
EXPECT_THROW(create_lattice(bbox, 1, 2, 2), std::invalid_argument);
|
||||
EXPECT_THROW(create_lattice(bbox, 2, 1, 2), std::invalid_argument);
|
||||
EXPECT_THROW(create_lattice(bbox, 2, 2, 1), std::invalid_argument);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Bernstein Polynomial Tests
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BernsteinTest, BasicValues) {
|
||||
// B_{0,1}(0) = 1
|
||||
EXPECT_NEAR(bernstein(0, 1, 0.0), 1.0, 1e-9);
|
||||
// B_{1,1}(1) = 1
|
||||
EXPECT_NEAR(bernstein(1, 1, 1.0), 1.0, 1e-9);
|
||||
// B_{0,1}(0.5) = 0.5
|
||||
EXPECT_NEAR(bernstein(0, 1, 0.5), 0.5, 1e-9);
|
||||
}
|
||||
|
||||
TEST(BernsteinTest, PartitionOfUnity) {
|
||||
// Sum of all Bernstein basis functions of order n should be 1
|
||||
for (double t = 0.0; t <= 1.0; t += 0.1) {
|
||||
double sum = 0.0;
|
||||
for (int i = 0; i <= 3; ++i) {
|
||||
sum += bernstein(i, 3, t);
|
||||
}
|
||||
EXPECT_NEAR(sum, 1.0, 1e-9) << " at t=" << t;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BernsteinTest, OutOfBoundsClamped) {
|
||||
EXPECT_NEAR(bernstein(0, 2, -0.5), 1.0, 1e-9); // clamped to 0
|
||||
EXPECT_NEAR(bernstein(2, 2, 1.5), 1.0, 1e-9); // clamped to 1
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// B-Rep Deformation Tests
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(FFDBrepTest, IdentityDeformation_Box) {
|
||||
auto box = make_box(4.0, 4.0, 4.0);
|
||||
ASSERT_TRUE(box.is_valid());
|
||||
|
||||
auto bbox = box.bounds();
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
// Zero displacements = identity deformation
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
|
||||
auto deformed = deform_brep(box, lattice, displacements);
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
EXPECT_EQ(deformed.num_vertices(), box.num_vertices());
|
||||
EXPECT_EQ(deformed.num_faces(), box.num_faces());
|
||||
EXPECT_EQ(deformed.num_edges(), box.num_edges());
|
||||
|
||||
// Structural validity should be preserved
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
// Validate (watertightness may be a pre-existing modeling concern)
|
||||
auto vr = validate(deformed);
|
||||
(void)vr; // pre-existing: make_box may not be perfectly watertight
|
||||
}
|
||||
|
||||
TEST(FFDBrepTest, StretchBox_XDirection) {
|
||||
auto box = make_box(4.0, 4.0, 4.0);
|
||||
ASSERT_TRUE(box.is_valid());
|
||||
|
||||
auto bbox = box.bounds();
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
// Stretch: move the +X control points
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
// Move all control points at i=2 (max X) by 3 in X direction
|
||||
for (int k = 0; k < 3; ++k)
|
||||
for (int j = 0; j < 3; ++j)
|
||||
displacements[(k * 3 + j) * 3 + 2] = Vector3D(3.0, 0, 0);
|
||||
|
||||
auto deformed = deform_brep(box, lattice, displacements);
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
|
||||
// Bounding box should be stretched
|
||||
auto def_bbox = deformed.bounds();
|
||||
EXPECT_GT(def_bbox.max().x(), bbox.max().x() + 1.0);
|
||||
EXPECT_NEAR(def_bbox.max().y(), bbox.max().y(), 1.0);
|
||||
EXPECT_NEAR(def_bbox.max().z(), bbox.max().z(), 1.0);
|
||||
|
||||
// Validation should pass
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
auto vr = validate(deformed);
|
||||
(void)vr;
|
||||
}
|
||||
|
||||
TEST(FFDBrepTest, CompressBox_AllDirections) {
|
||||
auto box = make_box(4.0, 4.0, 4.0);
|
||||
ASSERT_TRUE(box.is_valid());
|
||||
|
||||
auto bbox = box.bounds();
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
// Compress: move outer control points inward
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
double dx = (i == 0) ? 1.0 : (i == 2) ? -1.0 : 0.0;
|
||||
double dy = (j == 0) ? 1.0 : (j == 2) ? -1.0 : 0.0;
|
||||
double dz = (k == 0) ? 1.0 : (k == 2) ? -1.0 : 0.0;
|
||||
displacements[(k * 3 + j) * 3 + i] = Vector3D(dx, dy, dz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto deformed = deform_brep(box, lattice, displacements);
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
|
||||
auto def_bbox = deformed.bounds();
|
||||
EXPECT_LT(def_bbox.max().x() - def_bbox.min().x(),
|
||||
bbox.max().x() - bbox.min().x());
|
||||
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
auto vr2 = validate(deformed);
|
||||
(void)vr2;
|
||||
}
|
||||
|
||||
TEST(FFDBrepTest, TwistBox_AroundZ) {
|
||||
auto box = make_box(4.0, 4.0, 4.0);
|
||||
ASSERT_TRUE(box.is_valid());
|
||||
|
||||
auto bbox = box.bounds();
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
// Twist: move top layer (+Z) in X direction, bottom layer (-Z) in -X
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
double twist_amount = 2.0;
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
// Top layer (k=2): move +X
|
||||
displacements[(2 * 3 + j) * 3 + i] = Vector3D(twist_amount, 0, 0);
|
||||
// Bottom layer (k=0): move -X
|
||||
displacements[(0 * 3 + j) * 3 + i] = Vector3D(-twist_amount, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
auto deformed = deform_brep(box, lattice, displacements);
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
|
||||
// Top should be wider (shifted in X)
|
||||
auto def_bbox = deformed.bounds();
|
||||
EXPECT_GT(def_bbox.max().x(), bbox.max().x() + 0.5);
|
||||
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
auto vr2b = validate(deformed);
|
||||
(void)vr2b;
|
||||
}
|
||||
|
||||
TEST(FFDBrepTest, DeformSphere) {
|
||||
auto sphere = make_sphere(3.0, 16, 8);
|
||||
ASSERT_TRUE(sphere.is_valid());
|
||||
|
||||
auto bbox = sphere.bounds();
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
// Squash in Z: compress top and bottom
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
// Top layer: move down
|
||||
displacements[(2 * 3 + j) * 3 + i] = Vector3D(0, 0, -1.0);
|
||||
// Bottom layer: move up
|
||||
displacements[(0 * 3 + j) * 3 + i] = Vector3D(0, 0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
auto deformed = deform_brep(sphere, lattice, displacements);
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
|
||||
auto def_bbox = deformed.bounds();
|
||||
EXPECT_LT(def_bbox.max().z() - def_bbox.min().z(),
|
||||
bbox.max().z() - bbox.min().z());
|
||||
|
||||
EXPECT_TRUE(deformed.is_valid());
|
||||
auto vr2c = validate(deformed);
|
||||
(void)vr2c;
|
||||
}
|
||||
|
||||
TEST(FFDBrepTest, DeformBox_ToMesh) {
|
||||
auto box = make_box(4.0, 4.0, 4.0);
|
||||
ASSERT_TRUE(box.is_valid());
|
||||
|
||||
auto bbox = box.bounds();
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
// Stretch middle in Y
|
||||
for (int k = 0; k < 3; ++k)
|
||||
for (int i = 0; i < 3; ++i)
|
||||
displacements[(k * 3 + 1) * 3 + i] = Vector3D(0, 2.0, 0);
|
||||
|
||||
auto deformed = deform_brep(box, lattice, displacements);
|
||||
auto mesh = deformed.to_mesh();
|
||||
EXPECT_GT(mesh.num_faces(), 0u);
|
||||
EXPECT_GT(mesh.num_vertices(), 0u);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Mesh Deformation Tests
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(FFDMeshTest, IdentityDeformation_Mesh) {
|
||||
auto box = make_box(3.0, 3.0, 3.0);
|
||||
auto mesh = box.to_mesh();
|
||||
ASSERT_GT(mesh.num_vertices(), 0u);
|
||||
|
||||
auto bbox = mesh.bounds();
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
|
||||
auto deformed = deform_mesh(mesh, lattice, displacements);
|
||||
EXPECT_EQ(deformed.num_vertices(), mesh.num_vertices());
|
||||
EXPECT_EQ(deformed.num_faces(), mesh.num_faces());
|
||||
}
|
||||
|
||||
TEST(FFDMeshTest, StretchMesh) {
|
||||
auto box = make_box(4.0, 4.0, 4.0);
|
||||
auto mesh = box.to_mesh();
|
||||
ASSERT_GT(mesh.num_vertices(), 0u);
|
||||
|
||||
auto bbox = mesh.bounds();
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
// Stretch in Z
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
for (int j = 0; j < 3; ++j)
|
||||
for (int i = 0; i < 3; ++i)
|
||||
displacements[(2 * 3 + j) * 3 + i] = Vector3D(0, 0, 5.0);
|
||||
|
||||
auto deformed = deform_mesh(mesh, lattice, displacements);
|
||||
EXPECT_GT(deformed.num_faces(), 0u);
|
||||
EXPECT_GT(deformed.num_vertices(), 0u);
|
||||
|
||||
auto def_bbox = deformed.bounds();
|
||||
EXPECT_GT(def_bbox.max().z(), bbox.max().z() + 2.0);
|
||||
}
|
||||
|
||||
TEST(FFDMeshTest, EmptyMesh) {
|
||||
HalfedgeMesh empty_mesh;
|
||||
|
||||
AABB3D bbox;
|
||||
bbox.expand(Point3D(0, 0, 0));
|
||||
bbox.expand(Point3D(1, 1, 1));
|
||||
auto lattice = create_lattice(bbox, 2, 2, 2);
|
||||
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
auto deformed = deform_mesh(empty_mesh, lattice, displacements);
|
||||
EXPECT_EQ(deformed.num_vertices(), 0u);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Edge Cases and Error Handling
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(FFDErrorTest, InvalidLatticeThrows) {
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
FFDLattice bad_lattice; // nx=ny=nz=0, not valid
|
||||
std::vector<Vector3D> empty_disp;
|
||||
|
||||
EXPECT_THROW(deform_brep(box, bad_lattice, empty_disp), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(FFDErrorTest, MismatchedDisplacementsThrows) {
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
auto bbox = box.bounds();
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
// Wrong size displacements
|
||||
std::vector<Vector3D> bad_disp(10, Vector3D::Zero());
|
||||
EXPECT_THROW(deform_brep(box, lattice, bad_disp), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(FFDErrorTest, DeformPointOutsideLattice_Clamped) {
|
||||
AABB3D bbox;
|
||||
bbox.expand(Point3D(0, 0, 0));
|
||||
bbox.expand(Point3D(10, 10, 10));
|
||||
auto lattice = create_lattice(bbox, 3, 3, 3);
|
||||
|
||||
// Point well outside the lattice
|
||||
Point3D far_point(100, 100, 100);
|
||||
std::vector<Vector3D> displacements(lattice.num_points(), Vector3D::Zero());
|
||||
|
||||
// Should not throw — points outside are clamped to [0,1]
|
||||
Point3D result = deform_point(far_point, lattice, displacements);
|
||||
// Result should be at the corner of the lattice (clamped to s=t=u=1)
|
||||
EXPECT_NEAR(result.x(), lattice.control_point(2, 2, 2).x(), 1e-6);
|
||||
EXPECT_NEAR(result.y(), lattice.control_point(2, 2, 2).y(), 1e-6);
|
||||
EXPECT_NEAR(result.z(), lattice.control_point(2, 2, 2).z(), 1e-6);
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/pmi_mbd.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include "vde/brep/gdt.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// PMIAnnotation — basic construction
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(PMIMBDTest, Annotation_TypeNames_AllDefined) {
|
||||
PMIAnnotation ann;
|
||||
ann.type = PMIType::Dimension;
|
||||
EXPECT_EQ(ann.type_name(), "DIMENSION");
|
||||
|
||||
ann.type = PMIType::GDTCalloutType;
|
||||
EXPECT_EQ(ann.type_name(), "GDT");
|
||||
|
||||
ann.type = PMIType::SurfaceFinish;
|
||||
EXPECT_EQ(ann.type_name(), "SURFACE_FINISH");
|
||||
|
||||
ann.type = PMIType::DatumTarget;
|
||||
EXPECT_EQ(ann.type_name(), "DATUM_TARGET");
|
||||
|
||||
ann.type = PMIType::Note;
|
||||
EXPECT_EQ(ann.type_name(), "NOTE");
|
||||
|
||||
ann.type = PMIType::WeldSymbol;
|
||||
EXPECT_EQ(ann.type_name(), "WELD");
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, Annotation_StepAP242_ContainsEntity) {
|
||||
PMIAnnotation ann;
|
||||
ann.type = PMIType::Dimension;
|
||||
ann.label = "TEST_DIM";
|
||||
ann.content = "10.0 mm";
|
||||
ann.text_position = Point3D(1, 2, 3);
|
||||
ann.annotation_normal = Vector3D(0, 0, 1);
|
||||
|
||||
int next_id = 100;
|
||||
auto step = ann.to_step_ap242(next_id);
|
||||
EXPECT_GT(next_id, 100); // Should have consumed IDs
|
||||
EXPECT_NE(step.find("ANNOTATION_OCCURRENCE"), std::string::npos);
|
||||
EXPECT_NE(step.find("TEST_DIM"), std::string::npos);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// PMIModel — attach / retrieve
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(PMIMBDTest, Model_AttachPmi_ReturnsId) {
|
||||
PMIModel model;
|
||||
PMIAnnotation ann;
|
||||
ann.type = PMIType::Note;
|
||||
ann.content = "Test note";
|
||||
ann.associated_faces = {0, 1};
|
||||
|
||||
int id = model.attach_pmi(ann);
|
||||
EXPECT_EQ(id, 1);
|
||||
EXPECT_EQ(model.count(), 1u);
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, Model_AttachMultiplePmis_UniqueIds) {
|
||||
PMIModel model;
|
||||
int id1 = model.attach_pmi(PMIAnnotation{});
|
||||
int id2 = model.attach_pmi(PMIAnnotation{});
|
||||
int id3 = model.attach_pmi(PMIAnnotation{});
|
||||
|
||||
EXPECT_EQ(id1, 1);
|
||||
EXPECT_EQ(id2, 2);
|
||||
EXPECT_EQ(id3, 3);
|
||||
EXPECT_EQ(model.count(), 3u);
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, Model_GetAnnotation_ById) {
|
||||
PMIModel model;
|
||||
PMIAnnotation ann;
|
||||
ann.content = "Find me";
|
||||
int id = model.attach_pmi(ann);
|
||||
|
||||
const auto* found = model.get_annotation(id);
|
||||
ASSERT_NE(found, nullptr);
|
||||
EXPECT_EQ(found->content, "Find me");
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, Model_GetAnnotation_NotFound) {
|
||||
PMIModel model;
|
||||
EXPECT_EQ(model.get_annotation(9999), nullptr);
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, Model_RemovePmi_RemovesFromViews) {
|
||||
PMIModel model;
|
||||
int id = model.attach_pmi(PMIAnnotation{});
|
||||
|
||||
EXPECT_TRUE(model.remove_pmi(id));
|
||||
EXPECT_EQ(model.count(), 0u);
|
||||
EXPECT_EQ(model.get_annotation(id), nullptr);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// PMIModel — face / edge queries
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(PMIMBDTest, Model_AnnotationsForFace_FindsMatches) {
|
||||
PMIModel model;
|
||||
PMIAnnotation ann;
|
||||
ann.associated_faces = {3, 5};
|
||||
model.attach_pmi(ann);
|
||||
|
||||
auto found = model.annotations_for_face(3);
|
||||
EXPECT_EQ(found.size(), 1u);
|
||||
|
||||
auto none = model.annotations_for_face(999);
|
||||
EXPECT_TRUE(none.empty());
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, Model_AnnotationsOfType_Filters) {
|
||||
PMIModel model;
|
||||
PMIAnnotation dim;
|
||||
dim.type = PMIType::Dimension;
|
||||
model.attach_pmi(dim);
|
||||
|
||||
PMIAnnotation note;
|
||||
note.type = PMIType::Note;
|
||||
model.attach_pmi(note);
|
||||
|
||||
PMIAnnotation gdt;
|
||||
gdt.type = PMIType::GDTCalloutType;
|
||||
model.attach_pmi(gdt);
|
||||
|
||||
EXPECT_EQ(model.annotations_of_type(PMIType::Dimension).size(), 1u);
|
||||
EXPECT_EQ(model.annotations_of_type(PMIType::Note).size(), 1u);
|
||||
EXPECT_EQ(model.annotations_of_type(PMIType::GDTCalloutType).size(), 1u);
|
||||
EXPECT_EQ(model.annotations_of_type(PMIType::SurfaceFinish).size(), 0u);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// PMIView — visibility management
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(PMIMBDTest, View_AddAndCheckVisibility) {
|
||||
PMIView view("front");
|
||||
view.add_annotation(10);
|
||||
view.add_annotation(20);
|
||||
|
||||
EXPECT_TRUE(view.is_visible(10));
|
||||
EXPECT_TRUE(view.is_visible(20));
|
||||
EXPECT_FALSE(view.is_visible(30));
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, View_RemoveAnnotation) {
|
||||
PMIView view;
|
||||
view.add_annotation(10);
|
||||
view.add_annotation(20);
|
||||
view.remove_annotation(10);
|
||||
|
||||
EXPECT_FALSE(view.is_visible(10));
|
||||
EXPECT_TRUE(view.is_visible(20));
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, View_SetAllVisible) {
|
||||
PMIView view;
|
||||
view.set_all_visible({1, 2, 3, 4, 5});
|
||||
|
||||
EXPECT_TRUE(view.is_visible(1));
|
||||
EXPECT_TRUE(view.is_visible(5));
|
||||
EXPECT_FALSE(view.is_visible(10));
|
||||
EXPECT_EQ(view.visible_annotations().size(), 5u);
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, View_ClearAll) {
|
||||
PMIView view;
|
||||
view.set_all_visible({1, 2, 3});
|
||||
view.clear_all();
|
||||
EXPECT_TRUE(view.visible_annotations().empty());
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, View_HideAnnotations) {
|
||||
PMIView view;
|
||||
view.set_all_visible({1, 2, 3, 4});
|
||||
view.hide_annotations({2, 4});
|
||||
|
||||
EXPECT_TRUE(view.is_visible(1));
|
||||
EXPECT_FALSE(view.is_visible(2));
|
||||
EXPECT_TRUE(view.is_visible(3));
|
||||
EXPECT_FALSE(view.is_visible(4));
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// PMIModel — views
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(PMIMBDTest, Model_CreateDefaultViews) {
|
||||
PMIModel model;
|
||||
model.attach_pmi(PMIAnnotation{});
|
||||
model.create_default_views();
|
||||
|
||||
EXPECT_NE(model.get_view("front"), nullptr);
|
||||
EXPECT_NE(model.get_view("top"), nullptr);
|
||||
EXPECT_NE(model.get_view("right"), nullptr);
|
||||
EXPECT_NE(model.get_view("iso"), nullptr);
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, Model_RemoveView) {
|
||||
PMIModel model;
|
||||
model.set_view("test", PMIView("test"));
|
||||
EXPECT_NE(model.get_view("test"), nullptr);
|
||||
|
||||
model.remove_view("test");
|
||||
EXPECT_EQ(model.get_view("test"), nullptr);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// export_pmi_step_ap242
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(PMIMBDTest, Export_StepAP242_ContainsEntities) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
PMIModel pmi;
|
||||
pmi.attach_pmi(make_pmi_dimension(PMIType::Dimension, 10.0, Point3D(0, 0, 0), {0}));
|
||||
pmi.create_default_views();
|
||||
|
||||
auto step = export_pmi_step_ap242(box, pmi);
|
||||
EXPECT_FALSE(step.empty());
|
||||
EXPECT_NE(step.find("ISO-10303-21"), std::string::npos);
|
||||
EXPECT_NE(step.find("ANNOTATION_OCCURRENCE"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, Export_StepAP242_EmptyPmi_StillValid) {
|
||||
auto box = make_box(5, 5, 5);
|
||||
PMIModel pmi;
|
||||
|
||||
auto step = export_pmi_step_ap242(box, pmi);
|
||||
EXPECT_NE(step.find("ISO-10303-21"), std::string::npos);
|
||||
EXPECT_NE(step.find("ENDSEC"), std::string::npos);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Factory functions
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(PMIMBDTest, MakePmiDimension_ReturnsConfigured) {
|
||||
auto ann = make_pmi_dimension(PMIType::Dimension, 25.4,
|
||||
Point3D(1, 2, 3), {0, 1});
|
||||
|
||||
EXPECT_EQ(ann.type, PMIType::Dimension);
|
||||
EXPECT_EQ(ann.nominal_value, 25.4);
|
||||
EXPECT_EQ(ann.associated_faces.size(), 2u);
|
||||
EXPECT_FALSE(ann.content.empty());
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, MakePmiGdt_ReturnsConfigured) {
|
||||
auto callout = make_gdt_callout(GDTType::Flatness, 0.05);
|
||||
auto ann = make_pmi_gdt(callout, Point3D(10, 20, 0));
|
||||
|
||||
EXPECT_EQ(ann.type, PMIType::GDTCalloutType);
|
||||
EXPECT_EQ(ann.gdt_type, GDTType::Flatness);
|
||||
EXPECT_FALSE(ann.content.empty());
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, MakePmiSurfaceFinish_ReturnsConfigured) {
|
||||
auto ann = make_pmi_surface_finish(3.2, 0, Point3D(0, 0, 0));
|
||||
|
||||
EXPECT_EQ(ann.type, PMIType::SurfaceFinish);
|
||||
EXPECT_NE(ann.content.find("Ra"), std::string::npos);
|
||||
EXPECT_EQ(ann.associated_faces.size(), 1u);
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, MakePmiDatumTarget_ReturnsConfigured) {
|
||||
auto ann = make_pmi_datum_target("A", 0, Point3D(5, 5, 0));
|
||||
|
||||
EXPECT_EQ(ann.type, PMIType::DatumTarget);
|
||||
EXPECT_EQ(ann.content, "A");
|
||||
EXPECT_EQ(ann.associated_faces.size(), 1u);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// auto_generate_pmi
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(PMIMBDTest, AutoGeneratePmi_Box_ProducesAnnotations) {
|
||||
auto box = make_box(10, 5, 3);
|
||||
PMIModel pmi;
|
||||
|
||||
int count = auto_generate_pmi(box, pmi, DrawingStandard::ISO);
|
||||
EXPECT_GT(count, 0);
|
||||
EXPECT_EQ(pmi.count(), static_cast<size_t>(count));
|
||||
|
||||
// Should have created default views
|
||||
EXPECT_NE(pmi.get_view("front"), nullptr);
|
||||
}
|
||||
|
||||
TEST(PMIMBDTest, AutoGeneratePmi_EmptyModel_ReturnsZero) {
|
||||
BrepModel empty;
|
||||
PMIModel pmi;
|
||||
int count = auto_generate_pmi(empty, pmi);
|
||||
EXPECT_EQ(count, 0);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// PMIDisplayProps
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(PMIMBDTest, DisplayProps_Defaults) {
|
||||
PMIDisplayProps props;
|
||||
EXPECT_TRUE(props.visible);
|
||||
EXPECT_EQ(props.color, "#000000");
|
||||
EXPECT_GT(props.text_height, 0.0);
|
||||
EXPECT_TRUE(props.show_leader);
|
||||
EXPECT_FALSE(props.show_frame);
|
||||
}
|
||||
Reference in New Issue
Block a user