mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
typing
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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`;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user