This commit is contained in:
Andrej730
2024-05-16 16:27:41 +05:00
parent 8d4b5d83e1
commit 3884403231
10 changed files with 89 additions and 46 deletions
+11 -9
View File
@@ -26,6 +26,7 @@ import bmesh
import logging import logging
import mathutils import mathutils
import numpy as np import numpy as np
import numpy.typing as npt
import multiprocessing import multiprocessing
import ifcopenshell import ifcopenshell
import ifcopenshell.geom import ifcopenshell.geom
@@ -303,22 +304,22 @@ class IfcImporter:
self.update_progress(100) self.update_progress(100)
bpy.context.window_manager.progress_end() bpy.context.window_manager.progress_end()
def is_element_far_away(self, element): def is_element_far_away(self, element: ifcopenshell.entity_instance) -> bool:
try: try:
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
point = placement[:, 3][0:3] point = placement[:, 3][0:3]
return self.is_point_far_away(point, is_meters=False) return self.is_point_far_away(point, is_meters=False)
except: except:
pass return False
def is_point_far_away(self, point, is_meters=True): def is_point_far_away(
self, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]], is_meters: bool = True
) -> bool:
# Locations greater than 1km are not considered "small sites" according to the georeferencing guide # Locations greater than 1km are not considered "small sites" according to the georeferencing guide
# Users can configure this if they have to handle larger sites but beware of surveying precision # Users can configure this if they have to handle larger sites but beware of surveying precision
limit = self.ifc_import_settings.distance_limit limit = self.ifc_import_settings.distance_limit
limit = limit if is_meters else (limit / self.unit_scale) limit = limit if is_meters else (limit / self.unit_scale)
coords = point coords = getattr(point, "Coordinates", point)
if hasattr(point, "Coordinates"):
coords = point.Coordinates
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
def process_context_filter(self): def process_context_filter(self):
@@ -683,7 +684,7 @@ class IfcImporter:
props.blender_orthogonal_height = str(offset_point[2]) props.blender_orthogonal_height = str(offset_point[2])
props.has_blender_offset = True props.has_blender_offset = True
def get_offset_point(self): def get_offset_point(self) -> Union[npt.NDArray[np.float64], None]:
elements_checked = 0 elements_checked = 0
# If more than these elements aren't far away, the file probably isn't absolutely positioned # If more than these elements aren't far away, the file probably isn't absolutely positioned
element_checking_threshold = 10 element_checking_threshold = 10
@@ -718,7 +719,7 @@ class IfcImporter:
if self.is_point_far_away(point, is_meters=False): if self.is_point_far_away(point, is_meters=False):
return point return point
def does_element_likely_have_geometry_far_away(self, element): def does_element_likely_have_geometry_far_away(self, element: ifcopenshell.entity_instance) -> bool:
for representation in element.Representation.Representations: for representation in element.Representation.Representations:
items = [] items = []
for item in representation.Items: for item in representation.Items:
@@ -735,13 +736,14 @@ class IfcImporter:
if subelement.is_a("IfcCartesianPoint"): if subelement.is_a("IfcCartesianPoint"):
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False): if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True return True
return False
def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix: def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix:
props = bpy.context.scene.BIMGeoreferenceProperties props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset: if props.has_blender_offset:
if obj.data and obj.data.get("has_cartesian_point_offset", None): if obj.data and obj.data.get("has_cartesian_point_offset", None):
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT" obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
elif self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])): elif self.is_point_far_away((matrix[:3, 3])):
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT" obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
matrix = ifcopenshell.util.geolocation.global2local( matrix = ifcopenshell.util.geolocation.global2local(
matrix, matrix,
@@ -23,6 +23,7 @@ import blenderbim.bim.handler
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.core.misc as core import blenderbim.core.misc as core
import blenderbim.core.geometry as core_geometry import blenderbim.core.geometry as core_geometry
import blenderbim.core.root
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from mathutils import Vector, Matrix, Euler from mathutils import Vector, Matrix, Euler
@@ -144,6 +145,7 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
cutter = context.active_object cutter = context.active_object
objs = [o for o in context.selected_objects if o != cutter] objs = [o for o in context.selected_objects if o != cutter]
objs_to_cut = []
# Splitting only works on meshes # Splitting only works on meshes
for obj in objs: for obj in objs:
# You cannot split meshes if the representation is mapped. # You cannot split meshes if the representation is mapped.
@@ -153,7 +155,13 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
if relating_type and tool.Root.does_type_have_representations(relating_type): if relating_type and tool.Root.does_type_have_representations(relating_type):
bpy.ops.bim.unassign_type(related_object=obj.name) bpy.ops.bim.unassign_type(related_object=obj.name)
# refresh representation
representation = tool.Geometry.get_active_representation(obj) representation = tool.Geometry.get_active_representation(obj)
# skip empty objects that might get in the way
if not representation:
continue
core_geometry.switch_representation( core_geometry.switch_representation(
tool.Ifc, tool.Ifc,
tool.Geometry, tool.Geometry,
@@ -168,11 +176,13 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
if not tool.Geometry.is_meshlike(representation): if not tool.Geometry.is_meshlike(representation):
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet") bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet")
new_objs = tool.Misc.split_objects_with_cutter(objs, cutter) objs_to_cut.append(obj)
new_objs = tool.Misc.split_objects_with_cutter(objs_to_cut, cutter)
for obj in new_objs: for obj in new_objs:
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj) blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
bpy.ops.bim.update_representation(obj=obj.name) bpy.ops.bim.update_representation(obj=obj.name)
for obj in objs: for obj in objs_to_cut:
bpy.ops.bim.update_representation(obj=obj.name) bpy.ops.bim.update_representation(obj=obj.name)
representation = tool.Geometry.get_active_representation(obj) representation = tool.Geometry.get_active_representation(obj)
+20 -12
View File
@@ -18,10 +18,11 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
import blenderbim.core.tool as tool
if TYPE_CHECKING: if TYPE_CHECKING:
import bpy import bpy
import ifcopenshell
import blenderbim.tool as tool
def edit_object_placement( def edit_object_placement(
@@ -37,8 +38,15 @@ def edit_object_placement(
def add_representation( def add_representation(
ifc, geometry, style, surveyor, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None ifc: tool.Ifc,
): geometry: tool.Geometry,
style: tool.Style,
surveyor: tool.Surveyor,
obj: bpy.types.Object,
context: ifcopenshell.entity_instance,
ifc_representation_class: Optional[str] = None,
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
element = ifc.get_entity(obj) element = ifc.get_entity(obj)
if not element: if not element:
return return
@@ -89,15 +97,15 @@ def add_representation(
def switch_representation( def switch_representation(
ifc, ifc: tool.Ifc,
geometry, geometry: tool.Geometry,
obj=None, obj: bpy.types.Object,
representation=None, representation: ifcopenshell.entity_instance,
should_reload=True, should_reload: bool = True,
is_global=True, is_global: bool = True,
should_sync_changes_first=False, should_sync_changes_first: bool = False,
apply_openings=True, apply_openings: bool = True,
): ) -> None:
"""Function can switch to representation that wasn't yet assigned to that object. See #2766. """Function can switch to representation that wasn't yet assigned to that object. See #2766.
`should_sync_changes_first` - sync ifc representation with current state of `obj.data`; `should_sync_changes_first` - sync ifc representation with current state of `obj.data`;
+21 -11
View File
@@ -16,8 +16,18 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
def copy_class(ifc, collector, geometry, root, obj=None): if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def copy_class(
ifc: tool.Ifc, collector: tool.Collector, geometry: tool.Geometry, root: tool.Root, obj: bpy.types.Object
) -> ifcopenshell.entity_instance:
element = ifc.get_entity(obj) element = ifc.get_entity(obj)
if not element: if not element:
return return
@@ -48,16 +58,16 @@ def copy_class(ifc, collector, geometry, root, obj=None):
def assign_class( def assign_class(
ifc, ifc: tool.Ifc,
collector, collector: tool.Collector,
root, root: tool.Root,
obj=None, obj: bpy.types.Object,
ifc_class=None, ifc_class: str,
predefined_type=None, context: ifcopenshell.entity_instance,
should_add_representation=True, predefined_type: Optional[str] = None,
context=None, should_add_representation: bool = True,
ifc_representation_class=None, ifc_representation_class: Optional[str] = None,
): ) -> ifcopenshell.entity_instance:
if ifc.get_entity(obj): if ifc.get_entity(obj):
return return
+1 -1
View File
@@ -18,11 +18,11 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union from typing import TYPE_CHECKING, Optional, Union
import blenderbim.core.tool as tool
if TYPE_CHECKING: if TYPE_CHECKING:
import bpy import bpy
import ifcopenshell import ifcopenshell
import blenderbim.tool as tool
def reference_structure( def reference_structure(
+10 -5
View File
@@ -26,6 +26,7 @@ import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.guid import ifcopenshell.guid
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.system import ifcopenshell.util.system
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.core.drawing import blenderbim.core.drawing
@@ -319,7 +320,7 @@ class Geometry(blenderbim.core.tool.Geometry):
return new_mesh return new_mesh
@classmethod @classmethod
def get_active_representation(cls, obj): def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
"""< IfcShapeRepresentation or None""" """< IfcShapeRepresentation or None"""
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id: if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
@@ -459,7 +460,9 @@ class Geometry(blenderbim.core.tool.Geometry):
return f"{representation.ContextOfItems.id()}/{representation.id()}" return f"{representation.ContextOfItems.id()}/{representation.id()}"
@classmethod @classmethod
def get_styles(cls, obj, only_assigned_to_faces=False): def get_styles(
cls, obj: bpy.types.Object, only_assigned_to_faces: bool = False
) -> list[Union[ifcopenshell.entity_instance, None]]:
styles = [tool.Style.get_style(s.material) for s in obj.material_slots if s.material] styles = [tool.Style.get_style(s.material) for s in obj.material_slots if s.material]
if not only_assigned_to_faces: if not only_assigned_to_faces:
return styles return styles
@@ -572,11 +575,11 @@ class Geometry(blenderbim.core.tool.Geometry):
return not all([tool.Cad.is_x(o, 1.0) for o in obj.scale]) or obj in IfcStore.edited_objs return not all([tool.Cad.is_x(o, 1.0) for o in obj.scale]) or obj in IfcStore.edited_objs
@classmethod @classmethod
def is_mapped_representation(cls, representation): def is_mapped_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
return representation.RepresentationType == "MappedRepresentation" return representation.RepresentationType == "MappedRepresentation"
@classmethod @classmethod
def is_meshlike(cls, representation): def is_meshlike(cls, representation: ifcopenshell.entity_instance) -> bool:
if ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in ( if ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in (
"AdvancedBrep", "AdvancedBrep",
"Annotation2D", "Annotation2D",
@@ -656,7 +659,9 @@ class Geometry(blenderbim.core.tool.Geometry):
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
@classmethod @classmethod
def resolve_mapped_representation(cls, representation): def resolve_mapped_representation(
cls, representation: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
if representation.RepresentationType == "MappedRepresentation": if representation.RepresentationType == "MappedRepresentation":
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation) return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
return representation return representation
+3 -1
View File
@@ -97,7 +97,9 @@ class Misc(blenderbim.core.tool.Misc):
IfcStore.edited_objs.add(obj) IfcStore.edited_objs.add(obj)
@classmethod @classmethod
def split_objects_with_cutter(cls, objs, cutter): def split_objects_with_cutter(
cls, objs: list[bpy.types.Object], cutter: bpy.types.Object
) -> list[bpy.types.Object]:
cutter_mesh = cutter.data cutter_mesh = cutter.data
bm = bmesh.new() bm = bmesh.new()
@@ -21,6 +21,7 @@ import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.core.root
import blenderbim.bim.schema import blenderbim.bim.schema
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
+8 -3
View File
@@ -29,6 +29,7 @@ import blenderbim.core.style
import blenderbim.tool as tool import blenderbim.tool as tool
from mathutils import Vector from mathutils import Vector
from blenderbim.bim.module.model.opening import FilledOpeningGenerator from blenderbim.bim.module.model.opening import FilledOpeningGenerator
from typing import Union, Optional
class Root(blenderbim.core.tool.Root): class Root(blenderbim.core.tool.Root):
@@ -129,7 +130,7 @@ class Root(blenderbim.core.tool.Root):
return ifcopenshell.util.representation.get_representation(element, context=context.ContextType) return ifcopenshell.util.representation.get_representation(element, context=context.ContextType)
@classmethod @classmethod
def get_element_type(cls, element): def get_element_type(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
return ifcopenshell.util.element.get_type(element) return ifcopenshell.util.element.get_type(element)
@classmethod @classmethod
@@ -282,8 +283,12 @@ class Root(blenderbim.core.tool.Root):
@classmethod @classmethod
def run_geometry_add_representation( def run_geometry_add_representation(
cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None cls,
): obj: bpy.types.Object,
context: ifcopenshell.entity_instance,
ifc_representation_class: Optional[str] = None,
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
return blenderbim.core.geometry.add_representation( return blenderbim.core.geometry.add_representation(
tool.Ifc, tool.Ifc,
tool.Geometry, tool.Geometry,
@@ -446,13 +446,13 @@ def get_predefined_type(element: ifcopenshell.entity_instance) -> str:
return predefined_type return predefined_type
def get_type(element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: def get_type(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
"""Retrieves the construction type element of an element occurrence """Retrieves the construction type element of an element occurrence
:param element: The element occurrence :param element: The element occurrence
:type: ifcopenshell.entity_instance :type: ifcopenshell.entity_instance
:return: The related type element :return: The related type element
:rtype: ifcopenshell.entity_instance :rtype: Union[ifcopenshell.entity_instance, None]
Example: Example: