From acf135a6edf67925dcde87747b740a37df90966a Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 8 Jul 2024 17:40:07 +0500 Subject: [PATCH] Store item_ids in imported meshes and set them on toggling edit mode It will be used later to add more IFC capabilities to EDIT mode. Example - https://imgur.com/a/HAkjoCY --- src/blenderbim/blenderbim/bim/import_ifc.py | 1 + .../bim/module/geometry/operator.py | 1 + src/blenderbim/blenderbim/tool/geometry.py | 34 +++++++++++++++++++ src/blenderbim/test/tool/test_geometry.py | 32 +++++++++++++++++ .../ifcopenshell/util/shape.py | 5 +++ 5 files changed, 73 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 1c7e2a9cbd..597bc7621f 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -1387,6 +1387,7 @@ class IfcImporter: # # 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.vertices.add(num_vertices) mesh.vertices.foreach_set("co", verts) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 2f4ccb4eb7..6c40c03fb9 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1651,6 +1651,7 @@ class OverrideModeSetEdit(bpy.types.Operator): should_sync_changes_first=False, apply_openings=False, ) + tool.Geometry.apply_item_ids_as_vertex_groups(obj) tool.Geometry.dissolve_triangulated_edges(obj) obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data) else: diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 19e7c57550..cf58791a1f 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -37,6 +37,7 @@ import blenderbim.core.system import blenderbim.core.geometry import blenderbim.tool as tool import blenderbim.bim.import_ifc +from collections import defaultdict from math import radians, pi from mathutils import Vector, Matrix from blenderbim.bim.ifc import IfcStore @@ -173,6 +174,39 @@ class Geometry(blenderbim.core.tool.Geometry): bm.free() del obj.data["ios_edges"] + @classmethod + def apply_item_ids_as_vertex_groups(cls, obj: bpy.types.Object) -> None: + """Save mesh-object item_ids as vertex groups in format 'ios_item_id_xxxx'. + + Since ios_item_ids are item ids for original faces (triangulated), + this method should be used before `dissolve_triangulated_edges`.""" + + mesh = obj.data + assert isinstance(mesh, bpy.types.Mesh) + # I guess, they're already applied. + if "ios_item_ids" not in mesh: + return + + # Just to be safe. + if "ios_edges" not in mesh: + raise Exception("Triangulated edges are already dissolved, cannot aply item ids.") + + polygon_verts = np.empty(len(mesh.polygons) * 3, dtype="I") + mesh.polygons.foreach_get("vertices", polygon_verts) + polygon_verts = polygon_verts.reshape(-1, 3) + + ios_item_ids: list[int] = mesh["ios_item_ids"] + vertices_by_item_ids = defaultdict(list[int]) + for i, item_id in enumerate(ios_item_ids): + # .tolist() as VertexGroup.add() is not ready for uints. + vertices_by_item_ids[item_id].extend(polygon_verts[i].tolist()) + + for item_id, verts in vertices_by_item_ids.items(): + vg = obj.vertex_groups.new(name=f"ios_item_id_{item_id}") + vg.add(verts, weight=1.0, type="ADD") + + del mesh["ios_item_ids"] + @classmethod def does_representation_id_exist(cls, representation_id: int) -> bool: try: diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py index f3cfea1c79..2498af227e 100644 --- a/src/blenderbim/test/tool/test_geometry.py +++ b/src/blenderbim/test/tool/test_geometry.py @@ -727,3 +727,35 @@ class TestRemoveRepresentationItemFromShapeAspect(NewFile): subject.remove_representation_items_from_shape_aspect([items[1]], shape_aspect1) representation = shape_aspect1.ShapeRepresentations[0] assert set(representation.Items) == {items[0]} + + +class TestApplyItemIdsAsVertexGroups(NewFile): + def test_run(self): + bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4)) + obj = bpy.data.objects["Cube"] + bpy.ops.object.editmode_toggle() + bpy.ops.mesh.quads_convert_to_tris() + bpy.ops.mesh.duplicate_move() + bpy.ops.object.editmode_toggle() + + mesh = obj.data + assert isinstance(mesh, bpy.types.Mesh) + assert len(mesh.polygons) == 24 + + mesh["ios_edges"] = [] # Method requirement. + ios_item_ids = (95,) * 12 + (45,) * 12 + mesh["ios_item_ids"] = ios_item_ids + unique = tuple(dict.fromkeys(ios_item_ids)) + ios_item_ids_unique = [unique.index(i) for i in ios_item_ids] + + tool.Geometry.apply_item_ids_as_vertex_groups(obj) + + vertex_group_names = [vg.name for vg in obj.vertex_groups] + assert vertex_group_names == [f"ios_item_id_{i}" for i in unique] + + verts = mesh.vertices + for i, polygon in enumerate(mesh.polygons): + for vi in polygon.vertices: + vert = verts[vi] + groups = [g.group for g in vert.groups] + assert groups == [ios_item_ids_unique[i]] diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape.py b/src/ifcopenshell-python/ifcopenshell/util/shape.py index e42097b2a6..bc88e0b06e 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape.py @@ -275,6 +275,11 @@ def get_faces(geometry: ShapeType) -> npt.NDArray[np.int32]: return np.array([[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)]) +def get_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_shape_vertices(shape: ShapeType, geometry: ShapeType) -> npt.NDArray[np.float64]: """Get the shape's vertices as a numpy array