我想用C++实现一个JT文件阅读器。 JT 文件格式参考 记录了文件格式。我想出了以下“最小”示例(注意:我只对顶点和面数据(如果可用)感兴趣):
#include <iostream>
#include <fstream>
#include <vector>
#include <cassert>
struct GUID {
uint32_t a;
uint16_t b;
uint16_t c;
uint64_t d;
};
struct Header {
char version[80];
uint8_t byte_order;
int32_t reserved_field;
int32_t toc_offset;
GUID guid;
};
struct TocEntry {
GUID segment_id;
int32_t segment_offset;
int32_t segment_length;
uint32_t segment_attributes;
};
struct SegmentHeader {
GUID segment_id;
int32_t segment_type;
int32_t segment_length;
uint32_t segment_attributes;
};
struct ElementHeader {
GUID object_type_id;
char8_t object_base_type;
uint32_t object_id;
};
struct BaseNodeData {
int16_t version_number;
uint32_t node_flags;
int32_t attribute_count;
std::vector<int32_t> attribute_object_ids;
};
struct CoordF32 {
float x, y, z;
};
struct BBoxF32 {
CoordF32 min_corner;
CoordF32 max_corner;
};
struct BaseShapeData {
int16_t version_number;
BBoxF32 reserved_field;
BBoxF32 untransformed_bbox;
float area;
};
void read_guid(std::ifstream &file, GUID &guid) {
unsigned char guid_bytes[16];
file.read(reinterpret_cast<char *>(guid_bytes), sizeof(guid_bytes));
guid.a = (guid_bytes[0] << 24) | (guid_bytes[1] << 16) | (guid_bytes[2] << 8) | guid_bytes[3];
guid.b = (guid_bytes[4] << 8) | guid_bytes[5];
guid.c = (guid_bytes[6] << 8) | guid_bytes[7];
guid.d = (static_cast<uint64_t>(guid_bytes[8]) << 56) |
(static_cast<uint64_t>(guid_bytes[9]) << 48) |
(static_cast<uint64_t>(guid_bytes[10]) << 40) |
(static_cast<uint64_t>(guid_bytes[11]) << 32) |
(static_cast<uint64_t>(guid_bytes[12]) << 24) |
(static_cast<uint64_t>(guid_bytes[13]) << 16) |
(static_cast<uint64_t>(guid_bytes[14]) << 8) |
static_cast<uint64_t>(guid_bytes[15]);
}
void read_header(std::ifstream &file, Header &header) {
file.read(reinterpret_cast<char *>(&header.version), sizeof(char) * 80);
file.read(reinterpret_cast<char *>(&header.byte_order), sizeof(uint8_t) * 1);
file.read(reinterpret_cast<char *>(&header.reserved_field), sizeof(int32_t) * 1);
file.read(reinterpret_cast<char *>(&header.toc_offset), sizeof(header.toc_offset) * 1);
read_guid(file, header.guid);
}
int main() {
std::string filename = "jt/data/145275T010_001_MODEL_SOLIDS.jt";
std::ifstream file(filename.c_str());
if (!file.is_open()) {
throw std::runtime_error("could not load file");
}
// Read header
Header header{};
read_header(file, header);
// print version
std::cout << "version: ";
for (int i = 0; i < 75; i++) {
std::cout << header.version[i];
}
std::cout << std::endl;
// Read TOC entries
int32_t toc_entry_count = -1;
file.read(reinterpret_cast<char *>(&toc_entry_count), sizeof(int32_t) * 1);
std::vector<TocEntry> toc_entries;
toc_entries.resize(toc_entry_count);
for (int i = 0; i < toc_entry_count; ++i) {
TocEntry &entry = toc_entries[i];
read_guid(file, entry.segment_id);
file.read(reinterpret_cast<char *>(&entry.segment_offset), sizeof(int32_t) * 1);
file.read(reinterpret_cast<char *>(&entry.segment_length), sizeof(int32_t) * 1);
file.read(reinterpret_cast<char *>(&entry.segment_attributes), sizeof(uint32_t) * 1);
}
// no visit every segment
for (const TocEntry &entry: toc_entries) {
file.seekg(entry.segment_offset);
SegmentHeader segment_header{};
read_guid(file, segment_header.segment_id);
file.read(reinterpret_cast<char *>(&segment_header.segment_type), sizeof(int32_t) * 1);
file.read(reinterpret_cast<char *>(&segment_header.segment_length), sizeof(int32_t) * 1);
if (segment_header.segment_type == 6) std::cout << "Shape" << std::endl;
// it it is of type "Shape"
if (segment_header.segment_type == 6) {
//std::cout << "Found shape!" << std::endl;
uint32_t element_length;
file.read(reinterpret_cast<char *>(&element_length), sizeof(int32_t) * 1);
ElementHeader element_type{};
read_guid(file, element_type.object_type_id);
file.read(reinterpret_cast<char *>(&element_type.object_base_type), sizeof(char8_t) * 1);
file.read(reinterpret_cast<char *>(&element_type.object_id), sizeof(int32_t) * 1);
std::cout << "Base type: " << static_cast<int>(element_type.object_base_type) << std::endl;
if (static_cast<int>(element_type.object_base_type) == 4) {
std::cout << "Shape LOD found" << std::endl;
// read 7.2.1.1.1.1.1 Base Node Data
BaseNodeData base_node_data{};
file.read(reinterpret_cast<char *>(&base_node_data.version_number), sizeof(int16_t) * 1);
assert(base_node_data.version_number == 0x0001);
file.read(reinterpret_cast<char *>(&base_node_data.node_flags), sizeof(uint32_t) * 1);
file.read(reinterpret_cast<char *>(&base_node_data.attribute_count), sizeof(int32_t) * 1);
for(int i = 0; i < base_node_data.attribute_count; ++i) {
int attribute_object_id = -1;
file.read(reinterpret_cast<char *>(&attribute_object_id), sizeof(int32_t) * 1);
base_node_data.attribute_object_ids.push_back(attribute_object_id);
}
// see 7.2.1.1.1.10.1.1 Base Shape Data
BaseShapeData base_shape_data{};
file.read(reinterpret_cast<char *>(&base_shape_data.version_number), sizeof(int16_t) * 1);
assert(base_shape_data.version_number == 0x0001); // <--- fails
}
}
}
}
可以从这里下载用于测试该程序的演示文件。我下载了存档 http://media.ugs.com/teamcenter/jtfiles/NX_TurboCharger.zip 并绑定到加载“145275T010_001_MODEL_SOLIDS.jt”文件。
从文档中,我了解到文件格式如下所示:
.
├── Segment Header
| └── [...]
└── Data
├── Logical Element Header
| ├── ElementLength : I32
| └── ElementHeader
| ├── ObjectType Id : I32
| ├── ObjectBase Type : UChar
| └── ObjectID : I32
└── Object Data
├── Base Node Type
| ├── VersionNumber : I16
| ├── NodeFlags : U32
| ├── AttributeCount
| └── Attributes
├── VersionNumber : I16
问题:
在文档中,逻辑元素标头表示为元素长度+对象数据。这让我很困惑——这是一个递归定义吗?我认为这是一个错误,应该是元素长度+元素标题
似乎基本节点类型以及对象数据部分都有一个
VersionNumber
属性。它是否正确?由于某种原因,我的断言失败了 - 文档说 ObjectData 的版本属性应该始终是 1
。
如果我尝试继续读取数据,例如
// see 7.2.1.1.1.10.1.1 Base Shape Data
BaseShapeData base_shape_data{};
file.read(reinterpret_cast<char *>(&base_shape_data.version_number), sizeof(int16_t) * 1);
//assert(base_shape_data.version_number == 0x0001);
file.read(reinterpret_cast<char *>(&base_shape_data.reserved_field.min_corner.x), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.reserved_field.min_corner.y), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.reserved_field.min_corner.z), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.reserved_field.max_corner.x), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.reserved_field.max_corner.y), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.reserved_field.max_corner.z), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.untransformed_bbox.min_corner.x), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.untransformed_bbox.min_corner.y), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.untransformed_bbox.min_corner.z), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.untransformed_bbox.max_corner.x), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.untransformed_bbox.max_corner.y), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.untransformed_bbox.max_corner.z), sizeof(float) * 1);
file.read(reinterpret_cast<char *>(&base_shape_data.area), sizeof(float) * 1);
std::cout << "Area: " << base_shape_data.area << std::endl;
我只得到边界框和面积值的无意义数据。也许我跳过了一些东西 - 但什么?
很高兴能澄清这些问题。
我对你的问题没有具体的答案,但我建议你看一下 JTAssistant 源代码,特别是 TKJT 部分。它可能对你有帮助。
我最近分叉了它以允许在我的环境中进行编译: https://github.com/pgibertini/oce-jt