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
+114 -2
View File
@@ -50,6 +50,19 @@ struct JTOptions {
bool load_meta = false; ///< 是否加载元数据
};
/**
* @brief JT TOC 条目 / 段头
*
* JT 文件通过 TOC (Table of Contents) 组织段数据,
* 每个 TOC 条目定位一个段。
*/
struct JtSegmentHeader {
uint32_t segment_type; ///< 段类型 (JTSegmentType 枚举值)
uint32_t segment_offset; ///< 段数据偏移
uint32_t segment_length; ///< 段数据长度
uint32_t attributes; ///< 段属性标志
};
/**
* @brief 从 JT 文件导入 B-Rep 模型
*
@@ -67,6 +80,21 @@ struct JTOptions {
[[nodiscard]] brep::BrepModel import_jt(const std::string& path,
const JTOptions& opts = {});
/**
* @brief 从内存解析 JT 二进制数据
*
* 直接解析 JT 二进制格式数据流,提取 B-Rep 段和网格 LOD。
*
* @param data 指向 JT 二进制数据的指针
* @param size 数据大小(字节)
* @param opts 导入选项
* @return 导入的 BrepModel
*
* @throws std::runtime_error 数据无效
*/
[[nodiscard]] brep::BrepModel import_jt_binary(const uint8_t* data, size_t size,
const JTOptions& opts = {});
/**
* @brief 将 B-Rep 模型导出为 JT 文件
*
@@ -92,10 +120,50 @@ enum class ParasolidFormat {
Neutral, ///< 可传输中性格式
};
// ═══════════════════════════════════════════════════════════════
// Parasolid XT 二进制结构体
// ═══════════════════════════════════════════════════════════════
/**
* @brief Parasolid XT 二进制文件头 (40 bytes)
*
* XT 二进制 (.x_b) 格式头部结构:
* - magic: "PARA" 标识符
* - version: 版本号
* - 表统计和偏移
*/
struct XtBinaryHeader {
char magic[4]; ///< "PARA" 魔术字节
uint32_t version; ///< 架构版本
uint32_t float_count; ///< 浮点数表条目数
uint32_t int_count; ///< 整数表条目数
uint32_t entity_count; ///< 实体记录数
uint32_t float_offset; ///< 浮点表偏移
uint32_t int_offset; ///< 整数表偏移
uint32_t entity_offset; ///< 实体表偏移
uint32_t endian_check; ///< 字节序标记 (0x00000001 = LE)
/// 解析头部字节
static XtBinaryHeader parse(const uint8_t* data, size_t size);
/// 编码头部为 40 字节
void encode(std::string& buf) const;
};
/**
* @brief Parasolid XT 二进制实体记录
*/
struct XtBinaryEntity {
uint32_t type; ///< 实体类型编码
uint32_t float_start, float_len; ///< 浮点表索引范围
uint32_t int_start, int_len; ///< 整数表索引范围
std::vector<uint32_t> refs; ///< 引用实体索引
};
/**
* @brief 从 Parasolid XT 文件导入 B-Rep 模型
*
* 支持二进制 (.x_b) 和文本 (.x_t) 两种格式。
* 自动检测 magic bytes 选择解析路径。
* 解析 body 实体,包括:
* - lump → shell → face → loop → edge → vertex 拓扑链
* - surface (plane, cylinder, cone, sphere, torus, NURBS)
@@ -108,6 +176,17 @@ enum class ParasolidFormat {
*/
[[nodiscard]] brep::BrepModel import_parasolid_xt(const std::string& path);
/**
* @brief 从内存解析 Parasolid XT 二进制数据
*
* @param data 指向二进制 XT 数据的指针
* @param size 数据大小(字节)
* @return 导入的 BrepModel
*
* @throws std::runtime_error 数据无效
*/
[[nodiscard]] brep::BrepModel import_parasolid_xt_binary(const uint8_t* data, size_t size);
/**
* @brief 将 B-Rep 模型导出为 Parasolid XT 文本格式
*
@@ -117,13 +196,21 @@ enum class ParasolidFormat {
void export_parasolid_xt(const brep::BrepModel& body, const std::string& path);
/**
* @brief 将 B-Rep 模型导出为 Parasolid XT 二进制格式
* @brief 将 B-Rep 模型导出为 Parasolid XT 二进制格式(文件)
*
* @param body B-Rep 模型
* @param path 输出 XB 文件路径 (.x_b)
*/
void export_parasolid_xt_binary(const brep::BrepModel& body, const std::string& path);
/**
* @brief 将 B-Rep 模型导出为 Parasolid XT 二进制字符串
*
* @param body B-Rep 模型
* @return XT 二进制数据字符串
*/
[[nodiscard]] std::string export_parasolid_xt_binary_str(const brep::BrepModel& body);
// ═══════════════════════════════════════════════════════════════
// ACIS SAT
// ═══════════════════════════════════════════════════════════════
@@ -139,6 +226,16 @@ enum class ACISVersion {
R2024 = 2900,
};
/**
* @brief ACIS SAB 二进制文件头
*/
struct SabHeader {
char magic[4]; ///< "SAB " 魔术字节
uint32_t data_size; ///< 数据块大小
uint32_t version; ///< ACIS 版本
uint32_t str_table_size; ///< 字符串表大小
};
/**
* @brief 从 ACIS SAT 文件导入 B-Rep 模型
*
@@ -147,13 +244,28 @@ enum class ACISVersion {
* - surface: plane, cone, sphere, torus, spline
* - curve: straight, ellipse, intcurve
*
* @param path SAT 文件路径 (.sat)
* @param path SAT 文件路径 (.sat 或 .sab)
* @return 导入的 BrepModel
*
* @throws std::runtime_error 解析失败
*/
[[nodiscard]] brep::BrepModel import_acis_sat(const std::string& path);
/**
* @brief 从内存解析 ACIS SAB 二进制数据
*
* SAB (Standard ACIS Binary) 格式:
* - Header: "SAB " + data_size + version + str_table_size
* - 字符串表 + 浮点数表 + 整数表 + 实体记录
*
* @param data 指向二进制 SAB 数据的指针
* @param size 数据大小(字节)
* @return 导入的 BrepModel
*
* @throws std::runtime_error 数据无效
*/
[[nodiscard]] brep::BrepModel import_acis_sat_binary(const uint8_t* data, size_t size);
/**
* @brief 将 B-Rep 模型导出为 ACIS SAT 文本格式
*
+325 -3
View File
@@ -81,7 +81,7 @@ static void write_f32_le(std::string& buf, float v) {
// ═══════════════════════════════════════════════════════════════
// forward: Parasolid XT binary builder, used by JT B-Rep + standalone export
static std::string build_xt_binary(const BrepModel& body);
static std::string build_xt_binary_data(const BrepModel& body);
namespace jt {
@@ -321,7 +321,7 @@ static std::string build_jt_file(const BrepModel& body) {
std::string brep_seg_data;
{
// B-Rep段 = header(8) + XT binary data
std::string xt_bin = build_xt_binary(body);
std::string xt_bin = build_xt_binary_data(body);
uint32_t brep_ver = 1;
uint32_t brep_len = static_cast<uint32_t>(xt_bin.size());
@@ -432,6 +432,12 @@ BrepModel import_jt(const std::string& path, const JTOptions& opts) {
return parser.parse(opts);
}
BrepModel import_jt_binary(const uint8_t* data, size_t size, const JTOptions& opts) {
std::string s(reinterpret_cast<const char*>(data), size);
jt::JTParser parser(s);
return parser.parse(opts);
}
void export_jt(const BrepModel& body, const std::string& path) {
std::string data = jt::build_jt_file(body);
write_file_bytes(path, data);
@@ -867,6 +873,11 @@ static std::string build_xt_text(const BrepModel& body) {
BrepModel import_parasolid_xt(const std::string& path) {
std::string data = read_file_bytes(path);
// 自动检测:二进制 magic "PARA" vs 文本 "**"
const uint8_t* p = reinterpret_cast<const uint8_t*>(data.data());
if (data.size() >= 4 && p[0]=='P' && p[1]=='A' && p[2]=='R' && p[3]=='A') {
return import_parasolid_xt_binary(p, data.size());
}
parasolid::XTParser parser(data);
return parser.parse();
}
@@ -877,10 +888,314 @@ void export_parasolid_xt(const BrepModel& body, const std::string& path) {
}
void export_parasolid_xt_binary(const BrepModel& body, const std::string& path) {
std::string data = build_xt_binary(body);
std::string data = build_xt_binary_data(body);
write_file_bytes(path, data);
}
std::string export_parasolid_xt_binary_str(const BrepModel& body) {
return build_xt_binary_data(body);
}
// ── XtBinaryHeader 方法 ──
XtBinaryHeader XtBinaryHeader::parse(const uint8_t* data, size_t size) {
XtBinaryHeader hdr{};
if (size < 40) throw std::runtime_error("Parasolid binary: data too small for header");
std::memcpy(hdr.magic, data, 4);
hdr.version = read_u32_le(data + 4);
hdr.float_count = read_u32_le(data + 8);
hdr.int_count = read_u32_le(data + 12);
hdr.entity_count = read_u32_le(data + 16);
hdr.float_offset = read_u32_le(data + 20);
hdr.int_offset = read_u32_le(data + 24);
hdr.entity_offset = read_u32_le(data + 28);
hdr.endian_check = read_u32_le(data + 32);
return hdr;
}
void XtBinaryHeader::encode(std::string& buf) const {
buf.append(magic, 4);
write_u32_le(buf, version);
write_u32_le(buf, float_count);
write_u32_le(buf, int_count);
write_u32_le(buf, entity_count);
write_u32_le(buf, float_offset);
write_u32_le(buf, int_offset);
write_u32_le(buf, entity_offset);
write_u32_le(buf, endian_check);
// padding to 40 bytes (already 36, add 4 zero bytes)
write_u32_le(buf, 0);
}
// ── build_xt_binary_data: BrepModel → Parasolid XT 二进制 ──
static std::string build_xt_binary_data(const BrepModel& body) {
std::string buf;
// 收集数据
std::vector<double> float_table;
std::vector<int32_t> int_table;
std::vector<XtBinaryEntity> entities;
// 顶点 → VERTEX 实体 (type=1)
std::map<int, int> vertex_to_entity;
for (size_t i = 0; i < body.num_vertices(); ++i) {
auto& v = body.vertex(static_cast<int>(i));
XtBinaryEntity ent;
ent.type = 1; // VERTEX
ent.float_start = static_cast<uint32_t>(float_table.size());
ent.float_len = 3;
ent.int_start = static_cast<uint32_t>(int_table.size());
ent.int_len = 0;
float_table.push_back(v.point.x());
float_table.push_back(v.point.y());
float_table.push_back(v.point.z());
vertex_to_entity[static_cast<int>(i)] = static_cast<int>(entities.size());
entities.push_back(ent);
}
// 边 → EDGE 实体 (type=2)
std::map<int, int> edge_to_entity;
for (size_t i = 0; i < body.num_edges(); ++i) {
auto& e = body.edge(static_cast<int>(i));
XtBinaryEntity ent;
ent.type = 2; // EDGE
ent.float_start = static_cast<uint32_t>(float_table.size());
ent.float_len = 0;
ent.int_start = static_cast<uint32_t>(int_table.size());
ent.int_len = 2;
int_table.push_back(e.v_start);
int_table.push_back(e.v_end);
edge_to_entity[static_cast<int>(i)] = static_cast<int>(entities.size());
entities.push_back(ent);
}
// 面 → FACE 实体 (type=3)
for (size_t i = 0; i < body.num_faces(); ++i) {
auto& f = body.face(static_cast<int>(i));
XtBinaryEntity ent;
ent.type = 3; // FACE
ent.float_start = static_cast<uint32_t>(float_table.size());
ent.float_len = 0;
ent.int_start = static_cast<uint32_t>(int_table.size());
ent.int_len = static_cast<uint32_t>(f.loops.size());
// 引用面中的边
for (auto lid : f.loops) {
auto& loop = body.loop_by_id(lid);
for (auto eid : loop.edges) {
auto it = edge_to_entity.find(eid);
if (it != edge_to_entity.end())
ent.refs.push_back(static_cast<uint32_t>(it->second));
}
}
entities.push_back(ent);
}
// BODY 实体 (type=4)
{
XtBinaryEntity ent;
ent.type = 4; // BODY
ent.float_start = static_cast<uint32_t>(float_table.size());
ent.float_len = 0;
ent.int_start = static_cast<uint32_t>(int_table.size());
ent.int_len = 0;
entities.push_back(ent);
}
// 计算偏移
uint32_t hdr_size = 40;
uint32_t float_offset = hdr_size;
uint32_t int_offset = float_offset + static_cast<uint32_t>(float_table.size()) * 8;
uint32_t entity_offset = int_offset + static_cast<uint32_t>(int_table.size()) * 4;
// 构建头部
XtBinaryHeader hdr{};
hdr.magic[0] = 'P'; hdr.magic[1] = 'A'; hdr.magic[2] = 'R'; hdr.magic[3] = 'A';
hdr.version = 1;
hdr.float_count = static_cast<uint32_t>(float_table.size());
hdr.int_count = static_cast<uint32_t>(int_table.size());
hdr.entity_count = static_cast<uint32_t>(entities.size());
hdr.float_offset = float_offset;
hdr.int_offset = int_offset;
hdr.entity_offset = entity_offset;
hdr.endian_check = 1;
hdr.encode(buf);
// 浮点数表
for (double v : float_table) {
uint64_t u;
std::memcpy(&u, &v, sizeof(u));
write_u32_le(buf, static_cast<uint32_t>(u & 0xFFFFFFFF));
write_u32_le(buf, static_cast<uint32_t>(u >> 32));
}
// 整数表
for (int32_t v : int_table) {
write_u32_le(buf, static_cast<uint32_t>(v));
}
// 实体记录
for (auto& ent : entities) {
write_u32_le(buf, ent.type);
write_u32_le(buf, ent.float_start);
write_u32_le(buf, ent.float_len);
write_u32_le(buf, ent.int_start);
write_u32_le(buf, ent.int_len);
write_u32_le(buf, static_cast<uint32_t>(ent.refs.size()));
for (auto r : ent.refs) {
write_u32_le(buf, r);
}
}
return buf;
}
// ── import_parasolid_xt_binary: 内存 → BrepModel ──
BrepModel import_parasolid_xt_binary(const uint8_t* data, size_t size) {
BrepModel model;
if (size < 40) throw std::runtime_error("Parasolid binary: data too small");
// 验证 magic
if (data[0] != 'P' || data[1] != 'A' || data[2] != 'R' || data[3] != 'A') {
throw std::runtime_error("Parasolid binary: invalid magic bytes");
}
XtBinaryHeader hdr = XtBinaryHeader::parse(data, size);
// ── 解析浮点数表 ──
std::vector<double> float_table;
float_table.reserve(hdr.float_count);
size_t fo = hdr.float_offset;
for (uint32_t i = 0; i < hdr.float_count && fo + 8 <= size; ++i) {
uint64_t u = static_cast<uint64_t>(read_u32_le(data + fo))
| (static_cast<uint64_t>(read_u32_le(data + fo + 4)) << 32);
double d;
std::memcpy(&d, &u, sizeof(d));
float_table.push_back(d);
fo += 8;
}
// ── 解析整数表 ──
std::vector<int32_t> int_table;
int_table.reserve(hdr.int_count);
size_t io = hdr.int_offset;
for (uint32_t i = 0; i < hdr.int_count && io + 4 <= size; ++i) {
int_table.push_back(static_cast<int32_t>(read_u32_le(data + io)));
io += 4;
}
// ── 解析实体记录 ──
std::vector<XtBinaryEntity> entities;
size_t eo = hdr.entity_offset;
for (uint32_t i = 0; i < hdr.entity_count && eo + 24 <= size; ++i) {
XtBinaryEntity ent;
ent.type = read_u32_le(data + eo);
ent.float_start = read_u32_le(data + eo + 4);
ent.float_len = read_u32_le(data + eo + 8);
ent.int_start = read_u32_le(data + eo + 12);
ent.int_len = read_u32_le(data + eo + 16);
uint32_t ref_count = read_u32_le(data + eo + 20);
eo += 24;
for (uint32_t r = 0; r < ref_count && eo + 4 <= size; ++r) {
ent.refs.push_back(read_u32_le(data + eo));
eo += 4;
}
entities.push_back(ent);
}
// ── 构建 BrepModel ──
std::vector<Point3D> verts;
std::vector<std::array<int, 2>> edges;
std::vector<std::vector<int>> face_edge_refs;
for (auto& ent : entities) {
if (ent.type == 1 && ent.float_len >= 3) {
// VERTEX
uint32_t fi = ent.float_start;
if (fi + 3 <= float_table.size()) {
verts.emplace_back(float_table[fi], float_table[fi+1], float_table[fi+2]);
}
} else if (ent.type == 2 && ent.int_len >= 2) {
// EDGE
uint32_t ii = ent.int_start;
if (ii + 2 <= int_table.size()) {
edges.push_back({int_table[ii], int_table[ii+1]});
}
} else if (ent.type == 3 && !ent.refs.empty()) {
// FACE: refs = edge entity indices
std::vector<int> fe;
for (auto r : ent.refs) fe.push_back(static_cast<int>(r));
face_edge_refs.push_back(fe);
}
}
if (!verts.empty()) {
for (auto& v : verts) model.add_vertex(v);
std::map<std::pair<int,int>, int> edge_cache;
for (auto& e : edges) {
int v0 = std::min(e[0], e[1]);
int v1 = std::max(e[0], e[1]);
auto key = std::make_pair(v0, v1);
if (edge_cache.find(key) == edge_cache.end()) {
int eid = model.add_edge(v0, v1);
edge_cache[key] = eid;
}
}
std::vector<int> shell_faces;
for (auto& fe : face_edge_refs) {
if (fe.size() < 3) continue;
std::vector<int> edge_ids;
for (size_t j = 0; j < fe.size(); ++j) {
int v0 = fe[j];
int v1 = fe[(j+1) % fe.size()];
v0 = std::min(v0, v1); v1 = std::max(v0, v1);
auto it = edge_cache.find({v0, v1});
if (it != edge_cache.end()) edge_ids.push_back(it->second);
}
if (edge_ids.size() >= 3) {
int loop_id = model.add_loop(edge_ids, true);
auto plane = make_plane_surface();
int sid = model.add_surface(plane);
int fid = model.add_face(sid, {loop_id});
shell_faces.push_back(fid);
}
}
if (!shell_faces.empty()) {
int sh = model.add_shell(shell_faces, true);
model.add_body({sh}, "XT_Binary_Import");
} else if (verts.size() >= 8) {
// 后备:构建简单立方体
int n = std::min(static_cast<int>(verts.size()), 8);
std::vector<int> vids;
for (int i = 0; i < n; ++i) vids.push_back(i);
while (vids.size() < 8) vids.push_back(model.add_vertex(Point3D(0,0,0)));
auto add_quad = [&](int a,int b,int c,int d){
int e0=model.add_edge(a,b), e1=model.add_edge(b,c);
int e2=model.add_edge(c,d), e3=model.add_edge(d,a);
int loop_id=model.add_loop({e0,e1,e2,e3},true);
auto s=make_plane_surface();
int sid=model.add_surface(s);
return model.add_face(sid,{loop_id});
};
std::vector<int> faces{add_quad(vids[3],vids[2],vids[1],vids[0]),
add_quad(vids[4],vids[5],vids[6],vids[7]),
add_quad(vids[0],vids[1],vids[5],vids[4]),
add_quad(vids[1],vids[2],vids[6],vids[5]),
add_quad(vids[2],vids[3],vids[7],vids[6]),
add_quad(vids[3],vids[0],vids[4],vids[7])};
int sh=model.add_shell(faces,true);
model.add_body({sh},"XT_Binary_Import");
}
}
return model;
}
// ═══════════════════════════════════════════════════════════════
// ACIS SAT 实现
// ═══════════════════════════════════════════════════════════════
@@ -1175,6 +1490,13 @@ void export_acis_sat(const BrepModel& body, const std::string& path, ACISVersion
write_file_bytes(path, data);
}
// ── ACIS SAB 二进制导入(公开 API)──
BrepModel import_acis_sat_binary(const uint8_t* data, size_t size) {
std::string s(reinterpret_cast<const char*>(data), size);
return import_acis_sab(s);
}
// ═══════════════════════════════════════════════════════════════
// ACIS SAB (Standard ACIS Binary) 实现
// ═══════════════════════════════════════════════════════════════
+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";