Files
ViewDesignEngine/tests/brep/test_step_import.cpp
T
茂之钳 fcf25e561d feat(v4.4): complete remaining v4.1-v4.4 features + precision tolerance + Euler ops
v4.1 收尾:
- IncrementalUpdateEngine: dirty flag propagation, cache invalidation
- LargeAssembly: InstanceCache, assembly instancing
- STEP import: robust/graceful parsing with skip tracking

v4.3 分析工具:
- Mass properties (volume, centroid, inertia tensor)
- Clearance analysis, wall thickness analysis
- Enhanced drawing: hidden-line removal, offset sections, BOM
- DXF import (LINE/CIRCLE/ARC/LWPOLYLINE/SPLINE → B-Rep extrusion)

v4.4 地基加固:
- ToleranceChain: RSS cumulative tolerance propagation (7 tests)
- Euler operations: MEV/KEV/MEF/KEF/KEMR/MEKR (20 tests)
- Replace hardcoded tolerances with ToleranceConfig in validate
- Fix incremental_update test API mismatch (15/15 pass on Linux)

Docs:
- v4.1-v4.4 development plans + roadmap updated
- v4.4 marked complete on Linux

30 files, +3424/-210
2026-07-26 16:49:37 +08:00

683 lines
25 KiB
C++
Raw 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/step_import.h"
#include "vde/brep/brep.h"
#include "vde/brep/modeling.h"
#include <cmath>
#include <sstream>
using namespace vde::brep;
using namespace vde::core;
// ─────────────────────────────────────────────────────────────
// Helper: build a minimal STEP file as a string
// ─────────────────────────────────────────────────────────────
static std::string step_header() {
return R"(ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDesignEngine test'),'2;1');
FILE_NAME('test.stp','2025-01-01T00:00:00',('Author'),(''),'','','');
FILE_SCHEMA(('CONFIG_CONTROL_DESIGN','GEOMETRIC_MODEL_SCHEMA'));
ENDSEC;
DATA;
)";
}
static std::string step_footer() {
return R"(ENDSEC;
END-ISO-10303-21;
)";
}
// ─────────────────────────────────────────────────────────────
// Test: Parse a minimal box STEP file
// ─────────────────────────────────────────────────────────────
TEST(StepImport, MinimalBox) {
// A simple box as MANIFOLD_SOLID_BREP with 6 planar faces
// Points are the 8 corners of a 2×2×2 box centered at origin
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(-1.0,-1.0,-1.0));
#2=CARTESIAN_POINT('',(1.0,-1.0,-1.0));
#3=CARTESIAN_POINT('',(1.0,1.0,-1.0));
#4=CARTESIAN_POINT('',(-1.0,1.0,-1.0));
#5=CARTESIAN_POINT('',(-1.0,-1.0,1.0));
#6=CARTESIAN_POINT('',(1.0,-1.0,1.0));
#7=CARTESIAN_POINT('',(1.0,1.0,1.0));
#8=CARTESIAN_POINT('',(-1.0,1.0,1.0));
#10=DIRECTION('',(0.0,0.0,1.0));
#11=DIRECTION('',(1.0,0.0,0.0));
#12=DIRECTION('',(0.0,1.0,0.0));
#13=DIRECTION('',(0.0,0.0,-1.0));
#14=DIRECTION('',(-1.0,0.0,0.0));
#15=DIRECTION('',(0.0,-1.0,0.0));
#16=DIRECTION('',(1.0,1.0,0.0));
#17=DIRECTION('',(-1.0,1.0,0.0));
#18=DIRECTION('',(-1.0,-1.0,0.0));
#19=DIRECTION('',(1.0,-1.0,0.0));
#20=AXIS2_PLACEMENT_3D('',#1,#10,#11);
#21=AXIS2_PLACEMENT_3D('',#5,#10,#11);
#22=AXIS2_PLACEMENT_3D('',#1,#12,#11);
#23=AXIS2_PLACEMENT_3D('',#4,#12,#11);
#24=AXIS2_PLACEMENT_3D('',#2,#14,#12);
#25=AXIS2_PLACEMENT_3D('',#1,#14,#12);
#30=PLANE('',#20);
#31=PLANE('',#21);
#32=PLANE('',#22);
#33=PLANE('',#23);
#34=PLANE('',#24);
#35=PLANE('',#25);
#40=LINE('',#1,#16);
#41=LINE('',#2,#17);
#42=LINE('',#3,#12);
#43=LINE('',#4,#18);
#44=LINE('',#5,#19);
#45=LINE('',#6,#17);
#46=LINE('',#7,#11);
#47=LINE('',#8,#18);
#48=LINE('',#1,#10);
#49=LINE('',#2,#10);
#50=LINE('',#3,#10);
#51=LINE('',#4,#10);
#52=LINE('',#2,#11);
#53=LINE('',#3,#12);
#54=LINE('',#7,#12);
#55=LINE('',#6,#15);
#56=LINE('',#1,#15);
#57=LINE('',#4,#14);
#58=LINE('',#8,#14);
#59=LINE('',#5,#15);
#60=LINE('',#5,#14);
#70=EDGE_CURVE('',#80,#81,#40,.T.);
#71=EDGE_CURVE('',#81,#82,#41,.T.);
#72=EDGE_CURVE('',#82,#83,#42,.T.);
#73=EDGE_CURVE('',#83,#80,#43,.T.);
#74=EDGE_CURVE('',#84,#85,#44,.T.);
#75=EDGE_CURVE('',#85,#86,#45,.T.);
#76=EDGE_CURVE('',#86,#87,#46,.T.);
#77=EDGE_CURVE('',#87,#84,#47,.T.);
#78=EDGE_CURVE('',#80,#84,#48,.T.);
#79=EDGE_CURVE('',#81,#85,#49,.T.);
#80=VERTEX_POINT('',#1);
#81=VERTEX_POINT('',#2);
#82=VERTEX_POINT('',#3);
#83=VERTEX_POINT('',#4);
#84=VERTEX_POINT('',#5);
#85=VERTEX_POINT('',#6);
#86=VERTEX_POINT('',#7);
#87=VERTEX_POINT('',#8);
#90=EDGE_CURVE('',#82,#86,#50,.T.);
#91=EDGE_CURVE('',#85,#81,#52,.T.);
#92=EDGE_CURVE('',#83,#87,#53,.T.);
#93=EDGE_CURVE('',#86,#87,#54,.T.);
#94=EDGE_CURVE('',#81,#80,#55,.T.);
#95=EDGE_CURVE('',#84,#80,#56,.T.);
#96=EDGE_CURVE('',#82,#83,#57,.T.);
#97=EDGE_CURVE('',#87,#84,#58,.T.);
#98=EDGE_CURVE('',#80,#83,#59,.T.);
#99=EDGE_CURVE('',#84,#85,#60,.T.);
#100=ORIENTED_EDGE('',*,*,#70,.T.);
#101=ORIENTED_EDGE('',*,*,#71,.T.);
#102=ORIENTED_EDGE('',*,*,#72,.T.);
#103=ORIENTED_EDGE('',*,*,#73,.T.);
#104=ORIENTED_EDGE('',*,*,#74,.F.);
#105=ORIENTED_EDGE('',*,*,#75,.F.);
#106=ORIENTED_EDGE('',*,*,#76,.F.);
#107=ORIENTED_EDGE('',*,*,#77,.F.);
#108=ORIENTED_EDGE('',*,*,#78,.T.);
#109=ORIENTED_EDGE('',*,*,#94,.T.);
#110=ORIENTED_EDGE('',*,*,#95,.F.);
#111=ORIENTED_EDGE('',*,*,#98,.T.);
#112=ORIENTED_EDGE('',*,*,#56,.T.);
#113=ORIENTED_EDGE('',*,*,#79,.T.);
#114=ORIENTED_EDGE('',*,*,#91,.T.);
#115=ORIENTED_EDGE('',*,*,#90,.F.);
#116=ORIENTED_EDGE('',*,*,#96,.F.);
#117=ORIENTED_EDGE('',*,*,#92,.T.);
#118=ORIENTED_EDGE('',*,*,#97,.F.);
#119=ORIENTED_EDGE('',*,*,#54,.T.);
#120=ORIENTED_EDGE('',*,*,#93,.F.);
#121=ORIENTED_EDGE('',*,*,#46,.T.);
#122=ORIENTED_EDGE('',*,*,#60,.T.);
#123=ORIENTED_EDGE('',*,*,#99,.F.);
#130=EDGE_LOOP('',(#100,#101,#102,#103));
#131=EDGE_LOOP('',(#104,#105,#106,#107));
#132=EDGE_LOOP('',(#108,#109,#110,#112));
#133=EDGE_LOOP('',(#113,#114,#115,#116));
#134=EDGE_LOOP('',(#117,#118,#119,#120));
#135=EDGE_LOOP('',(#121,#122,#123,#111));
#140=FACE_OUTER_BOUND('',#130,.T.);
#141=FACE_OUTER_BOUND('',#131,.T.);
#142=FACE_OUTER_BOUND('',#132,.T.);
#143=FACE_OUTER_BOUND('',#133,.T.);
#144=FACE_OUTER_BOUND('',#134,.T.);
#145=FACE_OUTER_BOUND('',#135,.T.);
#150=ADVANCED_FACE('',(#140),#30,.T.);
#151=ADVANCED_FACE('',(#141),#31,.T.);
#152=ADVANCED_FACE('',(#142),#32,.T.);
#153=ADVANCED_FACE('',(#143),#33,.T.);
#154=ADVANCED_FACE('',(#144),#34,.T.);
#155=ADVANCED_FACE('',(#145),#35,.T.);
#160=CLOSED_SHELL('',(#150,#151,#152,#153,#154,#155));
#170=MANIFOLD_SOLID_BREP('box',#160);
)" + step_footer();
auto bodies = import_step_from_string(step);
EXPECT_EQ(step_last_error(), StepError::Ok);
ASSERT_GE(bodies.size(), 1u);
const auto& body = bodies[0];
EXPECT_TRUE(body.is_valid());
EXPECT_GE(body.num_faces(), 6u);
EXPECT_GE(body.num_edges(), 12u);
EXPECT_GE(body.num_vertices(), 8u);
EXPECT_EQ(body.num_bodies(), 1u);
}
// ─────────────────────────────────────────────────────────────
// Test: Simple box with CIRCLE edge (cylinder top/bottom face)
// ─────────────────────────────────────────────────────────────
TEST(StepImport, SimpleCylinder) {
// A simple cylinder: bottom circle + top circle + cylindrical side surface
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,-2.0));
#2=CARTESIAN_POINT('',(0.0,0.0,2.0));
#3=CARTESIAN_POINT('',(1.0,0.0,-2.0));
#4=CARTESIAN_POINT('',(1.0,0.0,2.0));
#10=DIRECTION('',(0.0,0.0,1.0));
#11=DIRECTION('',(1.0,0.0,0.0));
#12=DIRECTION('',(0.0,1.0,0.0));
#20=AXIS2_PLACEMENT_3D('',#1,#10,#11);
#21=AXIS2_PLACEMENT_3D('',#2,#10,#11);
#30=PLANE('',#20);
#31=PLANE('',#21);
#40=CIRCLE('',#20,1.0);
#41=CIRCLE('',#21,1.0);
#50=EDGE_CURVE('',#80,#80,#40,.T.);
#51=EDGE_CURVE('',#81,#81,#41,.T.);
#60=LINE('',#3,#10);
#70=EDGE_CURVE('',#80,#81,#60,.T.);
#80=VERTEX_POINT('',#3);
#81=VERTEX_POINT('',#4);
#90=ORIENTED_EDGE('',*,*,#50,.T.);
#91=ORIENTED_EDGE('',*,*,#51,.F.);
#92=ORIENTED_EDGE('',*,*,#70,.T.);
#100=EDGE_LOOP('',(#90));
#101=EDGE_LOOP('',(#91));
#110=FACE_OUTER_BOUND('',#100,.T.);
#111=FACE_OUTER_BOUND('',#101,.T.);
#120=ADVANCED_FACE('',(#110),#30,.T.);
#121=ADVANCED_FACE('',(#111),#31,.T.);
#142=CYLINDRICAL_SURFACE('',#20,1.0);
#143=ADVANCED_FACE('',(#110),#142,.T.);
#150=CLOSED_SHELL('',(#120,#121,#143));
#160=MANIFOLD_SOLID_BREP('cylinder',#150);
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
const auto& body = bodies[0];
EXPECT_TRUE(body.is_valid());
EXPECT_GE(body.num_faces(), 3u);
}
// ─────────────────────────────────────────────────────────────
// Test: Parse STEP with multiple bodies
// ─────────────────────────────────────────────────────────────
TEST(StepImport, MultipleBodies) {
// Two simple models: one circle-face body at origin, one at offset
std::string step = step_header() + R"(
#10=CARTESIAN_POINT('',(0.0,0.0,0.0));
#11=DIRECTION('',(0.0,0.0,1.0));
#12=DIRECTION('',(1.0,0.0,0.0));
#13=AXIS2_PLACEMENT_3D('',#10,#11,#12);
#14=CARTESIAN_POINT('',(5.0,0.0,0.0));
#15=VERTEX_POINT('',#14);
#16=CIRCLE('',#13,5.0);
#17=EDGE_CURVE('',#15,#15,#16,.T.);
#18=ORIENTED_EDGE('',*,*,#17,.T.);
#19=EDGE_LOOP('',(#18));
#20=FACE_OUTER_BOUND('',#19,.T.);
#21=PLANE('',#13);
#22=ADVANCED_FACE('',(#20),#21,.T.);
#23=CLOSED_SHELL('',(#22));
#24=MANIFOLD_SOLID_BREP('body1',#23);
#30=CARTESIAN_POINT('',(20.0,0.0,0.0));
#31=AXIS2_PLACEMENT_3D('',#30,#11,#12);
#32=CARTESIAN_POINT('',(23.0,0.0,0.0));
#33=VERTEX_POINT('',#32);
#34=CIRCLE('',#31,3.0);
#35=EDGE_CURVE('',#33,#33,#34,.T.);
#36=ORIENTED_EDGE('',*,*,#35,.T.);
#37=EDGE_LOOP('',(#36));
#38=FACE_OUTER_BOUND('',#37,.T.);
#39=PLANE('',#31);
#40=ADVANCED_FACE('',(#38),#39,.T.);
#41=CLOSED_SHELL('',(#40));
#42=MANIFOLD_SOLID_BREP('body2',#41);
)" + step_footer();
auto bodies = import_step_from_string(step);
EXPECT_EQ(step_last_error(), StepError::Ok);
ASSERT_GE(bodies.size(), 2u);
EXPECT_TRUE(bodies[0].is_valid());
EXPECT_TRUE(bodies[1].is_valid());
EXPECT_GE(bodies[0].num_faces(), 1u);
EXPECT_GE(bodies[1].num_faces(), 1u);
}
// ─────────────────────────────────────────────────────────────
// Test: Invalid input → graceful error
// ─────────────────────────────────────────────────────────────
TEST(StepImport, EmptyInput) {
auto bodies = import_step_from_string("");
EXPECT_NE(step_last_error(), StepError::Ok);
EXPECT_TRUE(bodies.empty());
}
TEST(StepImport, GarbageInput) {
auto bodies = import_step_from_string("this is not a step file at all");
EXPECT_NE(step_last_error(), StepError::Ok);
EXPECT_TRUE(bodies.empty());
}
TEST(StepImport, MissingEndsec) {
std::string step = step_header() + "#1=CARTESIAN_POINT('',(0,0,0));";
// Missing ENDSEC and footer
auto bodies = import_step_from_string(step);
// May or may not produce bodies depending on where it fails
// But should not crash
EXPECT_NO_THROW(import_step_from_string(step));
}
TEST(StepImport, OnlyHeader) {
std::string step = step_header() + step_footer();
auto bodies = import_step_from_string(step);
// Empty DATA section → no bodies
EXPECT_EQ(bodies.size(), 0u);
}
TEST(StepImport, MissingEntityId) {
std::string step = step_header() + "CARTESIAN_POINT('',(0,0,0));\n" + step_footer();
auto bodies = import_step_from_string(step);
EXPECT_NE(step_last_error(), StepError::Ok);
}
// ─────────────────────────────────────────────────────────────
// Test: Circle and ellipse curves
// ─────────────────────────────────────────────────────────────
TEST(StepImport, CircleCurve) {
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=DIRECTION('',(0.0,0.0,1.0));
#3=DIRECTION('',(1.0,0.0,0.0));
#4=AXIS2_PLACEMENT_3D('',#1,#2,#3);
#5=CARTESIAN_POINT('',(5.0,0.0,0.0));
#6=VERTEX_POINT('',#5);
#7=CIRCLE('',#4,5.0);
#8=EDGE_CURVE('',#6,#6,#7,.T.);
#9=ORIENTED_EDGE('',*,*,#8,.T.);
#10=EDGE_LOOP('',(#9));
#11=FACE_OUTER_BOUND('',#10,.T.);
#12=PLANE('',#4);
#13=ADVANCED_FACE('',(#11),#12,.T.);
#14=CLOSED_SHELL('',(#13));
#15=MANIFOLD_SOLID_BREP('circle_test',#14);
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
EXPECT_GE(bodies[0].num_vertices(), 1u);
}
TEST(StepImport, EllipseCurve) {
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=DIRECTION('',(0.0,0.0,1.0));
#3=DIRECTION('',(1.0,0.0,0.0));
#4=AXIS2_PLACEMENT_3D('',#1,#2,#3);
#5=CARTESIAN_POINT('',(3.0,0.0,0.0));
#6=VERTEX_POINT('',#5);
#7=ELLIPSE('',#4,3.0,1.0);
#8=EDGE_CURVE('',#6,#6,#7,.T.);
#9=ORIENTED_EDGE('',*,*,#8,.T.);
#10=EDGE_LOOP('',(#9));
#11=FACE_OUTER_BOUND('',#10,.T.);
#12=PLANE('',#4);
#13=ADVANCED_FACE('',(#11),#12,.T.);
#14=CLOSED_SHELL('',(#13));
#15=MANIFOLD_SOLID_BREP('ellipse_test',#14);
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
}
// ─────────────────────────────────────────────────────────────
// Test: STEP surfaces
// ─────────────────────────────────────────────────────────────
TEST(StepImport, SphericalSurface) {
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=CARTESIAN_POINT('',(1.0,0.0,0.0));
#3=DIRECTION('',(0.0,0.0,1.0));
#4=DIRECTION('',(1.0,0.0,0.0));
#5=AXIS2_PLACEMENT_3D('',#1,#3,#4);
#6=VERTEX_POINT('',#2);
#7=SPHERICAL_SURFACE('',#5,5.0);
#8=CIRCLE('',#5,5.0);
#9=EDGE_CURVE('',#6,#6,#8,.T.);
#10=ORIENTED_EDGE('',*,*,#9,.T.);
#11=EDGE_LOOP('',(#10));
#12=FACE_OUTER_BOUND('',#11,.T.);
#13=ADVANCED_FACE('',(#12),#7,.T.);
#14=CLOSED_SHELL('',(#13));
#15=MANIFOLD_SOLID_BREP('sphere',#14);
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
EXPECT_GE(bodies[0].num_faces(), 1u);
}
TEST(StepImport, ToroidalSurface) {
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=DIRECTION('',(0.0,0.0,1.0));
#3=DIRECTION('',(1.0,0.0,0.0));
#4=AXIS2_PLACEMENT_3D('',#1,#2,#3);
#5=CARTESIAN_POINT('',(8.0,0.0,0.0));
#6=VERTEX_POINT('',#5);
#7=TOROIDAL_SURFACE('',#4,5.0,2.0);
#8=CIRCLE('',#4,5.0);
#9=EDGE_CURVE('',#6,#6,#8,.T.);
#10=ORIENTED_EDGE('',*,*,#9,.T.);
#11=EDGE_LOOP('',(#10));
#12=FACE_OUTER_BOUND('',#11,.T.);
#13=ADVANCED_FACE('',(#12),#7,.T.);
#14=CLOSED_SHELL('',(#13));
#15=MANIFOLD_SOLID_BREP('torus',#14);
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
}
// ─────────────────────────────────────────────────────────────
// Test: B_SPLINE_CURVE and B_SPLINE_SURFACE
// ─────────────────────────────────────────────────────────────
TEST(StepImport, BSplineCurve) {
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=CARTESIAN_POINT('',(1.0,2.0,0.0));
#3=CARTESIAN_POINT('',(3.0,1.0,0.0));
#4=CARTESIAN_POINT('',(4.0,0.0,0.0));
#10=B_SPLINE_CURVE_WITH_KNOTS('',3,(#1,#2,#3,#4),.UNSPECIFIED.,.F.,.F.,(4,1,1,4),(0.0,0.25,0.75,1.0),.PIECEWISE_BEZIER_KNOTS.);
#11=CARTESIAN_POINT('',(0.0,0.0,0.0));
#12=CARTESIAN_POINT('',(4.0,0.0,0.0));
#20=VERTEX_POINT('',#11);
#21=VERTEX_POINT('',#12);
#30=EDGE_CURVE('',#20,#21,#10,.T.);
#40=LINE('',#11,#12);
#41=EDGE_CURVE('',#21,#20,#40,.T.);
#50=ORIENTED_EDGE('',*,*,#30,.T.);
#51=ORIENTED_EDGE('',*,*,#41,.T.);
#60=EDGE_LOOP('',(#50,#51));
#61=FACE_OUTER_BOUND('',#60,.T.);
#70=DIRECTION('',(0.0,0.0,1.0));
#71=DIRECTION('',(1.0,0.0,0.0));
#72=AXIS2_PLACEMENT_3D('',#11,#70,#71);
#73=PLANE('',#72);
#80=ADVANCED_FACE('',(#61),#73,.T.);
#90=CLOSED_SHELL('',(#80));
#100=MANIFOLD_SOLID_BREP('bspline_curve',#90);
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
}
// ─────────────────────────────────────────────────────────────
// Test: Round-trip (create → export is not available, but we
// can test creating a BrepModel programmatically and compare)
// ─────────────────────────────────────────────────────────────
TEST(StepImport, ProgrammaticBrepCanBeUsed) {
auto box = make_box(2.0, 2.0, 2.0);
EXPECT_TRUE(box.is_valid());
// Verify the box can be tessellated
auto mesh = box.to_mesh();
EXPECT_GT(mesh.num_faces(), 0u);
// Verify face and edge queries work after import
auto edges = box.face_edges(0);
EXPECT_FALSE(edges.empty());
auto efs = box.edge_faces(0);
EXPECT_FALSE(efs.empty());
}
// ─────────────────────────────────────────────────────────────
// Test: String escaping in STEP
// ─────────────────────────────────────────────────────────────
TEST(StepImport, StringEscaping) {
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('name with spaces',(1.0,2.0,3.0));
#2=CARTESIAN_POINT('it''s escaped',(4.0,5.0,6.0));
)" + step_footer();
auto bodies = import_step_from_string(step);
// No bodies (no topology) but should not crash
EXPECT_EQ(bodies.size(), 0u);
}
// ─────────────────────────────────────────────────────────────
// Test: Entities with omitted parameters ($)
// ─────────────────────────────────────────────────────────────
TEST(StepImport, OmittedParameters) {
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=DIRECTION('',(0.0,0.0,1.0));
#3=AXIS2_PLACEMENT_3D('',#1,#2,$);
#4=CARTESIAN_POINT('',(5.0,0.0,0.0));
#5=VERTEX_POINT('',#4);
#6=CIRCLE('',#3,5.0);
#7=EDGE_CURVE('',#5,#5,#6,.T.);
#8=ORIENTED_EDGE('',*,*,#7,.T.);
#9=EDGE_LOOP('',(#8));
#10=FACE_OUTER_BOUND('',#9,.T.);
#11=PLANE('',#3);
#12=ADVANCED_FACE('',(#10),#11,.T.);
#13=CLOSED_SHELL('',(#12));
#14=MANIFOLD_SOLID_BREP('omitted_test',#13);
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
}
// ─────────────────────────────────────────────────────────────
// Test: STEP file with comments
// ─────────────────────────────────────────────────────────────
TEST(StepImport, Comments) {
std::string step = step_header() + R"(
/* This is a comment block */
#1=CARTESIAN_POINT('',(0.0,0.0,0.0)); /* inline comment */
#2=DIRECTION('',(0.0,0.0,1.0));
#3=AXIS2_PLACEMENT_3D('',#1,#2,$);
#4=CARTESIAN_POINT('',(5.0,0.0,0.0));
#5=VERTEX_POINT('',#4);
#6=CIRCLE('',#3,5.0);
#7=EDGE_CURVE('',#5,#5,#6,.T.);
#8=ORIENTED_EDGE('',*,*,#7,.T.);
#9=EDGE_LOOP('',(#8));
#10=FACE_OUTER_BOUND('',#9,.T.);
#11=PLANE('',#3);
#12=ADVANCED_FACE('',(#10),#11,.T.);
#13=CLOSED_SHELL('',(#12));
#14=MANIFOLD_SOLID_BREP('commented',#13);
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
}
// ─────────────────────────────────────────────────────────────
// Test: Multi-line entities
// ─────────────────────────────────────────────────────────────
TEST(StepImport, MultiLineEntities) {
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',
(0.0,0.0,0.0));
#2=CARTESIAN_POINT('',(
5.0,0.0,0.0));
#3=DIRECTION('',
(0.0,0.0,1.0));
#4=AXIS2_PLACEMENT_3D('point',
#1,#3,$);
#5=VERTEX_POINT('',#2);
#6=CIRCLE('round',#4,5.0);
#7=EDGE_CURVE('edge',
#5,#5,#6,.T.);
#8=ORIENTED_EDGE('',*,*,#7,.T.);
#9=EDGE_LOOP('',(#8));
#10=FACE_OUTER_BOUND('',#9,.T.);
#11=PLANE('flat',#4);
#12=ADVANCED_FACE('face',(#10),#11,.T.);
#13=CLOSED_SHELL('shell',(#12));
#14=MANIFOLD_SOLID_BREP('solid',#13);
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
}
// ─────────────────────────────────────────────────────────────
// Test: SHELL_BASED_SURFACE_MODEL (alternative to MANIFOLD_SOLID_BREP)
// ─────────────────────────────────────────────────────────────
TEST(StepImport, ShellBasedSurfaceModel) {
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=DIRECTION('',(0.0,0.0,1.0));
#3=AXIS2_PLACEMENT_3D('',#1,#2,$);
#4=CARTESIAN_POINT('',(3.0,0.0,0.0));
#5=VERTEX_POINT('',#4);
#6=CIRCLE('',#3,3.0);
#7=EDGE_CURVE('',#5,#5,#6,.T.);
#8=ORIENTED_EDGE('',*,*,#7,.T.);
#9=EDGE_LOOP('',(#8));
#10=FACE_OUTER_BOUND('',#9,.T.);
#11=PLANE('',#3);
#12=ADVANCED_FACE('',(#10),#11,.T.);
#13=CLOSED_SHELL('',(#12));
#14=SHELL_BASED_SURFACE_MODEL('',(#13));
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
EXPECT_GE(bodies[0].num_faces(), 1u);
}
// ─────────────────────────────────────────────────────────────
// Fault tolerance tests
// ─────────────────────────────────────────────────────────────
TEST(StepImport, UnknownEntityTypeIsSkipped) {
// UNKNOWN_CURVE_TYPE is not recognized → should be skipped, not crash
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=DIRECTION('',(0.0,0.0,1.0));
#3=AXIS2_PLACEMENT_3D('',#1,#2,$);
#4=CARTESIAN_POINT('',(5.0,0.0,0.0));
#5=VERTEX_POINT('',#4);
#6=CIRCLE('',#3,5.0);
#7=EDGE_CURVE('',#5,#5,#6,.T.);
#8=ORIENTED_EDGE('',*,*,#7,.T.);
#9=EDGE_LOOP('',(#8));
#10=FACE_OUTER_BOUND('',#9,.T.);
#11=PLANE('',#3);
#12=ADVANCED_FACE('',(#10),#11,.T.);
#13=CLOSED_SHELL('',(#12));
#14=MANIFOLD_SOLID_BREP('good_solid',#13);
#15=UNKNOWN_CURVE_TYPE('weird',(1.0,2.0,3.0));
)" + step_footer();
auto bodies = import_step_from_string(step);
// Should still import the valid solid
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
}
TEST(StepImport, MissingEntityReferenceIsTolerated) {
// #999 doesn't exist → converter should handle gracefully
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=DIRECTION('',(0.0,0.0,1.0));
#3=AXIS2_PLACEMENT_3D('',#1,#2,$);
#4=CARTESIAN_POINT('',(5.0,0.0,0.0));
#5=VERTEX_POINT('',#4);
#6=CIRCLE('',#3,5.0);
#7=EDGE_CURVE('',#5,#5,#6,.T.);
#8=ORIENTED_EDGE('',*,*,#7,.T.);
#9=EDGE_LOOP('',(#8));
#10=FACE_OUTER_BOUND('',#9,.T.);
#11=PLANE('',#3);
#12=ADVANCED_FACE('',(#10),#11,.T.);
#13=CLOSED_SHELL('',(#12));
#14=MANIFOLD_SOLID_BREP('valid_one',#13);
#15=MANIFOLD_SOLID_BREP('broken_one',#999);
)" + step_footer();
auto bodies = import_step_from_string(step);
// The valid solid should still be imported
ASSERT_GE(bodies.size(), 1u);
}
TEST(StepImport, MixedValidAndInvalidEntities) {
// Mix valid and invalid entities → valid parts are imported
std::string step = step_header() + R"(
#1=CARTESIAN_POINT('',(0.0,0.0,0.0));
#2=DIRECTION('',(0.0,0.0,1.0));
#3=AXIS2_PLACEMENT_3D('',#1,#2,$);
#4=CARTESIAN_POINT('',(5.0,0.0,0.0));
#5=VERTEX_POINT('',#4);
#6=CIRCLE('',#3,5.0);
#7=EDGE_CURVE('',#5,#5,#6,.T.);
#8=ORIENTED_EDGE('',*,*,#7,.T.);
#9=EDGE_LOOP('',(#8));
#10=FACE_OUTER_BOUND('',#9,.T.);
#11=PLANE('',#3);
#12=ADVANCED_FACE('',(#10),#11,.T.);
#13=CLOSED_SHELL('',(#12));
#14=MANIFOLD_SOLID_BREP('good',#13);
#1000=WEIRD_CURVE_TYPE('odd',(1.0,2.0));
)" + step_footer();
auto bodies = import_step_from_string(step);
ASSERT_GE(bodies.size(), 1u);
EXPECT_TRUE(bodies[0].is_valid());
}