I want to implement a JT file reader in C++. The JT File Format Reference documents the file format. I came up with the following "minimal" example (note: I am only interested in the vertex and face data if available):
#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
}
}
}
}
A demo file to test this program can be downloaded from here. I downloaded the archive http://media.ugs.com/teamcenter/jtfiles/NX_TurboCharger.zip and tied to load the "145275T010_001_MODEL_SOLIDS.jt" file.
From the documentation, I understand that the file format looks like this:
.
├── 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
Questions:
In the documentation Logical Element Header is represented as Element Length + Object Data. This confuses me - is this a recursive definition? I think it is a mistake and should be Element Lenght + Element Header
It seems the the Base Node Type as well as the Object Data section has a
VersionNumberattribute. Is this correct? For some reason my assertion fails - the documentation says that the version attribute of the ObjectData should always be1.If I try to continue with reading the data e.g.
// 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;
I get only non sense data for the bounding box and area value. Maybe I skipped something - but what?
Would be happy for the clarification of these questions.
I do not have a specific answer for your question, but I recommend you to take a look at JTAssistant source code, especially TKJT part. It may help you.
I forked it recently to allow compilation in my environment: https://github.com/pgibertini/oce-jt