11b606bd39
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
315 lines
11 KiB
C++
315 lines
11 KiB
C++
#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);
|
|
}
|