Fix linking project missing offsets for instances of complex geometry

After ba2456a if main IFC project had an offset and linked object had more than 333 faces, then it was imported at it's original location instead, missing an offset.

Also processing occurrences should be now more optimized, since we reuse the same verts buffer instead of recreating it 10 times.
This commit is contained in:
Andrej730
2025-07-22 14:18:55 +05:00
parent 8f5905c801
commit 0ef255bc3c
2 changed files with 27 additions and 23 deletions
@@ -41,6 +41,7 @@ import ifcopenshell.util.representation
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.representation import ifcopenshell.util.representation
import ifcopenshell.util.shape import ifcopenshell.util.shape
import ifcopenshell.util.shape_builder
import ifcopenshell.util.unit import ifcopenshell.util.unit
import bonsai.bim.handler import bonsai.bim.handler
import bonsai.bim.helper import bonsai.bim.helper
@@ -1888,42 +1889,32 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
def process_occurrence(self, shape: W.TriangulationElement) -> None: def process_occurrence(self, shape: W.TriangulationElement) -> None:
element = self.file.by_id(shape.id) element = self.file.by_id(shape.id)
faces = shape.geometry.faces
verts = shape.geometry.verts
materials = shape.geometry.materials
material_ids = shape.geometry.material_ids
mat = ifcopenshell.util.shape.get_shape_matrix(shape) mat = ifcopenshell.util.shape.get_shape_matrix(shape)
mesh = self.meshes.get(shape.geometry.id, None) geometry = shape.geometry
mesh = self.meshes.get(geometry.id, None)
if not mesh: if not mesh:
mesh = bpy.data.meshes.new("Mesh") verts = ifcopenshell.util.shape.get_vertices(geometry)
material_ids = geometry.material_ids
mesh = tool.Loader.create_mesh_from_shape(geometry, mesh)
geometry = shape.geometry
gprops = tool.Georeference.get_georeference_props() gprops = tool.Georeference.get_georeference_props()
if ( if gprops.has_blender_offset and verts.size and tool.Loader.is_point_far_away(verts[0], is_meters=True):
gprops.has_blender_offset vert: np.ndarray = verts[0]
and geometry.verts
and tool.Loader.is_point_far_away(
(geometry.verts[0], geometry.verts[1], geometry.verts[2]), is_meters=True
)
):
# Shift geometry close to the origin based off that first vert it found # Shift geometry close to the origin based off that first vert it found
verts_array = np.array(geometry.verts) offset = ifcopenshell.util.shape_builder.np_translation_matrix(-vert)
offset = np.array([-geometry.verts[0], -geometry.verts[1], -geometry.verts[2]]) mat = offset @ mat
offset_verts = verts_array + np.tile(offset, len(verts_array) // 3)
verts = offset_verts.tolist()
mesh["has_cartesian_point_offset"] = True mesh["has_cartesian_point_offset"] = True
mesh["cartesian_point_offset"] = f"{geometry.verts[0]},{geometry.verts[1]},{geometry.verts[2]}" mesh["cartesian_point_offset"] = ",".join(vert.astype(str))
else: else:
verts = geometry.verts
mesh["has_cartesian_point_offset"] = False mesh["has_cartesian_point_offset"] = False
material_to_slot: dict[int, int] = {} material_to_slot: dict[int, int] = {}
max_slot_index = 0 max_slot_index = 0
for i, material in enumerate(materials): for i, material in enumerate(geometry.materials):
alpha = 1.0 alpha = 1.0
if material.has_transparency and material.transparency > 0: if material.has_transparency and material.transparency > 0:
alpha = 1.0 - material.transparency alpha = 1.0 - material.transparency
@@ -1946,11 +1937,10 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
material_index = np.array([(material_to_slot[i] if i != -1 else 0) for i in material_ids], dtype="I") material_index = np.array([(material_to_slot[i] if i != -1 else 0) for i in material_ids], dtype="I")
mesh = tool.Loader.create_mesh_from_shape(geometry, mesh)
mesh.polygons.foreach_set("material_index", material_index) mesh.polygons.foreach_set("material_index", material_index)
mesh.update() mesh.update()
self.meshes[shape.geometry.id] = mesh self.meshes[geometry.id] = mesh
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh) obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, mat) obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, mat)
@@ -157,6 +157,20 @@ def np_angle_signed(a: VectorType, b: VectorType) -> float:
return np.arctan2(det, dot) return np.arctan2(det, dot)
def np_translation_matrix(vector: VectorType) -> npt.NDArray[np.float64]:
"""Get translation matrix.
Designed to be similar to mathutils Matrix.Rotation but to use numpy.
:param vector: 3D translation vector.
:return: An 4x4 identity matrix with a translation
"""
eye = np.eye(4, dtype=np.float64)
M_TRANSLATION = (slice(0, 3), 3)
eye[M_TRANSLATION] = vector
return eye
def np_rotation_matrix( def np_rotation_matrix(
angle: float, size: int, axis: Optional[Union[Literal["X", "Y", "Z"], VectorType]] = None angle: float, size: int, axis: Optional[Union[Literal["X", "Y", "Z"], VectorType]] = None
) -> np.ndarray: ) -> np.ndarray: