mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Linking in models now uses the same cartesian point offset strategy as loading models with a dynamic distance limit
Previously, it would have a hardcoded 1km distance limit and any "far away" object would be treated as a chunk instead of an occurrence. Now it has dynamic distance limits and benefit from occurrence instancing.
This commit is contained in:
@@ -542,42 +542,6 @@ class IfcImporter:
|
||||
return tool.Loader.set_manual_blender_offset(self.file)
|
||||
return tool.Loader.guess_false_origin(self.file)
|
||||
|
||||
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 (
|
||||
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.
|
||||
obj.BIMObjectProperties.blender_offset_type = "NOT_APPLICABLE"
|
||||
return mathutils.Matrix(matrix.tolist())
|
||||
elif obj.data and obj.data.get("has_cartesian_point_offset", None):
|
||||
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
|
||||
if cartesian_point_offset := obj.data.get("cartesian_point_offset", None):
|
||||
obj.BIMObjectProperties.cartesian_point_offset = cartesian_point_offset
|
||||
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_offset_x) * self.unit_scale,
|
||||
float(props.blender_offset_y) * self.unit_scale,
|
||||
float(props.blender_offset_z) * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
|
||||
return mathutils.Matrix(matrix.tolist())
|
||||
|
||||
def create_grids(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
@@ -609,7 +573,7 @@ class IfcImporter:
|
||||
obj.lock_location = (True, True, True)
|
||||
obj.lock_rotation = (True, True, True)
|
||||
self.link_element(axis, obj)
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, grid_placement.copy()))
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, grid_placement.copy()))
|
||||
|
||||
def create_element_types(self):
|
||||
for element_type in self.element_types:
|
||||
@@ -835,7 +799,7 @@ class IfcImporter:
|
||||
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
|
||||
|
||||
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.link_element(product, obj)
|
||||
|
||||
def get_pointcloud_representation(self, product):
|
||||
@@ -901,7 +865,7 @@ class IfcImporter:
|
||||
tool.Ifc.link(representation, mesh)
|
||||
|
||||
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.link_element(product, obj)
|
||||
return product
|
||||
|
||||
@@ -942,13 +906,17 @@ class IfcImporter:
|
||||
if shape:
|
||||
# We use numpy here because Blender mathutils.Matrix is not accurate enough
|
||||
mat = np.array(shape.transformation.matrix).reshape((4, 4), order="F")
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, mat))
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, mat))
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
elif mesh:
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)))
|
||||
self.set_matrix_world(
|
||||
obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
|
||||
)
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
elif hasattr(element, "ObjectPlacement"):
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)))
|
||||
self.set_matrix_world(
|
||||
obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
|
||||
)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
@@ -1326,7 +1326,8 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
shape = iterator.get()
|
||||
results.add(self.file.by_id(shape.id))
|
||||
|
||||
if len(shape.geometry.faces) > 1000 and self.is_local(shape): # 333 tris
|
||||
# Elements with a lot of geometry benefit from instancing to save memory
|
||||
if len(shape.geometry.faces) > 1000: # 333 tris
|
||||
self.process_occurrence(shape)
|
||||
if not iterator.next():
|
||||
if not chunked_verts:
|
||||
@@ -1373,10 +1374,19 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
chunked_material_ids.append(mi + material_offset + 1)
|
||||
material_offset += len(ms)
|
||||
|
||||
M4 = np.frombuffer(shape.transformation_buffer).reshape((4, 4), order="F")
|
||||
matrix = np.frombuffer(shape.transformation_buffer).reshape((4, 4), order="F")
|
||||
if gprops.has_blender_offset:
|
||||
matrix = ifcopenshell.util.geolocation.global2local(
|
||||
matrix,
|
||||
float(gprops.blender_offset_x) * self.unit_scale,
|
||||
float(gprops.blender_offset_y) * self.unit_scale,
|
||||
float(gprops.blender_offset_z) * self.unit_scale,
|
||||
float(gprops.blender_x_axis_abscissa),
|
||||
float(gprops.blender_x_axis_ordinate),
|
||||
)
|
||||
vs = np.frombuffer(shape.geometry.verts_buffer).reshape((-1, 3))
|
||||
vs = np.hstack((vs, np.ones((len(vs), 1))))
|
||||
vs = (np.asmatrix(M4) * np.asmatrix(vs).T).T.A
|
||||
vs = (np.asmatrix(matrix) * np.asmatrix(vs).T).T.A
|
||||
vs = vs[:, :3].flatten()
|
||||
fs = np.frombuffer(shape.geometry.faces_buffer, dtype=np.int32)
|
||||
chunked_verts.append(vs)
|
||||
@@ -1457,14 +1467,6 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
print("Finished", time.time() - start)
|
||||
return {"FINISHED"}
|
||||
|
||||
def is_local(self, shape: ShapeElementType) -> bool:
|
||||
m = shape.transformation.matrix
|
||||
if max([abs(co) for co in (m[9], m[10], m[11])]) > 1000:
|
||||
return False
|
||||
elif max([abs(co) for co in shape.geometry.verts[0:3]]) > 1000:
|
||||
return False
|
||||
return True
|
||||
|
||||
def process_occurrence(self, shape: ShapeElementType) -> None:
|
||||
element = self.file.by_id(shape.id)
|
||||
faces = shape.geometry.faces
|
||||
@@ -1478,6 +1480,27 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
if not mesh:
|
||||
mesh = bpy.data.meshes.new("Mesh")
|
||||
|
||||
geometry = shape.geometry
|
||||
gprops = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if (
|
||||
gprops.has_blender_offset
|
||||
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
|
||||
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
|
||||
|
||||
material_to_slot = {}
|
||||
max_slot_index = 0
|
||||
|
||||
@@ -1525,7 +1548,7 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
self.meshes[shape.geometry.id] = mesh
|
||||
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
obj.matrix_world = Matrix(mat.tolist())
|
||||
obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, mat)
|
||||
|
||||
obj["guids"] = [shape.guid]
|
||||
obj["guid_ids"] = [len(mesh.polygons)]
|
||||
|
||||
@@ -26,7 +26,7 @@ import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from mathutils import Vector
|
||||
from mathutils import Vector, Matrix
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
@@ -572,7 +572,9 @@ class Loader(blenderbim.core.tool.Loader):
|
||||
props.has_blender_offset = True
|
||||
|
||||
@classmethod
|
||||
def guess_false_origin_and_project_north(cls, ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> None:
|
||||
def guess_false_origin_and_project_north(
|
||||
cls, ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
|
||||
return
|
||||
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
@@ -661,3 +663,41 @@ class Loader(blenderbim.core.tool.Loader):
|
||||
if building and cls.is_element_far_away(building):
|
||||
return cls.guess_false_origin_and_project_north(ifc_file, building)
|
||||
return cls.guess_false_origin_from_elements(ifc_file)
|
||||
|
||||
@classmethod
|
||||
def apply_blender_offset_to_matrix_world(cls, obj: bpy.types.Object, matrix: np.ndarray) -> Matrix:
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
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.
|
||||
obj.BIMObjectProperties.blender_offset_type = "NOT_APPLICABLE"
|
||||
return Matrix(matrix.tolist())
|
||||
elif obj.data and obj.data.get("has_cartesian_point_offset", None):
|
||||
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
|
||||
if cartesian_point_offset := obj.data.get("cartesian_point_offset", None):
|
||||
obj.BIMObjectProperties.cartesian_point_offset = cartesian_point_offset
|
||||
offset_xyz = list(map(float, cartesian_point_offset.split(","))) + [1.0]
|
||||
offset_xyz = matrix @ offset_xyz
|
||||
matrix[0][3] = offset_xyz[0]
|
||||
matrix[1][3] = offset_xyz[1]
|
||||
matrix[2][3] = offset_xyz[2]
|
||||
else:
|
||||
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
|
||||
matrix = ifcopenshell.util.geolocation.global2local(
|
||||
matrix,
|
||||
float(props.blender_offset_x) * cls.unit_scale,
|
||||
float(props.blender_offset_y) * cls.unit_scale,
|
||||
float(props.blender_offset_z) * cls.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
|
||||
return Matrix(matrix.tolist())
|
||||
|
||||
Reference in New Issue
Block a user