fix: preserve root translation in GLB output

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
sladro 2025-10-22 15:33:38 +08:00
parent 8218502ee8
commit b454f18a02

View File

@ -14,6 +14,18 @@
#include <map>
#include <StepData_StepModel.hxx>
#include <XSControl_WorkSession.hxx>
#include <Precision.hxx>
#include <gp_Vec.hxx>
#include <gp_Trsf.hxx>
#include <TopLoc_Location.hxx>
#include <array>
#include <vector>
#include <fstream>
#include <cstring>
#include <cmath>
#include <iostream>
#include <nlohmann/json.hpp>
#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<std::streamsize>(sizeof(std::uint32_t) * 3 + sizeof(std::uint32_t) * 2))
{
std::cerr << "GLB file too small: " << glb_path << "\n";
return;
}
std::vector<char> data(static_cast<std::size_t>(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<int>();
}
if (default_scene_index < 0 || default_scene_index >= static_cast<int>(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<int>();
if (world_node_index < 0 || world_node_index >= static_cast<int>(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<double>()) > epsilon ||
std::abs(world_node["translation"][1].get<double>()) > epsilon ||
std::abs(world_node["translation"][2].get<double>()) > 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<char> 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<std::uint32_t>(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<std::uint32_t>(padded_json_length);
const char* json_length_bytes = reinterpret_cast<const char*>(&new_json_length);
output.insert(output.end(), json_length_bytes, json_length_bytes + sizeof(std::uint32_t));
const char* chunk_type_bytes = reinterpret_cast<const char*>(&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<std::streamsize>(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<ProcessResult> 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<double>(stop - start).count();
std::cout << "Write complete in " << std::fixed << std::setprecision(2) << duration << " seconds" << "\n";