diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py index c080c79e73..403327ef7a 100644 --- a/src/bonsai/bonsai/bim/import_ifc.py +++ b/src/bonsai/bonsai/bim/import_ifc.py @@ -237,8 +237,6 @@ class IfcImporter: self.profile_code("Process context filter") self.calculate_model_offset() self.profile_code("Calculate model offset") - self.predict_dense_mesh() - self.profile_code("Predict dense mesh") self.set_units() self.profile_code("Set units") self.create_project() @@ -426,28 +424,6 @@ class IfcImporter: if not self.ifc_import_settings.should_use_native_meshes: return False # Performance improvements only occur on edge cases currently - # FacetedBreps (without voids) are meshes. See #841. - if self.is_native_faceted_brep(resolved_representation): - self.native_data[element.GlobalId] = { - "matrix": matrix, - "context": context, - "geometry_id": representation_id, - "representation": resolved_representation, - "type": "IfcFacetedBrep", - } - return True - - if self.is_native_face_based_surface_model(resolved_representation): - self.native_data[element.GlobalId] = { - "matrix": matrix, - "context": context, - "geometry_id": representation_id, - "representation": resolved_representation, - "type": "IfcFaceBasedSurfaceModel", - } - return True - return False - def is_native_swept_disk_solid( self, element: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance ) -> bool: @@ -466,55 +442,6 @@ class IfcImporter: return True return False - def is_native_faceted_brep(self, representation: ifcopenshell.entity_instance) -> bool: - # TODO handle mapped items - for i in representation.Items: - if i.is_a() != "IfcFacetedBrep": - return False - return True - - def is_native_face_based_surface_model(self, representation: ifcopenshell.entity_instance) -> bool: - for i in representation.Items: - if i.is_a() != "IfcFaceBasedSurfaceModel": - return False - return True - - def get_products_from_shape_representation(self, element: ifcopenshell.entity_instance) -> None: - products = [pr.ShapeOfProduct[0] for pr in element.OfProductRepresentation] - for rep_map in element.RepresentationMap: - for usage in rep_map.MapUsage: - for inverse_element in self.file.get_inverse(usage): - if inverse_element.is_a("IfcShapeRepresentation"): - products.extend(self.get_products_from_shape_representation(inverse_element)) - return products - - def predict_dense_mesh(self) -> None: - if self.ifc_import_settings.should_use_native_meshes: - return - - threshold = 10000 # Just from experience. - - # The check for CfsFaces/Faces/CoordIndex accommodates invalid data from Cadwork - # 0 IfcClosedShell.CfsFaces - faces = [len(faces) for e in self.file.by_type("IfcClosedShell") if (faces := e[0])] - if faces and max(faces) > threshold: - self.ifc_import_settings.should_use_native_meshes = True - return - - if self.file.schema == "IFC2X3": - return - - # 2 IfcPolygonalFaceSet.Faces - faces = [len(faces) for e in self.file.by_type("IfcPolygonalFaceSet") if (faces := e[2])] - if faces and max(faces) > threshold: - self.ifc_import_settings.should_use_native_meshes = True - return - - # 3 IfcTriangulatedFaceSet.CoordIndex - faces = [len(index) for e in self.file.by_type("IfcTriangulatedFaceSet") if (index := e[3])] - if faces and max(faces) > threshold: - self.ifc_import_settings.should_use_native_meshes = True - def calculate_model_offset(self) -> None: props = bpy.context.scene.BIMGeoreferenceProperties if self.ifc_import_settings.false_origin_mode == "MANUAL": @@ -621,10 +548,6 @@ class IfcImporter: if mesh is None: if native_data["type"] == "IfcSweptDiskSolid": mesh = self.create_native_swept_disk_solid(element, mesh_name, native_data) - elif native_data["type"] == "IfcFacetedBrep": - mesh = self.create_native_faceted_brep(element, mesh_name, native_data) - elif native_data["type"] == "IfcFaceBasedSurfaceModel": - mesh = self.create_native_faceted_brep(element, mesh_name, native_data) tool.Ifc.link(tool.Ifc.get().by_id(native_data["geometry_id"]), mesh) mesh.name = mesh_name self.meshes[mesh_name] = mesh @@ -923,180 +846,6 @@ class IfcImporter: def load_existing_meshes(self) -> None: self.meshes.update({m.name: m for m in bpy.data.meshes}) - def get_representation_item_material_name(self, item): - if not item.StyledByItem: - return - styles = list(item.StyledByItem[0].Styles) - while styles: - style = styles.pop() - if style.is_a("IfcSurfaceStyle"): - return style.id() - elif style.is_a("IfcPresentationStyleAssignment"): - styles.extend(style.Styles) - - def create_native_faceted_brep(self, element, mesh_name, native_data): - # co [x y z x y z x y z ...] - # vertex_index [i i i i i ...] - # loop_start [0 3 6 9 ...] (for tris) - # loop_total [3 3 3 3 ...] (for tris) - self.mesh_data = { - "co": [], - "vertex_index": [], - "loop_start": [], - "loop_total": [], - "total_verts": 0, - "total_polygons": 0, - "materials": [], - "material_ids": [], - } - - for item in native_data["representation"].Items: - if item.is_a() == "IfcFacetedBrep": - self.convert_representation_item_faceted_brep(item) - elif item.is_a() == "IfcFaceBasedSurfaceModel": - self.convert_representation_item_face_based_surface_model(item) - - mesh = bpy.data.meshes.new("Native") - - props = bpy.context.scene.BIMGeoreferenceProperties - if props.has_blender_offset and tool.Loader.is_point_far_away(self.mesh_data["co"][0:3], is_meters=False): - verts_array = np.array(self.mesh_data["co"]) - verts_array *= self.unit_scale - offset_x, offset_y, offset_z = verts_array[0:3] - offset = np.array([-offset_x, -offset_y, -offset_z]) - offset_verts = verts_array + np.tile(offset, len(verts_array) // 3) - - if np.allclose(native_data["matrix"], np.identity(4), atol=1e-8): - verts = offset_verts.tolist() - else: - verts = self.apply_matrix_to_flat_coords(offset_verts, native_data["matrix"]) - - mesh["has_cartesian_point_offset"] = True - mesh["cartesian_point_offset"] = f"{offset_x},{offset_y},{offset_z}" - else: - verts_array = np.array(self.mesh_data["co"]) - verts_array *= self.unit_scale - if np.allclose(native_data["matrix"], np.identity(4), atol=1e-8): - verts = verts_array.tolist() - else: - verts = self.apply_matrix_to_flat_coords(verts_array, native_data["matrix"]) - mesh["has_cartesian_point_offset"] = False - - mesh.vertices.add(self.mesh_data["total_verts"]) - mesh.vertices.foreach_set("co", verts) - mesh.loops.add(len(self.mesh_data["vertex_index"])) - mesh.loops.foreach_set("vertex_index", self.mesh_data["vertex_index"]) - mesh.polygons.add(self.mesh_data["total_polygons"]) - mesh.polygons.foreach_set("loop_start", self.mesh_data["loop_start"]) - mesh.polygons.foreach_set("loop_total", self.mesh_data["loop_total"]) - mesh.polygons.foreach_set("use_smooth", [0] * self.mesh_data["total_polygons"]) - mesh.update() - - mesh["ios_materials"] = self.mesh_data["materials"] - mesh["ios_material_ids"] = self.mesh_data["material_ids"] - return mesh - - def apply_matrix_to_flat_coords(self, coords, matrix): - coords_array = np.array(coords).reshape(-1, 3) - ones = np.ones((coords_array.shape[0], 1)) - homogeneous_coords = np.hstack([coords_array, ones]) - transformed_coords = homogeneous_coords @ matrix.T - return transformed_coords[:, :3].flatten().tolist() - - def convert_representation_item_face_based_surface_model(self, item): - mesh = item.get_info_2(recursive=True) - for face_set in mesh["FbsmFaces"]: - self.convert_representation_item_face_set(item, face_set) - - def convert_representation_item_faceted_brep(self, item): - mesh = item.get_info_2(recursive=True) - return self.convert_representation_item_face_set(item, mesh["Outer"]) - - def convert_representation_item_face_set(self, item, mesh): - # On a few occasions, we flatten a list. This seems to be the most efficient way to do it. - # https://stackoverflow.com/questions/20112776/how-do-i-flatten-a-list-of-lists-nested-lists - - # For huge face sets it might be better to do a "flatmap" instead of sum() - # bounds = sum((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1), ()) - bounds = tuple(chain.from_iterable(f["Bounds"] for f in mesh["CfsFaces"] if len(f["Bounds"]) == 1)) - # Here are some untested alternatives, are they faster? - # bounds = tuple((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1))[0] - # bounds = chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1) - - polygons = [[(p["id"], p["Coordinates"]) for p in b["Bound"]["Polygon"]] for b in bounds] - - for face in mesh["CfsFaces"]: - # Blender cannot handle faces with holes. - if len(face["Bounds"]) > 1: - inner_bounds = [] - inner_bound_point_ids = [] - for bound in face["Bounds"]: - if bound["type"] == "IfcFaceOuterBound": - outer_bound = [[p["Coordinates"] for p in bound["Bound"]["Polygon"]]] - outer_bound_point_ids = [[p["id"] for p in bound["Bound"]["Polygon"]]] - else: - inner_bounds.append([p["Coordinates"] for p in bound["Bound"]["Polygon"]]) - inner_bound_point_ids.append([p["id"] for p in bound["Bound"]["Polygon"]]) - points = outer_bound[0].copy() - [points.extend(p) for p in inner_bounds] - point_ids = outer_bound_point_ids[0].copy() - [point_ids.extend(p) for p in inner_bound_point_ids] - - tessellated_polygons = mathutils.geometry.tessellate_polygon(outer_bound + inner_bounds) - polygons.extend([[(point_ids[pi], points[pi]) for pi in t] for t in tessellated_polygons]) - - # Clever vertex welding algorithm by Thomas Krijnen. See #841. - - # by id - di0 = {} - # by coords - di1 = {} - - vertex_index_offset = self.mesh_data["total_verts"] - - def lookup(id_coords): - idx = di0.get(id_coords[0]) - if idx is None: - idx = di1.get(id_coords[1]) - if idx is None: - l = len(di0) - di0[id_coords[0]] = l - di1[id_coords[1]] = l - return l + vertex_index_offset - else: - return idx + vertex_index_offset - else: - return idx + vertex_index_offset - - mapped_polygons = [list(map(lookup, p)) for p in polygons] - - self.mesh_data["vertex_index"].extend(chain.from_iterable(mapped_polygons)) - - # Flattened vertex coords - self.mesh_data["co"].extend(chain.from_iterable(di1.keys())) - self.mesh_data["total_verts"] += len(di1.keys()) - loop_total = [len(p) for p in mapped_polygons] - total_polygons = len(mapped_polygons) - self.mesh_data["total_polygons"] += total_polygons - - self.mesh_data["materials"].append(self.get_representation_item_material_name(item) or "NULLMAT") - material_index = len(self.mesh_data["materials"]) - 1 - if self.mesh_data["materials"][material_index] == "NULLMAT": - # Magic number -1 represents no material, until this has a better approach - self.mesh_data["material_ids"] += [-1] * total_polygons - else: - self.mesh_data["material_ids"] += [material_index] * total_polygons - - if self.mesh_data["loop_start"]: - loop_start_offset = self.mesh_data["loop_start"][-1] + self.mesh_data["loop_total"][-1] - else: - loop_start_offset = 0 - - loop_start = [loop_start_offset] + [loop_start_offset + i for i in list(accumulate(loop_total[0:-1]))] - self.mesh_data["loop_total"].extend(loop_total) - self.mesh_data["loop_start"].extend(loop_start) - # list(di1.keys()) - def create_native_swept_disk_solid( self, element: ifcopenshell.entity_instance, mesh_name: str, native_data: dict[str, Any] ) -> bpy.types.Curve: @@ -1294,38 +1043,6 @@ class IfcImporter: result[2][3] *= self.unit_scale return result - def scale_matrix(self, matrix: np.array) -> np.array: - matrix[0][3] *= self.unit_scale - matrix[1][3] *= self.unit_scale - matrix[2][3] *= self.unit_scale - return matrix - - def get_representation_id(self, element): - if not element.Representation: - return None - for representation in element.Representation.Representations: - if not representation.is_a("IfcShapeRepresentation"): - continue - if ( - representation.RepresentationIdentifier == "Body" - and representation.RepresentationType != "MappedRepresentation" - ): - return representation.id() - elif representation.RepresentationIdentifier == "Body": - return representation.Items[0].MappingSource.MappedRepresentation.id() - - def get_representation_cartesian_transformation(self, element): - if not element.Representation: - return None - for representation in element.Representation.Representations: - if not representation.is_a("IfcShapeRepresentation"): - continue - if ( - representation.RepresentationIdentifier == "Body" - and representation.RepresentationType == "MappedRepresentation" - ): - return representation.Items[0].MappingTarget - def create_curve( self, element: ifcopenshell.entity_instance, @@ -1411,42 +1128,6 @@ class IfcImporter: print(traceback.format_exc()) - def a2p(self, o: mathutils.Vector, z: mathutils.Vector, x: mathutils.Vector) -> mathutils.Matrix: - y = z.cross(x) - r = mathutils.Matrix((x, y, z, o)) - r.resize_4x4() - r.transpose() - return r - - def get_axis2placement(self, plc: ifcopenshell.entity_instance) -> mathutils.Matrix: - if plc.is_a("IfcAxis2Placement3D"): - z = mathutils.Vector(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1)) - x = mathutils.Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0)) - o = plc.Location.Coordinates - else: - z = mathutils.Vector((0, 0, 1)) - if plc.RefDirection: - x = mathutils.Vector(list(plc.RefDirection.DirectionRatios) + [0]) - else: - x = mathutils.Vector((1, 0, 0)) - o = list(plc.Location.Coordinates) + [0] - return self.a2p(o, z, x) - - def get_cartesiantransformationoperator(self, plc): - x = mathutils.Vector(plc.Axis1.DirectionRatios if plc.Axis1 else (1, 0, 0)) - z = x.cross(mathutils.Vector(plc.Axis2.DirectionRatios if plc.Axis2 else (0, 1, 0))) - o = plc.LocalOrigin.Coordinates - return self.a2p(o, z, x) - - def get_local_placement(self, plc: Optional[ifcopenshell.entity_instance] = None) -> mathutils.Matrix: - if plc is None: - return mathutils.Matrix() - if plc.PlacementRelTo is None: - parent = mathutils.Matrix() - else: - parent = self.get_local_placement(plc.PlacementRelTo) - return parent @ self.get_axis2placement(plc.RelativePlacement) - def set_default_context(self): for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"): if subcontext.ContextIdentifier == "Body":