How to read a JT file?

88 views Asked by At

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:

  1. 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

  2. It seems the the Base Node Type as well as the Object Data section has a VersionNumber attribute. Is this correct? For some reason my assertion fails - the documentation says that the version attribute of the ObjectData should always be 1.

  3. 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.

1

There are 1 answers

0
Pierre Gibertini On

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