mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 06:58:56 +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.set_manual_blender_offset(self.file)
|
||||||
return tool.Loader.guess_false_origin(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):
|
def create_grids(self):
|
||||||
if not self.ifc_import_settings.should_load_geometry:
|
if not self.ifc_import_settings.should_load_geometry:
|
||||||
return
|
return
|
||||||
@@ -609,7 +573,7 @@ class IfcImporter:
|
|||||||
obj.lock_location = (True, True, True)
|
obj.lock_location = (True, True, True)
|
||||||
obj.lock_rotation = (True, True, True)
|
obj.lock_rotation = (True, True, True)
|
||||||
self.link_element(axis, obj)
|
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):
|
def create_element_types(self):
|
||||||
for element_type in self.element_types:
|
for element_type in self.element_types:
|
||||||
@@ -835,7 +799,7 @@ class IfcImporter:
|
|||||||
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
|
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
|
||||||
|
|
||||||
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), 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)
|
self.link_element(product, obj)
|
||||||
|
|
||||||
def get_pointcloud_representation(self, product):
|
def get_pointcloud_representation(self, product):
|
||||||
@@ -901,7 +865,7 @@ class IfcImporter:
|
|||||||
tool.Ifc.link(representation, mesh)
|
tool.Ifc.link(representation, mesh)
|
||||||
|
|
||||||
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), 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)
|
self.link_element(product, obj)
|
||||||
return product
|
return product
|
||||||
|
|
||||||
@@ -942,13 +906,17 @@ class IfcImporter:
|
|||||||
if shape:
|
if shape:
|
||||||
# We use numpy here because Blender mathutils.Matrix is not accurate enough
|
# We use numpy here because Blender mathutils.Matrix is not accurate enough
|
||||||
mat = np.array(shape.transformation.matrix).reshape((4, 4), order="F")
|
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)
|
self.material_creator.create(element, obj, mesh)
|
||||||
elif 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)
|
self.material_creator.create(element, obj, mesh)
|
||||||
elif hasattr(element, "ObjectPlacement"):
|
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
|
return obj
|
||||||
|
|
||||||
|
|||||||
@@ -1326,7 +1326,8 @@ class LoadLinkedProject(bpy.types.Operator):
|
|||||||
shape = iterator.get()
|
shape = iterator.get()
|
||||||
results.add(self.file.by_id(shape.id))
|
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)
|
self.process_occurrence(shape)
|
||||||
if not iterator.next():
|
if not iterator.next():
|
||||||
if not chunked_verts:
|
if not chunked_verts:
|
||||||
@@ -1373,10 +1374,19 @@ class LoadLinkedProject(bpy.types.Operator):
|
|||||||
chunked_material_ids.append(mi + material_offset + 1)
|
chunked_material_ids.append(mi + material_offset + 1)
|
||||||
material_offset += len(ms)
|
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.frombuffer(shape.geometry.verts_buffer).reshape((-1, 3))
|
||||||
vs = np.hstack((vs, np.ones((len(vs), 1))))
|
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()
|
vs = vs[:, :3].flatten()
|
||||||
fs = np.frombuffer(shape.geometry.faces_buffer, dtype=np.int32)
|
fs = np.frombuffer(shape.geometry.faces_buffer, dtype=np.int32)
|
||||||
chunked_verts.append(vs)
|
chunked_verts.append(vs)
|
||||||
@@ -1457,14 +1467,6 @@ class LoadLinkedProject(bpy.types.Operator):
|
|||||||
print("Finished", time.time() - start)
|
print("Finished", time.time() - start)
|
||||||
return {"FINISHED"}
|
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:
|
def process_occurrence(self, shape: ShapeElementType) -> None:
|
||||||
element = self.file.by_id(shape.id)
|
element = self.file.by_id(shape.id)
|
||||||
faces = shape.geometry.faces
|
faces = shape.geometry.faces
|
||||||
@@ -1478,6 +1480,27 @@ class LoadLinkedProject(bpy.types.Operator):
|
|||||||
if not mesh:
|
if not mesh:
|
||||||
mesh = bpy.data.meshes.new("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 = {}
|
material_to_slot = {}
|
||||||
max_slot_index = 0
|
max_slot_index = 0
|
||||||
|
|
||||||
@@ -1525,7 +1548,7 @@ class LoadLinkedProject(bpy.types.Operator):
|
|||||||
self.meshes[shape.geometry.id] = mesh
|
self.meshes[shape.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 = Matrix(mat.tolist())
|
obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, mat)
|
||||||
|
|
||||||
obj["guids"] = [shape.guid]
|
obj["guids"] = [shape.guid]
|
||||||
obj["guid_ids"] = [len(mesh.polygons)]
|
obj["guid_ids"] = [len(mesh.polygons)]
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import blenderbim.core.tool
|
|||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import numpy.typing as npt
|
import numpy.typing as npt
|
||||||
from mathutils import Vector
|
from mathutils import Vector, Matrix
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
@@ -572,7 +572,9 @@ class Loader(blenderbim.core.tool.Loader):
|
|||||||
props.has_blender_offset = True
|
props.has_blender_offset = True
|
||||||
|
|
||||||
@classmethod
|
@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"):
|
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
|
||||||
return
|
return
|
||||||
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
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):
|
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_and_project_north(ifc_file, building)
|
||||||
return cls.guess_false_origin_from_elements(ifc_file)
|
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