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:
Dion Moult
2024-06-17 17:03:42 +10:00
parent 743a197542
commit 9c61a9eed7
3 changed files with 87 additions and 56 deletions
+10 -42
View File
@@ -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)]
+42 -2
View File
@@ -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())