diff --git a/include/vde/brep/step_import.h b/include/vde/brep/step_import.h index e2e828e..60fc300 100644 --- a/include/vde/brep/step_import.h +++ b/include/vde/brep/step_import.h @@ -145,12 +145,15 @@ struct StepImportReport { int skipped_unsupported = 0; ///< 跳过的非标实体数 int approximated_surfaces = 0; ///< 近似处理的曲面数 int recovered_faces = 0; ///< 从破损数据中恢复的面数 + bool ap203_specific = false; ///< 是否启用 AP203 兼容模式 + int skipped_solidworks_entities = 0; ///< 跳过的 SolidWorks 非几何实体数 std::vector warnings; ///< 警告消息列表 /// 是否有任何异常 [[nodiscard]] bool has_issues() const { return skipped_damaged > 0 || skipped_unsupported > 0 || approximated_surfaces > 0 || recovered_faces > 0 || + skipped_solidworks_entities > 0 || !warnings.empty(); } diff --git a/src/brep/step_import.cpp b/src/brep/step_import.cpp index f1c8e90..0f2c00e 100644 --- a/src/brep/step_import.cpp +++ b/src/brep/step_import.cpp @@ -35,6 +35,8 @@ struct StepImportDiagnostic { int approximated_surfaces = 0; // non-standard surfaces approximated int recovered_faces = 0; // faces recovered from damaged data int entities_with_warnings = 0; + bool is_ap203 = false; // AP203 compatibility mode enabled + int skipped_solidworks_entities = 0; // SolidWorks non-geometric entities skipped std::vector warnings; void add_warning(const std::string& w) { @@ -238,7 +240,7 @@ struct StepEntity { // ───────────────────────────────────────────────────────────── class StepParser { public: - explicit StepParser(std::string data) : tokenizer_(std::move(data)) {} + explicit StepParser(std::string data) : data_(std::move(data)), tokenizer_(data_) {} bool parse_data_section() { // Find DATA; section @@ -274,9 +276,40 @@ public: return true; } + /// Scan HEADER section for FILE_SCHEMA to detect AP203 + /// Must be called BEFORE parse_data_section() + bool detect_ap203_header() { + // We need to scan from the beginning, so create a fresh tokenizer + // or scan inline — we scan the raw data for FILE_SCHEMA pattern + const std::string& raw = data_; // We need access to the raw data + + // Find "FILE_SCHEMA" + size_t pos = raw.find("FILE_SCHEMA"); + if (pos == std::string::npos) return false; + + // Find the opening parenthesis + pos = raw.find('(', pos); + if (pos == std::string::npos || pos + 1 >= raw.size()) return false; + + // Collect everything until the matching closing parenthesis + int depth = 1; + size_t end = pos + 1; + while (end < raw.size() && depth > 0) { + if (raw[end] == '(') depth++; + else if (raw[end] == ')') depth--; + end++; + } + + std::string schema_str = raw.substr(pos, end - pos); + // Check for "203" in the schema identifier + // AP203 manifests as 'CONFIG_CONTROL_DESIGN' or contains '203' in the schema id + return (schema_str.find("203") != std::string::npos); + } + const std::unordered_map& entities() const { return entities_; } private: + std::string data_; // raw step data for header scanning (MUST be before tokenizer_) StepTokenizer tokenizer_; std::unordered_map entities_; @@ -463,20 +496,67 @@ public: resolve_all_directions(); resolve_all_placements(); + // Pre-scan: count SolidWorks non-geometric entities for diagnostics + for (const auto& [id, ent] : entities_) { + if (ent.type == "APPLICATION_CONTEXT" || + ent.type == "APPLICATION_PROTOCOL_DEFINITION" || + ent.type == "MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION" || + ent.type == "STYLED_ITEM" || + ent.type == "PRESENTATION_STYLE_ASSIGNMENT" || + ent.type == "SHAPE_REPRESENTATION" || + ent.type == "REPRESENTATION_CONTEXT" || + ent.type == "PRODUCT_DEFINITION_CONTEXT" || + ent.type == "PRODUCT_RELATED_PRODUCT_CATEGORY" || + ent.type == "PRODUCT_CONTEXT" || + ent.type == "DESIGN_CONTEXT" || + ent.type == "CURVE_STYLE" || + ent.type == "SURFACE_STYLE_USAGE" || + ent.type == "SURFACE_SIDE_STYLE" || + ent.type == "SURFACE_STYLE_FILL_AREA" || + ent.type == "FILL_AREA_STYLE_COLOUR" || + ent.type == "COLOUR_RGB" || + ent.type == "DRAUGHTING_PRE_DEFINED_COLOUR" || + ent.type == "PRESENTATION_LAYER_ASSIGNMENT" || + ent.type == "MECHANICAL_CONTEXT" || + ent.type == "APPROVAL_ROLE" || + ent.type == "APPROVAL_PERSON_ORGANIZATION" || + ent.type == "APPROVAL_DATE_TIME" || + ent.type == "APPROVAL" || + ent.type == "CC_DESIGN_APPROVAL" || + ent.type == "CC_DESIGN_DATE_AND_TIME_ASSIGNMENT" || + ent.type == "CC_DESIGN_PERSON_AND_ORGANIZATION_ASSIGNMENT" || + ent.type == "CC_DESIGN_SECURITY_CLASSIFICATION" || + ent.type == "SECURITY_CLASSIFICATION_LEVEL" || + ent.type == "PERSON_AND_ORGANIZATION" || + ent.type == "PERSON" || + ent.type == "ORGANIZATION" || + ent.type == "DATE_AND_TIME" || + ent.type == "CALENDAR_DATE" || + ent.type == "LOCAL_TIME" || + ent.type == "COORDINATED_UNIVERSAL_TIME_OFFSET" || + ent.type == "UNCERTAINTY_MEASURE_WITH_UNIT" || + ent.type == "PROPERTY_DEFINITION" || + ent.type == "SUPPLIED_BREP_REPRESENTATION") { + g_import_diag.skipped_solidworks_entities++; + } + } + // Find top-level products or directly find MANIFOLD_SOLID_BREP std::vector root_entities; - // Try finding PRODUCT entities first + // Try finding PRODUCT entities first (also handle SolidWorks variants) for (const auto& [id, ent] : entities_) { - if (ent.type == "PRODUCT" || ent.type == "PRODUCT_DEFINITION") { + if (ent.type == "PRODUCT" || ent.type == "PRODUCT_DEFINITION" || + ent.type == "PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE") { root_entities.push_back(id); } } - // If no PRODUCT, find SHAPE_DEFINITION_REPRESENTATION or directly MANIFOLD_SOLID_BREP + // If no PRODUCT, find SHAPE_DEFINITION_REPRESENTATION or SHAPE_REPRESENTATION_RELATIONSHIP if (root_entities.empty()) { for (const auto& [id, ent] : entities_) { - if (ent.type == "SHAPE_DEFINITION_REPRESENTATION") { + if (ent.type == "SHAPE_DEFINITION_REPRESENTATION" || + ent.type == "SHAPE_REPRESENTATION_RELATIONSHIP") { root_entities.push_back(id); } } @@ -490,6 +570,41 @@ public: } } + // AP203 mode: CLOSED_SHELL may directly be the top-level entity (no MANIFOLD_SOLID_BREP wrapper) + std::vector direct_shell_ids; + if (solid_ids.empty() || g_import_diag.is_ap203) { + for (const auto& [id, ent] : entities_) { + if (ent.type == "CLOSED_SHELL") { + // In AP203, CLOSED_SHELL directly contains faces — no MANIFOLD_SOLID_BREP wrapper + // Check that this CLOSED_SHELL is NOT referenced by any MANIFOLD_SOLID_BREP (to avoid duplicates) + bool is_referenced_by_solid = false; + for (const auto& [sid, sent] : entities_) { + if (sent.type == "MANIFOLD_SOLID_BREP" || sent.type == "SHELL_BASED_SURFACE_MODEL") { + for (const auto& arg : sent.args) { + if (arg.type == StepValue::Type::Ref && arg.ival == id) { + is_referenced_by_solid = true; + break; + } + if (arg.type == StepValue::Type::List) { + for (const auto& sv : arg.list) { + if (sv.type == StepValue::Type::Ref && sv.ival == id) { + is_referenced_by_solid = true; + break; + } + } + } + if (is_referenced_by_solid) break; + } + } + if (is_referenced_by_solid) break; + } + if (!is_referenced_by_solid) { + direct_shell_ids.push_back(id); + } + } + } + } + if (!solid_ids.empty()) { for (int sid : solid_ids) { BrepModel body = convert_solid(sid); @@ -499,6 +614,27 @@ public: result.push_back(std::move(body)); } } + } + + // Handle direct CLOSED_SHELL bodies (AP203 mode) + if (!direct_shell_ids.empty()) { + for (int sh_id : direct_shell_ids) { + BrepModel model; + auto face_ids = convert_shell(sh_id, model); + if (!face_ids.empty()) { + int shell_id = model.add_shell(face_ids, true); + std::string name = find_product_name(sh_id); + model.add_body({shell_id}, name); + if (model.num_faces() > 0) { + result.push_back(std::move(model)); + } + } + } + } + + // Walk product tree for any root entities not yet handled + if (!solid_ids.empty() || !direct_shell_ids.empty()) { + // Already handled above } else if (!root_entities.empty()) { // Walk PRODUCT → PRODUCT_DEFINITION_FORMATION → ... for (int rid : root_entities) { @@ -1172,6 +1308,14 @@ private: bool is_outer = (ent.type == "FACE_OUTER_BOUND"); + // AP203 compatibility: FACE_OUTER_BOUND is the standard bound type in AP203 + // Both FACE_BOUND and FACE_OUTER_BOUND have identical structure + if (ent.type != "FACE_BOUND" && ent.type != "FACE_OUTER_BOUND") { + // Unknown bound type — try to parse anyway + g_import_diag.add_warning("Unknown face bound type #" + std::to_string(bound_id) + + ": " + ent.type + " — attempting to parse as FACE_BOUND"); + } + int loop_ref = -1; for (const auto& arg : ent.args) { int r = to_ref(arg); @@ -1411,11 +1555,61 @@ private: if (!entities_.count(root_id)) return result; const auto& ent = entities_.at(root_id); - // SHAPE_DEFINITION_REPRESENTATION → find MANIFOLD_SOLID_BREP refs + + // Skip SolidWorks-specific non-geometric entities + if (ent.type == "APPLICATION_CONTEXT" || + ent.type == "APPLICATION_PROTOCOL_DEFINITION" || + ent.type == "MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION" || + ent.type == "STYLED_ITEM" || + ent.type == "PRESENTATION_STYLE_ASSIGNMENT" || + ent.type == "SHAPE_REPRESENTATION" || + ent.type == "REPRESENTATION_CONTEXT" || + ent.type == "PRODUCT_DEFINITION_CONTEXT" || + ent.type == "PRODUCT_RELATED_PRODUCT_CATEGORY" || + ent.type == "PRODUCT_CONTEXT" || + ent.type == "DESIGN_CONTEXT" || + ent.type == "CURVE_STYLE" || + ent.type == "SURFACE_STYLE_USAGE" || + ent.type == "SURFACE_SIDE_STYLE" || + ent.type == "SURFACE_STYLE_FILL_AREA" || + ent.type == "FILL_AREA_STYLE_COLOUR" || + ent.type == "COLOUR_RGB" || + ent.type == "DRAUGHTING_PRE_DEFINED_COLOUR" || + ent.type == "PRESENTATION_LAYER_ASSIGNMENT" || + ent.type == "MECHANICAL_CONTEXT" || + ent.type == "APPROVAL_ROLE" || + ent.type == "APPROVAL_PERSON_ORGANIZATION" || + ent.type == "APPROVAL_DATE_TIME" || + ent.type == "APPROVAL" || + ent.type == "CC_DESIGN_APPROVAL" || + ent.type == "CC_DESIGN_DATE_AND_TIME_ASSIGNMENT" || + ent.type == "CC_DESIGN_PERSON_AND_ORGANIZATION_ASSIGNMENT" || + ent.type == "CC_DESIGN_SECURITY_CLASSIFICATION" || + ent.type == "SECURITY_CLASSIFICATION_LEVEL" || + ent.type == "PERSON_AND_ORGANIZATION" || + ent.type == "PERSON" || + ent.type == "ORGANIZATION" || + ent.type == "DATE_AND_TIME" || + ent.type == "CALENDAR_DATE" || + ent.type == "LOCAL_TIME" || + ent.type == "COORDINATED_UNIVERSAL_TIME_OFFSET" || + ent.type == "UNCERTAINTY_MEASURE_WITH_UNIT" || + ent.type == "PROPERTY_DEFINITION" || + ent.type == "SUPPLIED_BREP_REPRESENTATION") { + g_import_diag.skipped_solidworks_entities++; + return result; + } + + // SHAPE_DEFINITION_REPRESENTATION / SHAPE_REPRESENTATION_RELATIONSHIP → find MANIFOLD_SOLID_BREP refs std::vector solid_refs; find_nested_refs(ent, "MANIFOLD_SOLID_BREP", solid_refs); find_nested_refs(ent, "SHELL_BASED_SURFACE_MODEL", solid_refs); + // AP203: also look for CLOSED_SHELL directly + if (solid_refs.empty() || g_import_diag.is_ap203) { + find_nested_refs(ent, "CLOSED_SHELL", solid_refs); + } + if (solid_refs.empty()) { // Try just iterating all args collect_all_refs(ent, solid_refs); @@ -1424,16 +1618,32 @@ private: for (int r : solid_refs) { if (entities_.count(r)) { const auto& e = entities_.at(r); - if (e.type == "MANIFOLD_SOLID_BREP" || e.type == "SHELL_BASED_SURFACE_MODEL") + if (e.type == "MANIFOLD_SOLID_BREP" || e.type == "SHELL_BASED_SURFACE_MODEL" || + e.type == "CLOSED_SHELL") { filtered.push_back(r); + } } } solid_refs = filtered; } for (int sid : solid_refs) { - auto body = convert_solid(sid); - if (body.num_faces() > 0) result.push_back(std::move(body)); + if (entities_.count(sid) && entities_.at(sid).type == "CLOSED_SHELL") { + // Direct CLOSED_SHELL (AP203 mode) + BrepModel model; + auto face_ids = convert_shell(sid, model); + if (!face_ids.empty()) { + int shell_id = model.add_shell(face_ids, true); + std::string name = find_product_name(sid); + model.add_body({shell_id}, name); + if (model.num_faces() > 0) { + result.push_back(std::move(model)); + } + } + } else { + auto body = convert_solid(sid); + if (body.num_faces() > 0) result.push_back(std::move(body)); + } } return result; } @@ -1492,6 +1702,12 @@ std::vector import_step_from_string(const std::string& step_data) { } StepParser parser(step_data); + + // Detect AP203 schema from HEADER before parsing DATA + if (parser.detect_ap203_header()) { + g_import_diag.is_ap203 = true; + } + if (!parser.parse_data_section()) { // Even if parse failed entirely, return what we have if any entities parsed if (parser.entities().empty()) { @@ -1543,6 +1759,12 @@ std::string StepImportReport::report() const { oss << "Skipped (unsupported): " << skipped_unsupported << "\n"; oss << "Approximated surfaces: " << approximated_surfaces << "\n"; oss << "Recovered faces: " << recovered_faces << "\n"; + if (ap203_specific) { + oss << "AP203 compatibility mode: enabled\n"; + } + if (skipped_solidworks_entities > 0) { + oss << "Skipped SolidWorks non-geometric entities: " << skipped_solidworks_entities << "\n"; + } if (!warnings.empty()) { oss << "Warnings (" << warnings.size() << "):\n"; for (size_t i = 0; i < warnings.size() && i < 50; ++i) { @@ -1563,6 +1785,8 @@ StepImportReport step_import_diagnostic() { r.skipped_unsupported = g_import_diag.skipped_unsupported; r.approximated_surfaces = g_import_diag.approximated_surfaces; r.recovered_faces = g_import_diag.recovered_faces; + r.ap203_specific = g_import_diag.is_ap203; + r.skipped_solidworks_entities = g_import_diag.skipped_solidworks_entities; r.warnings = g_import_diag.warnings; return r; } diff --git a/tests/brep/test_step_import.cpp b/tests/brep/test_step_import.cpp index cfbaa73..8d07072 100644 --- a/tests/brep/test_step_import.cpp +++ b/tests/brep/test_step_import.cpp @@ -680,3 +680,200 @@ TEST(StepImport, MixedValidAndInvalidEntities) { ASSERT_GE(bodies.size(), 1u); EXPECT_TRUE(bodies[0].is_valid()); } + +// ───────────────────────────────────────────────────────────── +// Helper: build an AP203 header +// ───────────────────────────────────────────────────────────── + +static std::string step_header_ap203() { + return R"(ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDesignEngine AP203 test'),'2;1'); +FILE_NAME('test_ap203.stp','2025-01-01T00:00:00',('Author'),(''),'','',''); +FILE_SCHEMA(('CONFIG_CONTROL_DESIGN { 1 0 10303 203 2 1 4 }')); +ENDSEC; +DATA; +)"; +} + +// ───────────────────────────────────────────────────────────── +// Test: AP203 CLOSED_SHELL direct import (no MANIFOLD_SOLID_BREP) +// ───────────────────────────────────────────────────────────── + +TEST(StepImport, AP203_ClosedShellDirect) { + // AP203 may have CLOSED_SHELL directly as top-level without MANIFOLD_SOLID_BREP + std::string step = step_header_ap203() + 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('ap203_disc',(#12)); +)" + step_footer(); + + auto bodies = import_step_from_string(step); + EXPECT_EQ(step_last_error(), StepError::Ok); + + // AP203 mode should have been detected + auto diag = step_import_diagnostic(); + EXPECT_TRUE(diag.ap203_specific); + + ASSERT_GE(bodies.size(), 1u); + EXPECT_TRUE(bodies[0].is_valid()); + EXPECT_GE(bodies[0].num_faces(), 1u); +} + +// ───────────────────────────────────────────────────────────── +// Test: AP203 CLOSED_SHELL with ADVANCED_FACE using FACE_OUTER_BOUND +// ───────────────────────────────────────────────────────────── + +TEST(StepImport, AP203_FaceOuterBound) { + // AP203 uses FACE_OUTER_BOUND instead of FACE_BOUND + std::string step = step_header_ap203() + 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=LINE('',#1,#4); +#8=EDGE_CURVE('',#6,#6,#7,.T.); +#9=ORIENTED_EDGE('',*,*,#8,.T.); +#10=EDGE_LOOP('',(#9)); +#11=FACE_OUTER_BOUND('outer',#10,.T.); +#12=PLANE('',#5); +#13=ADVANCED_FACE('face_ap203',(#11),#12,.T.); +#14=CLOSED_SHELL('shell_ap203',(#13)); +)" + step_footer(); + + auto bodies = import_step_from_string(step); + EXPECT_EQ(step_last_error(), StepError::Ok); + + auto diag = step_import_diagnostic(); + EXPECT_TRUE(diag.ap203_specific); + + ASSERT_GE(bodies.size(), 1u); + EXPECT_TRUE(bodies[0].is_valid()); +} + +// ───────────────────────────────────────────────────────────── +// Test: AP203 with SolidWorks-style non-geometric entities +// ───────────────────────────────────────────────────────────── + +TEST(StepImport, AP203_SolidWorksStyleEntities) { + // Simulate SolidWorks AP203 export with non-geometric entities + std::string step = step_header_ap203() + 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('part',(#12)); +#20=APPLICATION_CONTEXT('configuration controlled 3d designs of mechanical parts and assemblies'); +#21=APPLICATION_PROTOCOL_DEFINITION('','config_control_design',2002,#20); +#22=PRODUCT('Part1','Part1','',(#20)); +#23=PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE('','',#22,.NOT_KNOWN.); +#24=PRODUCT_DEFINITION('design','',#23,#20); +#25=PRODUCT_DEFINITION_SHAPE('',$,#24); +#26=SHAPE_DEFINITION_REPRESENTATION(#25,#13); +#30=STYLED_ITEM('color',(#40),#12); +#31=PRESENTATION_STYLE_ASSIGNMENT((#41)); +#40=PRESENTATION_LAYER_ASSIGNMENT('layer1','visible',(#30)); +#41=SURFACE_STYLE_USAGE(.BOTH.,#42); +#42=SURFACE_SIDE_STYLE('',(#43)); +#43=SURFACE_STYLE_FILL_AREA(#44); +#44=FILL_AREA_STYLE_COLOUR('',#45); +#45=COLOUR_RGB('red',1.0,0.0,0.0); +)" + step_footer(); + + auto bodies = import_step_from_string(step); + EXPECT_EQ(step_last_error(), StepError::Ok); + + auto diag = step_import_diagnostic(); + EXPECT_TRUE(diag.ap203_specific); + // SolidWorks non-geometric entities should be counted as skipped + EXPECT_GT(diag.skipped_solidworks_entities, 0); + + ASSERT_GE(bodies.size(), 1u); + EXPECT_TRUE(bodies[0].is_valid()); +} + +// ───────────────────────────────────────────────────────────── +// Test: AP203 diagnostic report includes ap203_specific +// ───────────────────────────────────────────────────────────── + +TEST(StepImport, AP203_DiagnosticReport) { + std::string step = step_header_ap203() + 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('',(2.0,0.0,0.0)); +#5=VERTEX_POINT('',#4); +#6=CIRCLE('',#3,2.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('ap203_part',(#12)); +)" + step_footer(); + + auto bodies = import_step_from_string(step); + + auto diag = step_import_diagnostic(); + EXPECT_TRUE(diag.ap203_specific); + EXPECT_GT(diag.total_entities, 0); + + std::string report = diag.report(); + EXPECT_NE(report.find("AP203 compatibility mode: enabled"), std::string::npos); + EXPECT_NE(report.find("=== STEP Import Diagnostic Report ==="), std::string::npos); +} + +// ───────────────────────────────────────────────────────────── +// Test: AP203 with MANIFOLD_SOLID_BREP (backward compatible) +// ───────────────────────────────────────────────────────────── + +TEST(StepImport, AP203_WithManifoldSolidBrep) { + // Some AP203 files still use MANIFOLD_SOLID_BREP — should work fine + std::string step = step_header_ap203() + 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('',(4.0,0.0,0.0)); +#5=VERTEX_POINT('',#4); +#6=CIRCLE('',#3,4.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('box_with_msb',#13); +)" + step_footer(); + + auto bodies = import_step_from_string(step); + EXPECT_EQ(step_last_error(), StepError::Ok); + + auto diag = step_import_diagnostic(); + EXPECT_TRUE(diag.ap203_specific); + + ASSERT_GE(bodies.size(), 1u); + EXPECT_TRUE(bodies[0].is_valid()); + EXPECT_GE(bodies[0].num_faces(), 1u); +}