#include #include #include #include #include "vde/foundation/industrial_formats.h" #include "vde/brep/modeling.h" using namespace vde::io; using namespace vde::brep; using namespace vde::core; // ═══════════════════════════════════════════════════════════════ // 辅助函数 // ═══════════════════════════════════════════════════════════════ static BrepModel make_test_box() { return make_box(2.0, 3.0, 4.0); } static BrepModel make_test_cylinder() { return make_cylinder(1.0, 5.0); } // 清理临时文件 static void clean(const char* path) { std::remove(path); } // 验证 BrepModel 的基本有效性 static void expect_valid_body(const BrepModel& m) { EXPECT_GT(m.num_vertices(), 0); EXPECT_GT(m.num_edges(), 0); EXPECT_GT(m.num_faces(), 0); EXPECT_GT(m.num_bodies(), 0); } // ═══════════════════════════════════════════════════════════════ // JT 测试 (ISO 14306) // ═══════════════════════════════════════════════════════════════ TEST(IndustrialJT, ExportBoxToJt) { auto box = make_test_box(); const char* path = "/tmp/test_box.jt"; clean(path); ASSERT_NO_THROW(export_jt(box, path)); std::ifstream f(path, std::ios::binary); ASSERT_TRUE(f.good()); // 检查 JT 版本字符串 char version[80] = {}; f.read(version, 80); EXPECT_NE(std::string(version).find("JT"), std::string::npos); // 检查字节序标记 char byte_order; f.read(&byte_order, 1); EXPECT_EQ(byte_order, 0); // LE clean(path); } TEST(IndustrialJT, ImportExportedJt) { auto box = make_test_box(); const char* path = "/tmp/test_roundtrip.jt"; clean(path); export_jt(box, path); // 使用默认选项导入(加载网格) JTOptions opts; opts.load_mesh = true; opts.load_brep = false; auto imported = import_jt(path, opts); expect_valid_body(imported); clean(path); } TEST(IndustrialJT, ImportJtWithBrepOption) { auto box = make_test_box(); const char* path = "/tmp/test_jt_brep.jt"; clean(path); export_jt(box, path); JTOptions opts; opts.load_brep = true; opts.load_mesh = false; auto imported = import_jt(path, opts); // 即使 B-Rep 加载失败,也应返回有效(可能为空)模型 EXPECT_NO_THROW(import_jt(path, opts)); clean(path); } TEST(IndustrialJT, ImportJtWithCustomLod) { auto box = make_test_box(); const char* path = "/tmp/test_jt_lod.jt"; clean(path); export_jt(box, path); JTOptions opts; opts.max_lod = 1; auto imported = import_jt(path, opts); expect_valid_body(imported); clean(path); } TEST(IndustrialJT, ImportJtWithPmiOption) { auto box = make_test_box(); const char* path = "/tmp/test_jt_pmi.jt"; clean(path); export_jt(box, path); JTOptions opts; opts.load_pmi = true; opts.load_meta = true; auto imported = import_jt(path, opts); expect_valid_body(imported); clean(path); } TEST(IndustrialJT, ImportJtNonexistentFile) { EXPECT_THROW(import_jt("/tmp/nonexistent_jt_file.jt"), std::runtime_error); } // ═══════════════════════════════════════════════════════════════ // Parasolid XT 测试 // ═══════════════════════════════════════════════════════════════ TEST(IndustrialParasolid, ExportBoxToXt) { auto box = make_test_box(); const char* path = "/tmp/test_box.x_t"; clean(path); ASSERT_NO_THROW(export_parasolid_xt(box, path)); std::ifstream f(path); ASSERT_TRUE(f.good()); std::string first_line; std::getline(f, first_line); // Parasolid text 格式以 "**" 开头 EXPECT_EQ(first_line.substr(0, 2), "**"); clean(path); } TEST(IndustrialParasolid, ImportExportedXt) { auto box = make_test_box(); const char* path = "/tmp/test_xt_roundtrip.x_t"; clean(path); export_parasolid_xt(box, path); auto imported = import_parasolid_xt(path); expect_valid_body(imported); clean(path); } TEST(IndustrialParasolid, ImportXtWithVertexData) { auto box = make_test_box(); const char* path = "/tmp/test_xt_vertex.x_t"; clean(path); export_parasolid_xt(box, path); auto imported = import_parasolid_xt(path); EXPECT_GT(imported.num_vertices(), 0); EXPECT_GT(imported.num_edges(), 0); EXPECT_GT(imported.num_faces(), 0); clean(path); } TEST(IndustrialParasolid, ExportCylinderToXt) { auto cyl = make_test_cylinder(); const char* path = "/tmp/test_cyl.x_t"; clean(path); export_parasolid_xt(cyl, path); std::ifstream f(path); std::string content((std::istreambuf_iterator(f)), {}); EXPECT_NE(content.find("VERTEX"), std::string::npos); EXPECT_NE(content.find("EDGE"), std::string::npos); clean(path); } TEST(IndustrialParasolid, ImportXtNonexistentFile) { EXPECT_THROW(import_parasolid_xt("/tmp/nonexistent_xt.x_t"), std::runtime_error); } // ═══════════════════════════════════════════════════════════════ // ACIS SAT 测试 // ═══════════════════════════════════════════════════════════════ TEST(IndustrialACIS, ExportBoxToSatV27) { auto box = make_test_box(); const char* path = "/tmp/test_box.sat"; clean(path); ASSERT_NO_THROW(export_acis_sat(box, path, ACISVersion::V27)); std::ifstream f(path); ASSERT_TRUE(f.good()); std::string first_line; std::getline(f, first_line); // 第一行应包含版本号 EXPECT_NE(first_line.find("27"), std::string::npos); clean(path); } TEST(IndustrialACIS, ExportBoxToSatV16) { auto box = make_test_box(); const char* path = "/tmp/test_box_v16.sat"; clean(path); export_acis_sat(box, path, ACISVersion::V16); std::ifstream f(path); std::string first_line; std::getline(f, first_line); EXPECT_NE(first_line.find("16"), std::string::npos); clean(path); } TEST(IndustrialACIS, ImportExportedSat) { auto box = make_test_box(); const char* path = "/tmp/test_sat_roundtrip.sat"; clean(path); export_acis_sat(box, path); auto imported = import_acis_sat(path); expect_valid_body(imported); clean(path); } TEST(IndustrialACIS, ImportSatWithEndMarker) { auto box = make_test_box(); const char* path = "/tmp/test_sat_end.sat"; clean(path); export_acis_sat(box, path); std::ifstream f(path); std::string content((std::istreambuf_iterator(f)), {}); EXPECT_NE(content.find("End-of-ACIS-data"), std::string::npos); auto imported = import_acis_sat(path); expect_valid_body(imported); clean(path); } TEST(IndustrialACIS, ExportCylinderToSat) { auto cyl = make_test_cylinder(); const char* path = "/tmp/test_cyl.sat"; clean(path); export_acis_sat(cyl, path); std::ifstream f(path); std::string content((std::istreambuf_iterator(f)), {}); EXPECT_NE(content.find("ViewDesignEngine"), std::string::npos); EXPECT_NE(content.find("body"), std::string::npos); clean(path); } TEST(IndustrialACIS, ImportSatNonexistentFile) { EXPECT_THROW(import_acis_sat("/tmp/nonexistent.sat"), std::runtime_error); } // ═══════════════════════════════════════════════════════════════ // IFC 测试 (ISO 16739) // ═══════════════════════════════════════════════════════════════ static std::string make_minimal_ifc() { return R"(ISO-10303-21; HEADER; FILE_DESCRIPTION(('ViewDesignEngine Test'),'2;1'); FILE_NAME('test.ifc','2025-01-01',('VDE'),(''),'',''); FILE_SCHEMA(('IFC4')); ENDSEC; DATA; #1=IFCPROJECT('proj',$,'Test Project',$,$,$,$,(#2),#10); #2=IFCSITE('site',$,$,$,$,$,$,$,$,$,$,$); #3=IFCWALL('wall1',$,'Wall 300x3000x240',$,$,#20,#30,$); #10=IFCOWNERHISTORY(#11,#12,$,$,$,$,$,$); #11=IFCPERSON('user','','',$,$,$,$,$); #12=IFCORGANIZATION('VDE','',$,$); #20=IFCLOCALPLACEMENT($,#21); #21=IFCAXIS2PLACEMENT3D(#22,$,$); #22=IFCCARTESIANPOINT((0.0,0.0,0.0)); #30=IFCPRODUCTDEFINITIONSHAPE($,$,(#40)); #40=IFCSHAPEREPRESENTATION($,'Body','SweptSolid',(#50)); #50=IFCEXTRUDEDAREASOLID(#60,$,$,3.0); #60=IFCRECTANGLEPROFILEDEF(.AREA.,$,#70,5.0,0.24); #70=IFCAXIS2PLACEMENT2D(#71,$); #71=IFCCARTESIANPOINT((0.0,0.0)); #80=IFCSLAB('slab1',$,'Slab 4000x300x4000',$,$,#90,#100,$); #90=IFCLOCALPLACEMENT($,#91); #91=IFCAXIS2PLACEMENT3D(#92,$,$); #92=IFCCARTESIANPOINT((0.0,0.0,0.0)); #100=IFCPRODUCTDEFINITIONSHAPE($,$,(#110)); #110=IFCSHAPEREPRESENTATION($,'Body','SweptSolid',(#120)); #120=IFCEXTRUDEDAREASOLID(#130,$,$,0.3); #130=IFCRECTANGLEPROFILEDEF(.AREA.,$,#140,4.0,4.0); #140=IFCAXIS2PLACEMENT2D(#141,$); #141=IFCCARTESIANPOINT((0.0,0.0)); #150=IFCBEAM('beam1',$,'Beam 4000x300x300',$,$,#160,#170,$); #160=IFCLOCALPLACEMENT($,#161); #161=IFCAXIS2PLACEMENT3D(#162,$,$); #162=IFCCARTESIANPOINT((0.0,0.0,3.0)); #170=IFCPRODUCTDEFINITIONSHAPE($,$,(#180)); #180=IFCSHAPEREPRESENTATION($,'Body','SweptSolid',(#190)); #190=IFCEXTRUDEDAREASOLID(#200,$,$,0.3); #200=IFCRECTANGLEPROFILEDEF(.AREA.,$,#210,0.3,0.3); #210=IFCAXIS2PLACEMENT2D(#211,$); #211=IFCCARTESIANPOINT((0.0,0.0)); ENDSEC; END-ISO-10303-21; )"; } TEST(IndustrialIFC, ImportWall) { std::string ifc = make_minimal_ifc(); const char* path = "/tmp/test_wall.ifc"; clean(path); { std::ofstream f(path); f << ifc; } auto models = import_ifc(path); EXPECT_GE(models.size(), 1u) << "Should find at least one IFC product"; if (!models.empty()) { const auto& wall = models[0]; EXPECT_GT(wall.num_vertices(), 0); EXPECT_GT(wall.num_bodies(), 0); } clean(path); } TEST(IndustrialIFC, ImportSlab) { std::string ifc = make_minimal_ifc(); const char* path = "/tmp/test_slab.ifc"; clean(path); { std::ofstream f(path); f << ifc; } auto models = import_ifc(path); // 应找到 wall + slab + beam EXPECT_GE(models.size(), 2u); clean(path); } TEST(IndustrialIFC, ImportBeam) { std::string ifc = make_minimal_ifc(); const char* path = "/tmp/test_beam.ifc"; clean(path); { std::ofstream f(path); f << ifc; } auto models = import_ifc(path); EXPECT_GE(models.size(), 3u); clean(path); } TEST(IndustrialIFC, ImportProductCount) { std::string ifc = make_minimal_ifc(); const char* path = "/tmp/test_count.ifc"; clean(path); { std::ofstream f(path); f << ifc; } auto models = import_ifc(path); // IFC 包含 IfcWall + IfcSlab + IfcBeam EXPECT_EQ(models.size(), 3u); clean(path); } TEST(IndustrialIFC, WallHasCorrectDimensions) { std::string ifc = make_minimal_ifc(); const char* path = "/tmp/test_wall_dims.ifc"; clean(path); { std::ofstream f(path); f << ifc; } auto models = import_ifc(path); ASSERT_GE(models.size(), 1u); // 验证模型有顶点 const auto& wall = models[0]; EXPECT_GT(wall.num_vertices(), 0); EXPECT_GT(wall.num_faces(), 0); clean(path); } TEST(IndustrialIFC, ImportNonexistentFile) { EXPECT_THROW(import_ifc("/tmp/nonexistent.ifc"), std::runtime_error); } // ═══════════════════════════════════════════════════════════════ // STEP AP242 测试 // ═══════════════════════════════════════════════════════════════ TEST(IndustrialStepAP242, ExportSingleBox) { auto box = make_test_box(); std::string result = export_step_ap242({box}); EXPECT_NE(result.find("ISO-10303-21"), std::string::npos); EXPECT_NE(result.find("AP242"), std::string::npos); EXPECT_NE(result.find("MANIFOLD_SOLID_BREP"), std::string::npos); EXPECT_NE(result.find("ENDSEC"), std::string::npos); EXPECT_NE(result.find("END-ISO-10303-21"), std::string::npos); } TEST(IndustrialStepAP242, ExportMultipleBodies) { auto box = make_test_box(); auto cyl = make_test_cylinder(); std::string result = export_step_ap242({box, cyl}); // 应包含两个 PRODUCT EXPECT_NE(result.find("PRODUCT"), std::string::npos); EXPECT_NE(result.find("CLOSED_SHELL"), std::string::npos); } TEST(IndustrialStepAP242, ExportWithPMIAnnotations) { auto box = make_test_box(); std::vector annotations; StepPMIAnnotation ann; ann.type = "linear_dimension"; ann.id = "DIM001"; ann.description = "Length dimension"; ann.nominal_value = 100.0; ann.upper_tolerance = 0.1; ann.lower_tolerance = -0.1; ann.datum_system = "Datum_A"; annotations.push_back(ann); std::string result = export_step_ap242({box}, annotations); EXPECT_NE(result.find("PRODUCT_MANUFACTURING_INFORMATION"), std::string::npos); EXPECT_NE(result.find("DIMENSIONAL_CHARACTERISTIC_REPRESENTATION"), std::string::npos); EXPECT_NE(result.find("SHAPE_DIMENSION_REPRESENTATION"), std::string::npos); } TEST(IndustrialStepAP242, ExportWithGeometricTolerance) { auto box = make_test_box(); std::vector annotations; StepPMIAnnotation ann; ann.type = "angular_dimension"; ann.id = "TOL001"; ann.description = "Angular tolerance"; ann.nominal_value = 90.0; ann.upper_tolerance = 0.5; ann.lower_tolerance = -0.5; ann.datum_system = "Datum_B"; annotations.push_back(ann); std::string result = export_step_ap242({box}, annotations); EXPECT_NE(result.find("GEOMETRIC_TOLERANCE"), std::string::npos); EXPECT_NE(result.find("DATUM_SYSTEM"), std::string::npos); } TEST(IndustrialStepAP242, ExportWithMaterial) { auto box = make_test_box(); StepAP242Options opts; opts.include_material = true; opts.include_pmi = false; std::string result = export_step_ap242({box}, {}, opts); EXPECT_NE(result.find("MATERIAL_DESIGNATION"), std::string::npos); EXPECT_NE(result.find("MATERIAL_PROPERTY"), std::string::npos); } TEST(IndustrialStepAP242, ExportWithoutPmi) { auto box = make_test_box(); StepAP242Options opts; opts.include_pmi = false; opts.include_tolerance = false; std::string result = export_step_ap242({box}, {}, opts); // 不应包含 PMI 实体 EXPECT_EQ(result.find("PRODUCT_MANUFACTURING_INFORMATION"), std::string::npos); // 但基本几何信息应存在 EXPECT_NE(result.find("CARTESIAN_POINT"), std::string::npos); } TEST(IndustrialStepAP242, ExportValidationProperties) { auto box = make_test_box(); StepAP242Options opts; opts.include_validation = true; std::string result = export_step_ap242({box}, {}, opts); EXPECT_NE(result.find("VALIDATION"), std::string::npos); } TEST(IndustrialStepAP242, ExportToFile) { auto box = make_test_box(); const char* path = "/tmp/test_ap242.stp"; clean(path); export_step_ap242_file(path, {box}); std::ifstream f(path); ASSERT_TRUE(f.good()); std::string content((std::istreambuf_iterator(f)), {}); EXPECT_NE(content.find("AP242"), std::string::npos); clean(path); } TEST(IndustrialStepAP242, ExportToFileWithPMI) { auto box = make_test_box(); const char* path = "/tmp/test_ap242_pmi.stp"; clean(path); std::vector anns; StepPMIAnnotation ann; ann.type = "linear_dimension"; ann.id = "PMI1"; ann.nominal_value = 50.0; anns.push_back(ann); export_step_ap242_file(path, {box}, anns); std::ifstream f(path); std::string content((std::istreambuf_iterator(f)), {}); EXPECT_NE(content.find("PRODUCT_MANUFACTURING_INFORMATION"), std::string::npos); clean(path); } // ═══════════════════════════════════════════════════════════════ // PDF 3D 测试 // ═══════════════════════════════════════════════════════════════ TEST(IndustrialPDF3D, ExportBoxToPdf) { auto box = make_test_box(); auto pdf_data = export_pdf3d(box); EXPECT_GT(pdf_data.size(), 100u) << "PDF should have meaningful content"; // 检查 PDF 头部 std::string header(pdf_data.begin(), pdf_data.begin() + std::min(size_t(8), pdf_data.size())); EXPECT_EQ(header, "%PDF-1.7"); } TEST(IndustrialPDF3D, ExportContainsU3D) { auto box = make_test_box(); auto pdf_data = export_pdf3d(box); // 查找 U3D 标记 std::string content(pdf_data.begin(), pdf_data.end()); EXPECT_NE(content.find("U3D"), std::string::npos); EXPECT_NE(content.find("/Subtype /U3D"), std::string::npos); } TEST(IndustrialPDF3D, ExportToFile) { auto box = make_test_box(); const char* path = "/tmp/test_3d.pdf"; clean(path); export_pdf3d_file(path, box); std::ifstream f(path, std::ios::binary); ASSERT_TRUE(f.good()); char magic[8] = {}; f.read(magic, 8); EXPECT_EQ(std::string(magic, 8), "%PDF-1.7"); // 读取全部内容检查 f.seekg(0, std::ios::end); auto size = f.tellg(); EXPECT_GT(size, 200) << "PDF file should be at least 200 bytes"; clean(path); } TEST(IndustrialPDF3D, ExportContains3DAnnotation) { auto box = make_test_box(); auto pdf_data = export_pdf3d(box); std::string content(pdf_data.begin(), pdf_data.end()); EXPECT_NE(content.find("/Subtype /3D"), std::string::npos); EXPECT_NE(content.find("/3DA"), std::string::npos); } // ═══════════════════════════════════════════════════════════════ // 边界情况测试 // ═══════════════════════════════════════════════════════════════ TEST(IndustrialBoundary, ExportEmptyBrep) { BrepModel empty; // 导出空模型不应崩溃 EXPECT_NO_THROW(export_jt(empty, "/tmp/test_empty.jt")); EXPECT_NO_THROW(export_parasolid_xt(empty, "/tmp/test_empty.x_t")); EXPECT_NO_THROW(export_acis_sat(empty, "/tmp/test_empty.sat")); clean("/tmp/test_empty.jt"); clean("/tmp/test_empty.x_t"); clean("/tmp/test_empty.sat"); } TEST(IndustrialBoundary, StepAp242EmptyAnnotations) { auto box = make_test_box(); std::vector empty_anns; std::string result = export_step_ap242({box}, empty_anns); // 不应崩溃,且应包含基本结构 EXPECT_NE(result.find("ISO-10303-21"), std::string::npos); }