From d6daa64c48d3e1e771bc7ef3971dbd0041673449 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Mon, 17 Nov 2025 14:05:59 +0100 Subject: [PATCH] Support --element-hierarchy in .glb output --- src/ifcconvert/IfcConvert.cpp | 4 +- src/ifcgeom/Iterator.cpp | 20 +++-- src/serializers/GltfSerializer.cpp | 127 +++++++++++++++++++++++------ src/serializers/GltfSerializer.h | 2 + 4 files changed, 118 insertions(+), 35 deletions(-) diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 4f19d31e17..44f5affb14 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -867,8 +867,8 @@ int main(int argc, char** argv) { geometry_settings.get().value = true; } - if (geometry_settings.get().get() && output_extension != DAE && output_extension != USD && output_extension != USDA && output_extension != USDC) { - cerr_ << "[Error] --use-element-hierarchy can be used only with .dae or .usd output.\n"; + if (geometry_settings.get().get() && output_extension != DAE && output_extension != USD && output_extension != USDA && output_extension != USDC && output_extension != GLB) { + cerr_ << "[Error] --use-element-hierarchy can be used only with .dae or .usd or .glb output.\n"; /// @todo Lots of duplicate error-and-exit code. write_log(!quiet); print_usage(); diff --git a/src/ifcgeom/Iterator.cpp b/src/ifcgeom/Iterator.cpp index 2ea32947a3..a7f700f4de 100644 --- a/src/ifcgeom/Iterator.cpp +++ b/src/ifcgeom/Iterator.cpp @@ -564,13 +564,19 @@ IfcGeom::Element* IfcGeom::Iterator::get() // We need to find all the parents while (parent_object != NULL && hasParent && parent_object->parent_id() != -1) { // Find the next parent - try { - parent_object = get_object(parent_object->parent_id()); - } catch (const std::exception& e) { - Logger::Error(e); - hasParent = false; - } - + auto pid = parent_object->parent_id(); + auto ifc_product = ifc_file->instance_by_id(pid)->as(); + if (ifc_product->declaration().name() == "IfcProject") { + hasParent = false; + } else { + try { + parent_object = get_object(pid); + } catch (const std::exception& e) { + Logger::Error(e); + hasParent = false; + } + } + // Add the previously found parent to the vector if (hasParent) parents.insert(parents.begin(), parent_object); diff --git a/src/serializers/GltfSerializer.cpp b/src/serializers/GltfSerializer.cpp index 32844c67ca..12627e0b39 100644 --- a/src/serializers/GltfSerializer.cpp +++ b/src/serializers/GltfSerializer.cpp @@ -180,35 +180,108 @@ void GltfSerializer::write(const IfcGeom::TriangulationElement* o) { return; } - node_array_.push_back(json_["nodes"].size()); + size_t current_node_index = json_["nodes"].size(); + auto current_leaf_index = current_node_index; + json_["nodes"].emplace_back(); + node_indices_[o->product()] = current_node_index; + node_array_.push_back(current_node_index); - const auto& m = o->transformation().data()->ccomponents(); - - // @todo check - std::array matrix_flat; - if (settings_.get().get()) { - matrix_flat = { - m(0,0), m(1,0), m(2,0), m(3,0), - m(0,1), m(1,1), m(2,1), m(3,1), - m(0,2), m(1,2), m(2,2), m(3,2), - m(0,3), m(1,3), m(2,3), m(3,3) - }; - } else { - // nb: note that this contains the Y-UP transform as well. - matrix_flat = { - m(0,0), m(2,0), -m(1,0), m(3,0), - m(0,1), m(2,1), -m(1,1), m(3,1), - m(0,2), m(2,2), -m(1,2), m(3,2), - m(0,3), m(2,3), -m(1,3), m(3,3) - }; + auto m = o->transformation().data()->ccomponents(); + + if (o->parents().empty()) { + roots_.push_back(current_node_index); } + if (!o->parents().empty()) { + // apply inverse of last parent -> overwrite product transform (m) + m = o->parents().back()->transformation().data()->ccomponents().inverse() * m; + + for (auto it = o->parents().rbegin(); it != o->parents().rend(); ++it) { + const auto jt = it + 1; + const bool is_root = jt == o->parents().rend(); + + auto kt = node_indices_.find((*it)->product()); + if (kt != node_indices_.end()) { + // parent already processed as part of other parent sequence + json_["nodes"][kt->second]["children"].push_back(current_node_index); + break; + } + + + auto mm = (*it)->transformation().data()->ccomponents(); + if (!is_root) { + mm = (*jt)->transformation().data()->ccomponents().inverse() * mm; + } + + json parent_node = json::object(); + + std::array matrix_flat; + if (settings_.get().get() || !is_root) { + // y-up transform is only accounted for on root + matrix_flat = { + mm(0,0), mm(1,0), mm(2,0), mm(3,0), + mm(0,1), mm(1,1), mm(2,1), mm(3,1), + mm(0,2), mm(1,2), mm(2,2), mm(3,2), + mm(0,3), mm(1,3), mm(2,3), mm(3,3) + }; + } else { + // nb: note that this contains the Y-UP transform. + matrix_flat = { + mm(0,0), mm(2,0), -mm(1,0), mm(3,0), + mm(0,1), mm(2,1), -mm(1,1), mm(3,1), + mm(0,2), mm(2,2), -mm(1,2), mm(3,2), + mm(0,3), mm(2,3), -mm(1,3), mm(3,3) + }; + } - static const std::array identity_matrix = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1}; + static const std::array identity_matrix = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1}; + + if (matrix_flat != identity_matrix) { + // glTF validator complains about identity matrices + parent_node["matrix"] = matrix_flat; + } + + size_t new_node_index = json_["nodes"].size(); + node_indices_[(*it)->product()] = new_node_index; + node_array_.push_back(new_node_index); + parent_node["name"] = object_id(o); + parent_node["children"] = json::array({current_node_index}); + json_["nodes"].push_back(parent_node); + + current_node_index = new_node_index; + + if (is_root) { + roots_.push_back(current_node_index); + } + } + } json node; - if (matrix_flat != identity_matrix) { - // glTF validator complains about identity matrices - node["matrix"] = matrix_flat; + { + std::array matrix_flat; + if (settings_.get().get() || !o->parents().empty()) { + // y-up transform is only accounted for on root + matrix_flat = { + m(0,0), m(1,0), m(2,0), m(3,0), + m(0,1), m(1,1), m(2,1), m(3,1), + m(0,2), m(1,2), m(2,2), m(3,2), + m(0,3), m(1,3), m(2,3), m(3,3) + }; + } else { + // nb: note that this contains the Y-UP transform. + matrix_flat = { + m(0,0), m(2,0), -m(1,0), m(3,0), + m(0,1), m(2,1), -m(1,1), m(3,1), + m(0,2), m(2,2), -m(1,2), m(3,2), + m(0,3), m(2,3), -m(1,3), m(3,3) + }; + } + + static const std::array identity_matrix = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1}; + + if (matrix_flat != identity_matrix) { + // glTF validator complains about identity matrices + node["matrix"] = matrix_flat; + } } node["name"] = object_id(o); @@ -300,7 +373,7 @@ void GltfSerializer::write(const IfcGeom::TriangulationElement* o) { } node["mesh"] = current_mesh_index; - json_["nodes"].push_back(node); + json_["nodes"][current_leaf_index] = node; } template @@ -372,7 +445,9 @@ void GltfSerializer::finalize() { } json scene_0; - if (north_rotation_ || ecef_transform_) { + if (geometry_settings().get().get()) { + scene_0["nodes"] = roots_; + } else if (north_rotation_ || ecef_transform_) { scene_0["nodes"] = std::array{json_["nodes"].size() - 1}; } else { scene_0["nodes"] = node_array_; diff --git a/src/serializers/GltfSerializer.h b/src/serializers/GltfSerializer.h index d2c6edab89..e08f2b55f1 100644 --- a/src/serializers/GltfSerializer.h +++ b/src/serializers/GltfSerializer.h @@ -38,6 +38,8 @@ private: json json_, node_array_; boost::optional ecef_transform_, north_rotation_; int bufferViewId; + std::map node_indices_; + std::vector roots_; int writeMaterial(const ifcopenshell::geometry::taxonomy::style::ptr style); public: