diff --git a/src/cadit/occt/convert.cpp b/src/cadit/occt/convert.cpp index a1b5b0e..d18e8de 100644 --- a/src/cadit/occt/convert.cpp +++ b/src/cadit/occt/convert.cpp @@ -14,6 +14,18 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include "custom_progress.h" #include "geometry_iterator.h" @@ -21,6 +33,206 @@ #include "step_tree.h" #include "../../config_structs.h" +namespace +{ +gp_Vec extract_root_translation(const TDF_LabelSequence& labels) +{ + const double tolerance = Precision::Confusion(); + for (Standard_Integer i = 1; i <= labels.Length(); ++i) + { + const TDF_Label& label = labels.Value(i); + const TopLoc_Location location = XCAFDoc_ShapeTool::GetLocation(label); + if (location.IsIdentity()) + { + continue; + } + + const gp_Trsf trsf = location.Transformation(); + const gp_XYZ translation = trsf.TranslationPart(); + if (translation.SquareModulus() > tolerance * tolerance) + { + return gp_Vec(translation); + } + } + + return gp_Vec(0.0, 0.0, 0.0); +} + +void apply_world_translation_to_glb(const std::filesystem::path& glb_path, const gp_Vec& translation) +{ + if (translation.SquareMagnitude() <= Precision::Confusion() * Precision::Confusion()) + { + return; + } + + constexpr std::uint32_t kGlbMagic = 0x46546C67; // 'glTF' + constexpr std::uint32_t kJsonChunkType = 0x4E4F534A; // 'JSON' + + std::ifstream input(glb_path, std::ios::binary); + if (!input) + { + std::cerr << "Failed to open GLB for world translation update: " << glb_path << "\n"; + return; + } + + input.seekg(0, std::ios::end); + const std::streamsize file_size = input.tellg(); + input.seekg(0, std::ios::beg); + + if (file_size < static_cast(sizeof(std::uint32_t) * 3 + sizeof(std::uint32_t) * 2)) + { + std::cerr << "GLB file too small: " << glb_path << "\n"; + return; + } + + std::vector data(static_cast(file_size)); + input.read(data.data(), file_size); + input.close(); + + auto read_u32 = [](const char* ptr) + { + std::uint32_t value; + std::memcpy(&value, ptr, sizeof(std::uint32_t)); + return value; + }; + + const std::uint32_t magic = read_u32(data.data()); + if (magic != kGlbMagic) + { + std::cerr << "Not a GLB file: " << glb_path << "\n"; + return; + } + + const std::size_t json_chunk_header_offset = sizeof(std::uint32_t) * 3; + const std::uint32_t json_length = read_u32(data.data() + json_chunk_header_offset); + const std::uint32_t json_type = read_u32(data.data() + json_chunk_header_offset + sizeof(std::uint32_t)); + + if (json_type != kJsonChunkType) + { + std::cerr << "Unexpected first chunk type in GLB: " << glb_path << "\n"; + return; + } + + const std::size_t json_data_offset = json_chunk_header_offset + sizeof(std::uint32_t) * 2; + if (json_data_offset + json_length > data.size()) + { + std::cerr << "Invalid JSON chunk length in GLB: " << glb_path << "\n"; + return; + } + + const std::string json_text(data.data() + json_data_offset, data.data() + json_data_offset + json_length); + + nlohmann::json document; + try + { + document = nlohmann::json::parse(json_text); + } + catch (const std::exception& ex) + { + std::cerr << "Failed to parse GLB JSON chunk: " << ex.what() << "\n"; + return; + } + + if (!document.contains("scenes") || !document["scenes"].is_array() || document["scenes"].empty()) + { + return; + } + + int default_scene_index = 0; + if (document.contains("scene") && document["scene"].is_number_integer()) + { + default_scene_index = document["scene"].get(); + } + + if (default_scene_index < 0 || default_scene_index >= static_cast(document["scenes"].size())) + { + return; + } + + nlohmann::json& scene = document["scenes"][default_scene_index]; + if (!scene.contains("nodes") || !scene["nodes"].is_array() || scene["nodes"].empty()) + { + return; + } + + if (!document.contains("nodes") || !document["nodes"].is_array()) + { + return; + } + + const nlohmann::json& node_index_value = scene["nodes"].front(); + if (!node_index_value.is_number_integer()) + { + return; + } + + const int world_node_index = node_index_value.get(); + if (world_node_index < 0 || world_node_index >= static_cast(document["nodes"].size())) + { + return; + } + + nlohmann::json& world_node = document["nodes"][world_node_index]; + if (!world_node.contains("translation") || !world_node["translation"].is_array() || world_node["translation"].size() != 3) + { + world_node["translation"] = nlohmann::json::array({0.0, 0.0, 0.0}); + } + + constexpr double epsilon = 1.0e-8; + if (std::abs(world_node["translation"][0].get()) > epsilon || + std::abs(world_node["translation"][1].get()) > epsilon || + std::abs(world_node["translation"][2].get()) > epsilon) + { + return; + } + + world_node["translation"][0] = translation.X(); + world_node["translation"][1] = translation.Y(); + world_node["translation"][2] = translation.Z(); + + std::string updated_json = document.dump(); + const std::size_t padded_json_length = (updated_json.size() + 3) & ~std::size_t(3); + updated_json.resize(padded_json_length, ' '); + + const std::size_t json_chunk_total_size = sizeof(std::uint32_t) * 2 + padded_json_length; + const std::size_t remaining_size = data.size() - (json_data_offset + json_length); + + const std::size_t new_file_size = sizeof(std::uint32_t) * 3 + json_chunk_total_size + remaining_size; + + std::vector output; + output.reserve(new_file_size); + + // Header (magic, version, length) + output.insert(output.end(), data.begin(), data.begin() + sizeof(std::uint32_t) * 3); + + const std::uint32_t new_length = static_cast(new_file_size); + std::memcpy(output.data() + sizeof(std::uint32_t) * 2, &new_length, sizeof(std::uint32_t)); + + // JSON chunk header + const std::uint32_t new_json_length = static_cast(padded_json_length); + const char* json_length_bytes = reinterpret_cast(&new_json_length); + output.insert(output.end(), json_length_bytes, json_length_bytes + sizeof(std::uint32_t)); + + const char* chunk_type_bytes = reinterpret_cast(&kJsonChunkType); + output.insert(output.end(), chunk_type_bytes, chunk_type_bytes + sizeof(std::uint32_t)); + + // JSON chunk data + output.insert(output.end(), updated_json.begin(), updated_json.end()); + + // Remaining chunks (BIN + others) + output.insert(output.end(), data.begin() + json_data_offset + json_length, data.end()); + + std::ofstream out_file(glb_path, std::ios::binary | std::ios::trunc); + if (!out_file) + { + std::cerr << "Failed to open GLB for writing updated translation: " << glb_path << "\n"; + return; + } + + out_file.write(output.data(), static_cast(output.size())); +} +} + void convert_stp_to_glb(const GlobalConfig& config) { // Initialize the STEPCAFControl_Reader @@ -77,6 +289,8 @@ void convert_stp_to_glb(const GlobalConfig& config) shapeTool->GetShapes(labelSeq); std::vector failed_nodes; + const gp_Vec root_translation = extract_root_translation(labelSeq); + std::cout << "Beginning tessellation of shapes: " << labelSeq.Length() << "\n"; start = std::chrono::high_resolution_clock::now(); for (Standard_Integer i = 1; i <= labelSeq.Length(); ++i) @@ -119,6 +333,8 @@ void convert_stp_to_glb(const GlobalConfig& config) { throw std::runtime_error("Error writing GLB file"); } + + apply_world_translation_to_glb(config.glbFile, root_translation); stop = std::chrono::high_resolution_clock::now(); duration = std::chrono::duration(stop - start).count(); std::cout << "Write complete in " << std::fixed << std::setprecision(2) << duration << " seconds" << "\n";