From dd9de98290746ea5bcab7f73e68193a581dd559d Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 25 Sep 2024 15:15:47 +0500 Subject: [PATCH] Store edges representation item ids in Triangulation --- src/bonsai/bonsai/tool/loader.py | 2 +- src/ifcgeom/IfcGeomRepresentation.h | 10 ++- .../OpenCascadeConversionResult.cpp | 4 +- .../ifcopenshell/util/shape.py | 11 ++- .../test/test_create_shape.py | 83 ++++++++++++++++++- src/ifcwrap/IfcGeomWrapper.i | 5 ++ src/serializers/HdfSerializer.cpp | 3 + src/serializers/HdfSerializer.h | 1 + 8 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index 3bf6567f1c..061cc7d31f 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -915,7 +915,7 @@ class Loader(bonsai.core.tool.Loader): # # we do `.tolist()` because Blender can't assign `np.int32` to it's custom attributes mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry).tolist())) - mesh["ios_item_ids"] = ifcopenshell.util.shape.get_representation_item_ids(geometry).tolist() + mesh["ios_item_ids"] = ifcopenshell.util.shape.get_faces_representation_item_ids(geometry).tolist() mesh.vertices.add(num_vertices) mesh.vertices.foreach_set("co", verts) diff --git a/src/ifcgeom/IfcGeomRepresentation.h b/src/ifcgeom/IfcGeomRepresentation.h index a58bca380c..23c3398c96 100644 --- a/src/ifcgeom/IfcGeomRepresentation.h +++ b/src/ifcgeom/IfcGeomRepresentation.h @@ -117,6 +117,7 @@ namespace IfcGeom { std::vector material_ids_; std::vector materials_; std::vector item_ids_; + std::vector edges_item_ids_; size_t weld_offset_; VertexKeyMap welds; @@ -137,6 +138,7 @@ namespace IfcGeom { const std::vector& material_ids() const { return material_ids_; } const std::vector& materials() const { return materials_; } const std::vector& item_ids() const { return item_ids_; } + const std::vector& edges_item_ids() const { return edges_item_ids_; } Triangulation(const BRep& shape_model); @@ -152,6 +154,7 @@ namespace IfcGeom { const std::vector& material_ids, const std::vector& materials, const std::vector& item_ids + , const std::vector& edges_item_ids ) : Representation(settings, entity, id) , verts_(verts) @@ -162,6 +165,7 @@ namespace IfcGeom { , material_ids_(material_ids) , materials_(materials) , item_ids_(item_ids) + , edges_item_ids_(edges_item_ids) {} virtual ~Triangulation() {} @@ -204,16 +208,18 @@ namespace IfcGeom { material_ids_.push_back(style); } - void addEdge(int style, int i0, int i1) { + void addEdge(int item_id, int style, int i0, int i1) { edges_.push_back(i0); edges_.push_back(i1); material_ids_.push_back(style); + edges_item_ids_.push_back(item_id); } - void registerEdge(int i0, int i1) { + void registerEdge(int item_id, int i0, int i1) { edges_.push_back(i0); edges_.push_back(i1); + edges_item_ids_.push_back(item_id); } void registerEdgeCount(int n1, int n2, std::map, int>& edgecount); diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp index a3f2ae7609..d5e6e2c61a 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp @@ -303,7 +303,7 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr // @todo should be != 2? if (p.second == 1 && emitted_edges.find(p.first) == emitted_edges.end()) { // non manifold edge, face boundary - t->registerEdge(p.first.first, p.first.second); + t->registerEdge(item_id, p.first.first, p.first.second); if (settings.get().get()) { // only relevant while welding, because otherwise vertices are not shared among distinct faces emitted_edges.insert(p.first); @@ -386,7 +386,7 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr } for (auto& sgmt : segments) { - t->addEdge(surface_style_id, sgmt.first, sgmt.second); + t->addEdge(item_id, surface_style_id, sgmt.first, sgmt.second); } previous = current; diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape.py b/src/ifcopenshell-python/ifcopenshell/util/shape.py index 1fa0586e22..4a92d6c79f 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape.py @@ -300,11 +300,20 @@ def get_normals(geometry: ShapeType) -> npt.NDArray[np.float64]: return np.frombuffer(geometry.normals_buffer, dtype="d").reshape(-1, 3) -def get_representation_item_ids(geometry: ShapeType) -> npt.NDArray[np.int32]: +def get_faces_representation_item_ids(geometry: ShapeType) -> npt.NDArray[np.int32]: """Get representation item ids for the geometry faces.""" return np.frombuffer(geometry.item_ids_buffer, dtype="i") +def get_edges_representation_item_ids(geometry: ShapeType) -> npt.NDArray[np.int32]: + """Get representation item ids for the geometry edges. + + Can be useful for geometry without faces and in general is more universal + since it's possible that geometry will have elements with and without faces. + """ + return np.frombuffer(geometry.edges_item_ids_buffer, dtype="i") + + def get_shape_vertices(shape: ShapeType, geometry: ShapeType) -> npt.NDArray[np.float64]: """Get the shape's vertices as a numpy array diff --git a/src/ifcopenshell-python/test/test_create_shape.py b/src/ifcopenshell-python/test/test_create_shape.py index 36597a83fe..ad91dbbbf1 100644 --- a/src/ifcopenshell-python/test/test_create_shape.py +++ b/src/ifcopenshell-python/test/test_create_shape.py @@ -1,11 +1,15 @@ import pytest +import test.bootstrap import ifcopenshell -import ifcopenshell.api.unit -import ifcopenshell.api.root import ifcopenshell.api.context -import ifcopenshell.api.project -import ifcopenshell.geom import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.unit +import ifcopenshell.geom +import ifcopenshell.ifcopenshell_wrapper as W +import ifcopenshell.util.shape +from ifcopenshell.util.shape_builder import ShapeBuilder, V from typing import get_args @@ -47,6 +51,77 @@ class TestGeomSettings: assert "USE_PYTHON_OPENCASCADE" not in repr(settings) +class TestTriangulationAttributes(test.bootstrap.IFC4): + def test_faces_representation_item_ids(self): + ifc_file = ifcopenshell.file() + ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcProject", name="Test") + context = ifcopenshell.api.context.add_context(ifc_file, context_type="Model") + + builder = ShapeBuilder(ifc_file) + extrusion = builder.extrude(builder.rectangle(), magnitude=1.0) + representation = builder.get_representation(context, extrusion) + settings = ifcopenshell.geom.settings() + shape = ifcopenshell.geom.create_shape(settings, representation) + faces_item_ids = ifcopenshell.util.shape.get_faces_representation_item_ids(shape) + faces = ifcopenshell.util.shape.get_faces(shape) + assert set(faces_item_ids) == {extrusion.id()} + assert len(faces) == 12 # Cube has 12 tris. + assert len(faces_item_ids) == len(faces) + + edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(shape) + edges = ifcopenshell.util.shape.get_edges(shape) + assert set(edges_item_ids) == {extrusion.id()} + assert len(edges) == 12 # Cube has 12 edges. + assert len(edges_item_ids) == len(edges) + + def test_curve_representation_item_ids(self): + ifc_file = ifcopenshell.file() + ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcProject", name="Test") + context = ifcopenshell.api.context.add_context(ifc_file, context_type="Model") + + builder = ShapeBuilder(ifc_file) + curve = builder.rectangle() + representation = builder.get_representation(context, curve) + settings = ifcopenshell.geom.settings() + settings.set("dimensionality", W.CURVES_SURFACES_AND_SOLIDS) + shape = ifcopenshell.geom.create_shape(settings, representation) + + faces_item_ids = ifcopenshell.util.shape.get_faces_representation_item_ids(shape) + assert len(faces_item_ids) == 0 + + edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(shape) + edges = ifcopenshell.util.shape.get_edges(shape) + assert set(edges_item_ids) == {curve.id()} + assert len(edges) == 4 + assert len(edges_item_ids) == len(edges) + + def test_mixed_representation_item_ids(self): + ifc_file = ifcopenshell.file() + ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcProject", name="Test") + context = ifcopenshell.api.context.add_context(ifc_file, context_type="Model") + + builder = ShapeBuilder(ifc_file) + curve = builder.rectangle() + + fill = ifc_file.create_entity("IfcAnnotationFillArea", builder.rectangle()) + representation = builder.get_representation(context, (curve, fill)) + settings = ifcopenshell.geom.settings() + settings.set("dimensionality", W.CURVES_SURFACES_AND_SOLIDS) + shape = ifcopenshell.geom.create_shape(settings, representation) + + faces_item_ids = ifcopenshell.util.shape.get_faces_representation_item_ids(shape) + faces = ifcopenshell.util.shape.get_faces(shape) + assert len(faces) == 2 # Fill area will produce a triangulated face. + assert set(faces_item_ids) == {fill.id()} + assert len(faces_item_ids) == len(faces) + + edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(shape) + edges = ifcopenshell.util.shape.get_edges(shape) + assert set(edges_item_ids) == {fill.id(), curve.id()} + assert len(edges) == 8 # 4 edges rectangle curve + 4 edges fill area + assert len(edges_item_ids) == len(edges) + + class TestAssignObject: def test_no_welding_on_distinct_items(self): self.file = ifcopenshell.api.project.create_file() diff --git a/src/ifcwrap/IfcGeomWrapper.i b/src/ifcwrap/IfcGeomWrapper.i index b953d8845a..9b1ab46395 100644 --- a/src/ifcwrap/IfcGeomWrapper.i +++ b/src/ifcwrap/IfcGeomWrapper.i @@ -647,6 +647,10 @@ struct ShapeRTTI : public boost::static_visitor return vector_to_buffer(self->item_ids()); } + std::pair edges_item_ids_buffer() const { + return vector_to_buffer(self->edges_item_ids()); + } + std::pair verts_buffer() const { return vector_to_buffer(self->verts()); } @@ -703,6 +707,7 @@ struct ShapeRTTI : public boost::static_visitor edges_buffer = property(edges_buffer) material_ids_buffer = property(material_ids_buffer) item_ids_buffer = property(item_ids_buffer) + edges_item_ids_buffer = property(edges_item_ids_buffer) verts_buffer = property(verts_buffer) normals_buffer = property(normals_buffer) colors_buffer = property(colors_buffer) diff --git a/src/serializers/HdfSerializer.cpp b/src/serializers/HdfSerializer.cpp index a6388632bb..50b9be6e62 100644 --- a/src/serializers/HdfSerializer.cpp +++ b/src/serializers/HdfSerializer.cpp @@ -398,6 +398,7 @@ IfcGeom::Element* HdfSerializer::read(IfcParse::IfcFile& f, const std::string& g auto uvcoords = read_dataset(meshGroup, DATASET_NAME_UVCOORDS); auto material_ids = read_dataset(meshGroup, DATASET_NAME_MATERIAL_IDS); auto item_ids = read_dataset(meshGroup, DATASET_NAME_ITEM_IDS); + auto edges_item_ids = read_dataset(meshGroup, DATASET_NAME_EDGES_ITEM_IDS); std::vector surface_styles; @@ -435,6 +436,7 @@ IfcGeom::Element* HdfSerializer::read(IfcParse::IfcFile& f, const std::string& g material_ids, surface_style_ptrs, item_ids + , edges_item_ids )); triangulation_cache_.insert({ representation_id_str, triangulation_geometry }); @@ -696,6 +698,7 @@ const H5std_string HdfSerializer::DATASET_NAME_INDICES = "indices"; const H5std_string HdfSerializer::DATASET_NAME_EDGES = "edges"; const H5std_string HdfSerializer::DATASET_NAME_MATERIAL_IDS = "material_ids"; const H5std_string HdfSerializer::DATASET_NAME_ITEM_IDS = "item_ids"; +const H5std_string HdfSerializer::DATASET_NAME_EDGES_ITEM_IDS = "edges_item_ids"; const H5std_string HdfSerializer::DATASET_NAME_MATERIALS = "materials"; const H5std_string HdfSerializer::DATASET_NAME_OCCT = "brep"; const H5std_string HdfSerializer::DATASET_NAME_PLACEMENT = "placement"; diff --git a/src/serializers/HdfSerializer.h b/src/serializers/HdfSerializer.h index 0326300420..d5846337f6 100644 --- a/src/serializers/HdfSerializer.h +++ b/src/serializers/HdfSerializer.h @@ -47,6 +47,7 @@ private: static const H5std_string DATASET_NAME_EDGES; static const H5std_string DATASET_NAME_MATERIAL_IDS; static const H5std_string DATASET_NAME_ITEM_IDS; + static const H5std_string DATASET_NAME_EDGES_ITEM_IDS; static const H5std_string DATASET_NAME_MATERIALS; static const H5std_string DATASET_NAME_OCCT; static const H5std_string DATASET_NAME_PLACEMENT;