diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py index cbdf92af9e..6eb0a99a9c 100644 --- a/src/bonsai/bonsai/bim/import_ifc.py +++ b/src/bonsai/bonsai/bim/import_ifc.py @@ -589,6 +589,7 @@ class IfcImporter: obj.hide_select = True def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None: + assert isinstance(self.file, ifcopenshell.sql.sqlite) self.geometry_cache = self.file.get_geometry([e.id() for e in elements]) for geometry_id, geometry in self.geometry_cache["geometry"].items(): mesh_name = tool.Loader.get_mesh_name_from_shape(type("Geometry", (), {"id": geometry_id})) @@ -598,21 +599,9 @@ class IfcImporter: mesh["has_cartesian_point_offset"] = False if geometry["faces"]: - num_vertices = len(verts) // 3 - total_faces = len(geometry["faces"]) - loop_start = range(0, total_faces, 3) - num_loops = total_faces // 3 - loop_total = [3] * num_loops - num_vertex_indices = len(geometry["faces"]) - - mesh.vertices.add(num_vertices) - mesh.vertices.foreach_set("co", verts) - mesh.loops.add(num_vertex_indices) - mesh.loops.foreach_set("vertex_index", geometry["faces"]) - mesh.polygons.add(num_loops) - mesh.polygons.foreach_set("loop_start", loop_start) - mesh.polygons.foreach_set("loop_total", loop_total) - mesh.update() + mesh = tool.Loader.create_mesh_from_shape( + mesh=mesh, faces=geometry["faces"].reshape(-1, 3), verts=verts.reshape(-1, 3) + ) else: e = geometry["edges"] v = verts diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 93d7f7d221..80ac7d7b31 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -1848,21 +1848,7 @@ class LoadLinkedProject(bpy.types.Operator): material_index = np.array([(material_to_slot[i] if i != -1 else 0) for i in material_ids], dtype="I") - num_vertices = len(verts) // 3 - total_faces = len(faces) - loop_start = range(0, total_faces, 3) - num_loops = total_faces // 3 - loop_total = [3] * num_loops - num_vertex_indices = len(faces) - - mesh.vertices.add(num_vertices) - mesh.vertices.foreach_set("co", verts) - mesh.loops.add(num_vertex_indices) - mesh.loops.foreach_set("vertex_index", faces) - mesh.polygons.add(num_loops) - mesh.polygons.foreach_set("loop_start", loop_start) - mesh.polygons.foreach_set("loop_total", loop_total) - mesh.polygons.foreach_set("use_smooth", [0] * total_faces) + mesh = tool.Loader.create_mesh_from_shape(geometry, mesh) mesh.polygons.foreach_set("material_index", material_index) mesh.update() @@ -1890,29 +1876,13 @@ class LoadLinkedProject(bpy.types.Operator): num_vertices = len(verts) // 3 if not num_vertices: return - total_faces = len(faces) - loop_start = range(0, total_faces, 3) - num_loops = total_faces // 3 - loop_total = [3] * num_loops - num_vertex_indices = len(faces) - mesh = bpy.data.meshes.new("Mesh") + mesh = tool.Loader.create_mesh_from_shape(verts=verts.reshape(-1, 3), faces=faces.reshape(-1, 3)) for material in materials: mesh.materials.append(material) - - mesh.vertices.add(num_vertices) - mesh.vertices.foreach_set("co", verts) - mesh.loops.add(num_vertex_indices) - mesh.loops.foreach_set("vertex_index", faces) - mesh.polygons.add(num_loops) - mesh.polygons.foreach_set("loop_start", loop_start) - mesh.polygons.foreach_set("loop_total", loop_total) - mesh.polygons.foreach_set("use_smooth", [0] * total_faces) - if material_ids.size > 0 and len(mesh.polygons) == len(material_ids): mesh.polygons.foreach_set("material_index", material_ids) - mesh.update() obj = bpy.data.objects.new("Chunk", mesh) diff --git a/src/bonsai/bonsai/bim/module/qto/calculator.py b/src/bonsai/bonsai/bim/module/qto/calculator.py index 57e023f20e..e4ba0580a7 100644 --- a/src/bonsai/bonsai/bim/module/qto/calculator.py +++ b/src/bonsai/bonsai/bim/module/qto/calculator.py @@ -1188,27 +1188,7 @@ def create_mesh_from_shape( settings.set("keep-bounding-boxes", True) shape = ifcopenshell.geom.create_shape(settings, element) geometry = shape.geometry if element.is_a("IfcRoot") else shape - faces = geometry.faces - verts = geometry.verts - - mesh = bpy.data.meshes.new("myBeautifulMesh") - - num_vertices = len(verts) // 3 - total_faces = len(faces) - loop_start = range(0, total_faces, 3) - num_loops = total_faces // 3 - loop_total = [3] * num_loops - num_vertex_indices = len(faces) - - mesh.vertices.add(num_vertices) - mesh.vertices.foreach_set("co", verts) - mesh.loops.add(num_vertex_indices) - mesh.loops.foreach_set("vertex_index", faces) - mesh.polygons.add(num_loops) - mesh.polygons.foreach_set("loop_start", loop_start) - mesh.polygons.foreach_set("loop_total", loop_total) - mesh.update() - return mesh + return tool.Loader.create_mesh_from_shape(geometry) def get_bmesh_from_mesh(mesh: bpy.types.Mesh) -> bmesh.types.BMesh: diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index ee2e2103ea..8e44f29c94 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -972,10 +972,7 @@ class Loader(bonsai.core.tool.Loader): if verts is None: verts = ifcopenshell.util.shape.get_vertices(geometry) faces = ifcopenshell.util.shape.get_faces(geometry) - total_faces: int - if total_faces := faces.shape[0]: - num_vertices: int = verts.shape[0] - + if faces.shape[0] > 0: # See bug 3546 # ios_edges holds true edges that aren't triangulated. # @@ -984,38 +981,7 @@ class Loader(bonsai.core.tool.Loader): ios_item_ids = ifcopenshell.util.shape.get_faces_representation_item_ids(geometry).tolist() mesh["ios_item_ids"] = ios_item_ids - mesh.vertices.add(num_vertices) - mesh.vertices.foreach_set("co", verts.ravel().astype("f")) - - is_triangulated = True - num_vertex_indices = faces.size - if is_triangulated: - loop_start = np.arange(0, num_vertex_indices, 3, dtype="I") - loop_total = np.full(total_faces, 3, dtype="I") - use_smooth = np.zeros(num_vertex_indices, dtype="?") - - mesh.loops.add(num_vertex_indices) - mesh.loops.foreach_set("vertex_index", faces.ravel().astype("I")) - mesh.polygons.add(total_faces) - mesh.polygons.foreach_set("loop_start", loop_start) - mesh.polygons.foreach_set("loop_total", loop_total) - mesh.polygons.foreach_set("use_smooth", use_smooth) - else: - # TODO: optimize using correct numpy array types. - faces_array = np.array(geometry.faces, dtype=object) - loop_total = np.array(tuple(len(face) for face in faces_array), dtype="I") - loop_start = np.cumsum((0,) + loop_total)[:-1] - vertex_index = np.concatenate(faces_array) - use_smooth = np.zeros(num_vertex_indices, dtype="?") - - mesh.loops.add(len(vertex_index)) - mesh.loops.foreach_set("vertex_index", vertex_index) - mesh.polygons.add(len(loop_start)) - mesh.polygons.foreach_set("loop_start", loop_start) - mesh.polygons.foreach_set("loop_total", loop_total) - mesh.polygons.foreach_set("use_smooth", use_smooth) - - mesh.update() + mesh = tool.Loader.create_mesh_from_shape(mesh=mesh, verts=verts, faces=faces) rep_str: str = geometry.id if load_indexed_maps and "openings" not in rep_str: @@ -1043,6 +1009,72 @@ class Loader(bonsai.core.tool.Loader): mesh["ios_material_ids"] = ifcopenshell.util.shape.get_faces_material_style_ids(geometry).tolist() return mesh + @classmethod + def create_mesh_from_shape( + cls, + geometry: Optional[ifcopenshell.geom.ShapeType] = None, + mesh: Optional[bpy.types.Mesh] = None, + *, + verts: Optional[npt.NDArray[np.float64]] = None, + faces: Optional[npt.NDArray[np.int32]] = None, + ) -> bpy.types.Mesh: + """ + Either geometry or verts+faces should be provided. + + :param verts: Numpy array of shape (n, 3). + :param faces: Numpy array of shape (m, 3). + """ + assert geometry is not None or (verts is not None and faces is not None), ( + "Either geometry or verts+faces should be provided.\n" + f"Current geometry: {geometry}\n" + f"Current verts: {verts}\n" + f"Current faces: {faces}" + ) + + if mesh is None: + mesh = bpy.data.meshes.new("temp") + + if verts is None or faces is None: + verts = ifcopenshell.util.shape.get_vertices(geometry) + faces = ifcopenshell.util.shape.get_faces(geometry) + + total_faces: int = faces.shape[0] + num_vertices: int = verts.shape[0] + mesh.vertices.add(num_vertices) + mesh.vertices.foreach_set("co", verts.ravel().astype("f")) + + is_triangulated = True + num_vertex_indices = faces.size + if is_triangulated: + loop_start = np.arange(0, num_vertex_indices, 3, dtype="I") + loop_total = np.full(total_faces, 3, dtype="I") + use_smooth = np.zeros(num_vertex_indices, dtype="?") + + mesh.loops.add(num_vertex_indices) + mesh.loops.foreach_set("vertex_index", faces.ravel().astype("I")) + mesh.polygons.add(total_faces) + mesh.polygons.foreach_set("loop_start", loop_start) + mesh.polygons.foreach_set("loop_total", loop_total) + mesh.polygons.foreach_set("use_smooth", use_smooth) + else: + # TODO: optimize using correct numpy array types. + faces_array = np.array(geometry.faces, dtype=object) + loop_total = np.array(tuple(len(face) for face in faces_array), dtype="I") + loop_start = np.cumsum((0,) + loop_total)[:-1] + vertex_index = np.concatenate(faces_array) + use_smooth = np.zeros(num_vertex_indices, dtype="?") + + mesh.loops.add(len(vertex_index)) + mesh.loops.foreach_set("vertex_index", vertex_index) + mesh.polygons.add(len(loop_start)) + mesh.polygons.foreach_set("loop_start", loop_start) + mesh.polygons.foreach_set("loop_total", loop_total) + mesh.polygons.foreach_set("use_smooth", use_smooth) + + mesh.update() + + return mesh + @classmethod def setup_active_bsdd_classification(cls) -> None: ifc_file = tool.Ifc.get() diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index ecdc22233f..f2c784c464 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -758,26 +758,7 @@ class Spatial(bonsai.core.tool.Spatial): @classmethod def create_mesh_from_shape(cls, shape: ifcopenshell.geom.ShapeElementType) -> bpy.types.Mesh: geometry = shape.geometry - mesh = bpy.data.meshes.new("tmp") - verts = geometry.verts - if geometry.faces: - num_vertices = len(verts) // 3 - total_faces = len(geometry.faces) - loop_start = range(0, total_faces, 3) - num_loops = total_faces // 3 - loop_total = [3] * num_loops - num_vertex_indices = len(geometry.faces) - - mesh.vertices.add(num_vertices) - mesh.vertices.foreach_set("co", verts) - mesh.loops.add(num_vertex_indices) - mesh.loops.foreach_set("vertex_index", geometry.faces) - mesh.polygons.add(num_loops) - mesh.polygons.foreach_set("loop_start", loop_start) - mesh.polygons.foreach_set("loop_total", loop_total) - mesh.polygons.foreach_set("use_smooth", [0] * total_faces) - mesh.update() - return mesh + return tool.Loader.create_mesh_from_shape(geometry) @classmethod def get_x_y_z_h_mat_from_obj(cls, obj: bpy.types.Object) -> tuple[float, float, float, float, Matrix]: