638 lines
20 KiB
C++
638 lines
20 KiB
C++
|
|
#include <gtest/gtest.h>
|
||
|
|
#include <fstream>
|
||
|
|
#include <cstdio>
|
||
|
|
#include <cmath>
|
||
|
|
#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<char>(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<char>(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<char>(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<StepPMIAnnotation> 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<StepPMIAnnotation> 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<char>(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<StepPMIAnnotation> 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<char>(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<StepPMIAnnotation> empty_anns;
|
||
|
|
std::string result = export_step_ap242({box}, empty_anns);
|
||
|
|
// 不应崩溃,且应包含基本结构
|
||
|
|
EXPECT_NE(result.find("ISO-10303-21"), std::string::npos);
|
||
|
|
}
|