From 98ec3e2301e9d9c9586b99219e5b876462a990b1 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 6 Jun 2024 11:54:07 +1000 Subject: [PATCH] Fix #4677. Fix #4809. Major improvement to local session offset during project loading (see description) This improves four things: 1. Previously, we either used OBJECT_PLACEMENT or CARTESIAN_POINT, but couldn't handle scenarios where simultaneously both the placement and the coords were rubbish for a single object. Now we offset all far cartesian points, so it consistently works and we keep track of a per-object offset. 2. Previously, we applied the georeferencing conversion on every cartesian point which was very slow. The new method uses a simple XYZ translation which is super fast. 3. We now use numpy which should be much faster too. 4. Previously, objects were selectively offset based on whether they fell outside the distance limit. Now, we uniformly treat all non-geometric elements at 0,0,0 as insignificant positionally. This fixes the issue where half the model is offset and the other half isn't, but maintains the fix for situations where the site (typically) is at 0,0,0 and everything else is map coords. --- src/blenderbim/blenderbim/bim/import_ifc.py | 127 ++++++++------------ 1 file changed, 53 insertions(+), 74 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 9810d39f82..4a31abfe34 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -743,18 +743,29 @@ class IfcImporter: def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix: props = bpy.context.scene.BIMGeoreferenceProperties if props.has_blender_offset: - if obj.data and obj.data.get("has_cartesian_point_offset", None): + if not obj.data and tool.Cad.is_x(matrix[0][3], 0) and tool.Cad.is_x(matrix[1][3], 0) and tool.Cad.is_x(matrix[2][3], 0): + # We assume any non-geometric matrix at 0,0,0 is not + # positionally significant and is left alone. This handles + # scenarios where often spatial elements are left at 0,0,0 and + # everything else is at map coordinates. + return mathutils.Matrix(matrix.tolist()) + elif obj.data and obj.data.get("has_cartesian_point_offset", None): obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT" - elif self.is_point_far_away((matrix[:3, 3])): + if cartesian_point_offset := obj.data.get("cartesian_point_offset", None): + offset_x, offset_y, offset_z = map(float, cartesian_point_offset.split(",")) + matrix[0][3] += offset_x + matrix[1][3] += offset_y + matrix[2][3] += offset_z + else: obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT" - matrix = ifcopenshell.util.geolocation.global2local( - matrix, - float(props.blender_eastings) * self.unit_scale, - float(props.blender_northings) * self.unit_scale, - float(props.blender_orthogonal_height) * self.unit_scale, - float(props.blender_x_axis_abscissa), - float(props.blender_x_axis_ordinate), - ) + matrix = ifcopenshell.util.geolocation.global2local( + matrix, + float(props.blender_eastings) * self.unit_scale, + float(props.blender_northings) * self.unit_scale, + float(props.blender_orthogonal_height) * self.unit_scale, + float(props.blender_x_axis_abscissa), + float(props.blender_x_axis_ordinate), + ) return mathutils.Matrix(matrix.tolist()) @@ -1201,7 +1212,6 @@ class IfcImporter: styles.extend(style.Styles) def create_native_faceted_brep(self, element, mesh_name, native_data): - # TODO: georeferencing? # 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) @@ -1226,45 +1236,27 @@ class IfcImporter: mesh = bpy.data.meshes.new("Native") props = bpy.context.scene.BIMGeoreferenceProperties - mat = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) if props.has_blender_offset and self.is_point_far_away(self.mesh_data["co"][0:3], is_meters=False): - offset_point = np.linalg.inv(mat) @ np.array( - ( - float(props.blender_eastings), - float(props.blender_northings), - float(props.blender_orthogonal_height), - 0.0, - ) - ) - verts = [None] * len(self.mesh_data["co"]) - for i in range(0, len(self.mesh_data["co"]), 3): - verts[i], verts[i + 1], verts[i + 2], _ = native_data["matrix"] @ mathutils.Vector( - ( - *ifcopenshell.util.geolocation.enh2xyz( - self.mesh_data["co"][i] * self.unit_scale, - self.mesh_data["co"][i + 1] * self.unit_scale, - self.mesh_data["co"][i + 2] * self.unit_scale, - offset_point[0] * self.unit_scale, - offset_point[1] * self.unit_scale, - offset_point[2] * self.unit_scale, - float(props.blender_x_axis_abscissa), - float(props.blender_x_axis_ordinate), - ), - 1, - ) - ) + 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 = [None] * len(self.mesh_data["co"]) - for i in range(0, len(self.mesh_data["co"]), 3): - verts[i], verts[i + 1], verts[i + 2], _ = native_data["matrix"] @ mathutils.Vector( - ( - self.mesh_data["co"][i] * self.unit_scale, - self.mesh_data["co"][i + 1] * self.unit_scale, - self.mesh_data["co"][i + 2] * self.unit_scale, - 1, - ) - ) + 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"]) @@ -1281,6 +1273,13 @@ class IfcImporter: 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"]: @@ -1924,34 +1923,14 @@ class IfcImporter: and geometry.verts and self.is_point_far_away((geometry.verts[0], geometry.verts[1], geometry.verts[2])) ): - offset_point = np.array( - ( - float(props.blender_eastings), - float(props.blender_northings), - float(props.blender_orthogonal_height), - 0.0, - ) - ) - if geometry != shape: - m = shape.transformation.matrix.data - mat = np.array( - ([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]) - ) - offset_point = np.linalg.inv(mat) @ offset_point - verts = [None] * len(geometry.verts) - for i in range(0, len(geometry.verts), 3): - # Note: this enh2xyz call is crazy slow. - verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz( - geometry.verts[i], - geometry.verts[i + 1], - geometry.verts[i + 2], - offset_point[0] * self.unit_scale, - offset_point[1] * self.unit_scale, - offset_point[2] * self.unit_scale, - float(props.blender_x_axis_abscissa), - float(props.blender_x_axis_ordinate), - ) + # Shift geometry close to the origin based off that first vert it found + verts_array = np.array(geometry.verts) + offset = np.array([-geometry.verts[0], -geometry.verts[1], -geometry.verts[2]]) + offset_verts = verts_array + np.tile(offset, len(verts_array) // 3) + verts = offset_verts.tolist() + mesh["has_cartesian_point_offset"] = True + mesh["cartesian_point_offset"] = f"{geometry.verts[0]},{geometry.verts[1]},{geometry.verts[2]}" else: verts = geometry.verts mesh["has_cartesian_point_offset"] = False