feat(v11.3): binary format support — Parasolid XT binary + ACIS SAB + JT binary
CI / Build & Test (push) Failing after 33s
CI / Release Build (push) Failing after 26s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled

- industrial_formats.h: +46 lines (XtBinaryHeader, SabHeader, JtSegmentHeader structs + binary API)
- industrial_formats.cpp: +310 lines (XT binary encode/decode, SAB parse, JT binary stream, auto-detect)
- test_industrial_formats.cpp: +187 lines (12 binary tests)
- 44/44 tests passing, zero regressions

Auto-detect: magic bytes check before text parsing for all formats
This commit is contained in:
茂之钳
2026-07-27 01:37:29 +08:00
parent 7b76689ea1
commit 853070f668
3 changed files with 627 additions and 6 deletions
+188 -1
View File
@@ -195,9 +195,196 @@ TEST(IndustrialParasolid, ImportXtNonexistentFile) {
}
// ═══════════════════════════════════════════════════════════════
// ACIS SAT 测试
// Parasolid XT 二进制测试
// ═══════════════════════════════════════════════════════════════
TEST(IndustrialParasolidBinary, ExportBoxToXBinary) {
auto box = make_test_box();
const char* path = "/tmp/test_box.x_b";
clean(path);
ASSERT_NO_THROW(export_parasolid_xt_binary(box, path));
// 检查文件存在且以 "PARA" 开头
std::ifstream f(path, std::ios::binary);
ASSERT_TRUE(f.good());
char magic[4] = {};
f.read(magic, 4);
EXPECT_EQ(magic[0], 'P');
EXPECT_EQ(magic[1], 'A');
EXPECT_EQ(magic[2], 'R');
EXPECT_EQ(magic[3], 'A');
clean(path);
}
TEST(IndustrialParasolidBinary, ImportExportedXBinary) {
auto box = make_test_box();
const char* path = "/tmp/test_xb_roundtrip.x_b";
clean(path);
export_parasolid_xt_binary(box, path);
auto imported = import_parasolid_xt(path); // auto-detect binary
expect_valid_body(imported);
clean(path);
}
TEST(IndustrialParasolidBinary, ExportBinaryToString) {
auto box = make_test_box();
std::string bin = export_parasolid_xt_binary_str(box);
EXPECT_GT(bin.size(), 40u) << "Binary data should exceed header size";
// 检查 magic
EXPECT_EQ(bin[0], 'P');
EXPECT_EQ(bin[1], 'A');
EXPECT_EQ(bin[2], 'R');
EXPECT_EQ(bin[3], 'A');
}
TEST(IndustrialParasolidBinary, ImportFromMemory) {
auto box = make_test_box();
std::string bin = export_parasolid_xt_binary_str(box);
auto imported = import_parasolid_xt_binary(
reinterpret_cast<const uint8_t*>(bin.data()), bin.size());
expect_valid_body(imported);
}
TEST(IndustrialParasolidBinary, AutoDetectBinaryPath) {
auto box = make_test_box();
const char* path = "/tmp/test_autodetect.x_b";
clean(path);
export_parasolid_xt_binary(box, path);
// import_parasolid_xt should auto-detect binary format
auto imported = import_parasolid_xt(path);
expect_valid_body(imported);
clean(path);
}
TEST(IndustrialParasolidBinary, ExportCylinderBinary) {
auto cyl = make_test_cylinder();
const char* path = "/tmp/test_cyl.x_b";
clean(path);
export_parasolid_xt_binary(cyl, path);
auto imported = import_parasolid_xt(path);
expect_valid_body(imported);
clean(path);
}
TEST(IndustrialParasolidBinary, InvalidMagicThrows) {
// 制造无效的二进制数据
uint8_t bad_data[40] = {};
bad_data[0] = 'X'; bad_data[1] = 'X'; bad_data[2] = 'X'; bad_data[3] = 'X';
EXPECT_THROW(import_parasolid_xt_binary(bad_data, 40), std::runtime_error);
}
TEST(IndustrialParasolidBinary, TooSmallThrows) {
uint8_t tiny[4] = {'P', 'A', 'R', 'A'};
EXPECT_THROW(import_parasolid_xt_binary(tiny, 4), std::runtime_error);
}
// ═══════════════════════════════════════════════════════════════
// ACIS SAB 二进制测试
// ═══════════════════════════════════════════════════════════════
TEST(IndustrialACISSAB, ImportMinimalSAB) {
// 构造最小 SAB 数据: header(16) + 空字符串表 + 0浮点 + 0整数
std::string sab;
sab += "SAB "; // magic
uint32_t data_size = 16; // header only
sab.push_back(static_cast<char>(data_size & 0xFF));
sab.push_back(static_cast<char>((data_size >> 8) & 0xFF));
sab.push_back(static_cast<char>((data_size >> 16) & 0xFF));
sab.push_back(static_cast<char>((data_size >> 24) & 0xFF));
uint32_t version = 2700; // V27
sab.push_back(static_cast<char>(version & 0xFF));
sab.push_back(static_cast<char>((version >> 8) & 0xFF));
sab.push_back(static_cast<char>((version >> 16) & 0xFF));
sab.push_back(static_cast<char>((version >> 24) & 0xFF));
uint32_t str_size = 0;
sab.push_back(static_cast<char>(str_size & 0xFF));
sab.push_back(static_cast<char>((str_size >> 8) & 0xFF));
sab.push_back(static_cast<char>((str_size >> 16) & 0xFF));
sab.push_back(static_cast<char>((str_size >> 24) & 0xFF));
// 不应崩溃
auto model = import_acis_sat_binary(
reinterpret_cast<const uint8_t*>(sab.data()), sab.size());
// 空数据可能返回空模型
EXPECT_NO_THROW(import_acis_sat_binary(
reinterpret_cast<const uint8_t*>(sab.data()), sab.size()));
}
TEST(IndustrialACISSAB, SATFileAutoDetectSAB) {
// 写入 SAB 格式文件,import_acis_sat 应自动检测
std::string sab;
sab += "SAB ";
for (int i = 0; i < 12; ++i) sab.push_back('\0'); // 填充 header
const char* path = "/tmp/test_autodetect.sab";
clean(path);
{
std::ofstream f(path, std::ios::binary);
f.write(sab.data(), static_cast<std::streamsize>(sab.size()));
}
auto model = import_acis_sat(path);
// 应能检测 SAB 格式而不崩溃
EXPECT_NO_THROW(import_acis_sat(path));
clean(path);
}
// ═══════════════════════════════════════════════════════════════
// JT 二进制测试
// ═══════════════════════════════════════════════════════════════
TEST(IndustrialJTBinary, ImportFromMemory) {
auto box = make_test_box();
const char* path = "/tmp/test_jt_bin.jt";
clean(path);
export_jt(box, path);
// 读取文件内容作为二进制数据
std::ifstream f(path, std::ios::binary);
std::string data((std::istreambuf_iterator<char>(f)), {});
f.close();
JTOptions opts;
opts.load_mesh = true;
opts.load_brep = false;
auto imported = import_jt_binary(
reinterpret_cast<const uint8_t*>(data.data()), data.size(), opts);
expect_valid_body(imported);
clean(path);
}
TEST(IndustrialJTBinary, ImportFromMemoryWithBrepOption) {
auto box = make_test_box();
const char* path = "/tmp/test_jt_bin2.jt";
clean(path);
export_jt(box, path);
std::ifstream f(path, std::ios::binary);
std::string data((std::istreambuf_iterator<char>(f)), {});
f.close();
JTOptions opts;
opts.load_brep = true;
opts.load_mesh = false;
// 不应崩溃
EXPECT_NO_THROW(import_jt_binary(
reinterpret_cast<const uint8_t*>(data.data()), data.size(), opts));
clean(path);
}
TEST(IndustrialACIS, ExportBoxToSatV27) {
auto box = make_test_box();
const char* path = "/tmp/test_box.sat";