diff --git a/src/bonsai/bonsai/core/root.py b/src/bonsai/bonsai/core/root.py index f124951d53..7e0b9381ba 100644 --- a/src/bonsai/bonsai/core/root.py +++ b/src/bonsai/bonsai/core/root.py @@ -27,7 +27,7 @@ if TYPE_CHECKING: def copy_class( ifc: tool.Ifc, collector: tool.Collector, geometry: tool.Geometry, root: tool.Root, obj: bpy.types.Object -) -> ifcopenshell.entity_instance: +) -> ifcopenshell.entity_instance | None: element = ifc.get_entity(obj) if not element: return @@ -43,14 +43,12 @@ def copy_class( ifc.run("type.map_type_representations", related_object=new, relating_type=relating_type) root.link_object_data(ifc.get_object(relating_type), obj) elif representation: - root.copy_representation(element, new) - new_representation = root.get_element_representation(new, root.get_representation_context(representation)) + copied_entities = root.copy_representation(element, new) data = geometry.duplicate_object_data(obj) if data: + geometry.copy_data_links(data, copied_entities) geometry.change_object_data(obj, data, is_global=True) - geometry.rename_object(data, geometry.get_representation_name(new_representation)) - geometry.link(new_representation, data) - geometry.reload_representation_item_ids(new_representation, data) + geometry.rename_object(data, geometry.get_representation_name(ifc.get_entity(data))) root.assign_body_styles(new, obj) collector.assign(obj) if root.is_element_a(new, "IfcOpeningElement"): diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index 291e0b2890..1f673b9521 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -387,6 +387,7 @@ class Geometry: def clear_cache(cls, element): pass def clear_modifiers(cls, obj): pass def clear_scale(cls, obj): pass + def copy_data_links(cls, data, copied_entities) -> None: pass def delete_data(cls, data): pass def delete_ifc_object(cls, obj): pass def delete_opening_object_placement(cls, opening): pass @@ -420,7 +421,6 @@ class Geometry: def record_object_position(cls, obj): pass def recreate_object_with_data(cls, obj, data): pass def reimport_element_representations(cls, obj, representation, apply_openings=True): pass - def reload_representation_item_ids(cls, representation, data) -> None: pass def remove_connection(cls, connection): pass def rename_object(cls, obj, name): pass def replace_object_data_globally(cls, old_data, new_data): pass diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index faf228e286..ef6a141513 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -259,10 +259,14 @@ class Geometry(bonsai.core.tool.Geometry): # a cylinder) so dissolving edges should not be allowed. mesh_element = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) if ( - mesh_element.is_a("IfcShapeRepresentation") - and ifcopenshell.util.representation.resolve_representation(mesh_element).RepresentationType - == "AdvancedBrep" - ) or mesh_element.is_a("IfcAdvancedBrep") or not obj.data: + ( + mesh_element.is_a("IfcShapeRepresentation") + and ifcopenshell.util.representation.resolve_representation(mesh_element).RepresentationType + == "AdvancedBrep" + ) + or mesh_element.is_a("IfcAdvancedBrep") + or not obj.data + ): return if hasattr(obj.data, "attributes") and (ios_edges_attribute := obj.data.attributes.get("ios_edges")): # Edges from a forced triangulation are stored as True in a boolean attribute on the mesh @@ -612,7 +616,7 @@ class Geometry(bonsai.core.tool.Geometry): @classmethod def get_representation_name(cls, representation: ifcopenshell.entity_instance) -> str: - return tool.Loader.get_mesh_name(representation.ContextOfItems.id(), representation.id()) + return tool.Loader.get_mesh_name(representation) @classmethod def get_styles( @@ -719,7 +723,7 @@ class Geometry(bonsai.core.tool.Geometry): if not tool.Loader.is_native_swept_disk_solid(element, representation): continue if curve is None: - mesh_name = tool.Loader.get_mesh_name(context.id(), representation.id()) + mesh_name = tool.Loader.get_mesh_name(representation) native_data = { "representation": representation, # TODO: calculate mapped item matrix. @@ -1729,8 +1733,14 @@ class Geometry(bonsai.core.tool.Geometry): return results @classmethod - def reload_representation_item_ids(cls, representation: ifcopenshell.entity_instance, data: bpy.types.Mesh) -> None: - data["ios_item_ids"] = [i["item"].id() for i in ifcopenshell.util.representation.resolve_items(representation)] + def copy_data_links(cls, data: bpy.types.Mesh, copied_entities: dict[int, ifcopenshell.entity_instance]) -> None: + representation = tool.Ifc.get_entity(data) + representation = copied_entities.get(representation.id(), representation) + tool.Ifc.link(representation, data) + if item_ids := data.get("ios_item_ids"): + data["ios_item_ids"] = [copied_entities.get(i, tool.Ifc.get().by_id(i)).id() for i in item_ids] + if item_ids := data.get("ios_edges_item_ids"): + data["ios_edges_item_ids"] = [copied_entities.get(i, tool.Ifc.get().by_id(i)).id() for i in item_ids] @classmethod def export_mesh_to_tessellation( diff --git a/src/bonsai/bonsai/tool/ifc.py b/src/bonsai/bonsai/tool/ifc.py index c52c86bee4..8144325584 100644 --- a/src/bonsai/bonsai/tool/ifc.py +++ b/src/bonsai/bonsai/tool/ifc.py @@ -102,7 +102,7 @@ class Ifc(bonsai.core.tool.Ifc): Return None if object is not linked to IFC or it's linked to non-existent element. """ ifc = IfcStore.get_file() - if not ifc: + if not ifc or not obj: return props = None @@ -110,6 +110,8 @@ class Ifc(bonsai.core.tool.Ifc): props = obj.BIMObjectProperties elif isinstance(obj, bpy.types.Material): props = obj.BIMStyleProperties + else: + props = obj.BIMMeshProperties if props and (ifc_definition_id := props.ifc_definition_id): try: diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index 6f9b8c84a5..b558d75be5 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -84,13 +84,12 @@ class Loader(bonsai.core.tool.Loader): @classmethod def get_mesh_name_from_shape(cls, geometry: ifcopenshell.geom.ShapeType) -> str: representation_id = cls.get_representation_id_from_shape(geometry) - representation = tool.Ifc.get().by_id(representation_id) - context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0 - return cls.get_mesh_name(context_id, representation_id) + return cls.get_mesh_name(tool.Ifc.get().by_id(representation_id)) @classmethod - def get_mesh_name(cls, context_id: int, representation_id: int) -> str: - return "{}/{}".format(context_id, representation_id) + def get_mesh_name(cls, representation: ifcopenshell.entity_instance) -> str: + context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0 + return "{}/{}".format(context_id, representation.id()) @classmethod def get_name(cls, element: ifcopenshell.entity_instance) -> str: diff --git a/src/bonsai/bonsai/tool/root.py b/src/bonsai/bonsai/tool/root.py index ab5f0a6fc8..b2af1b50d5 100644 --- a/src/bonsai/bonsai/tool/root.py +++ b/src/bonsai/bonsai/tool/root.py @@ -55,28 +55,38 @@ class Root(bonsai.core.tool.Root): ) @classmethod - def copy_representation(cls, source: ifcopenshell.entity_instance, dest: ifcopenshell.entity_instance) -> None: + def copy_representation( + cls, source: ifcopenshell.entity_instance, dest: ifcopenshell.entity_instance + ) -> dict[int, ifcopenshell.entity_instance]: def exclude_callback(attribute): return attribute.is_a("IfcProfileDef") and attribute.ProfileName + copied_entities: dict[int, ifcopenshell.entity_instance] = {} + if dest.is_a("IfcProduct"): if not source.Representation: - return + return copied_entities dest.Representation = ifcopenshell.util.element.copy_deep( tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"], exclude_callback=exclude_callback, + copied_entities=copied_entities, ) elif dest.is_a("IfcTypeProduct"): if not source.RepresentationMaps: - return + return copied_entities dest.RepresentationMaps = [ ifcopenshell.util.element.copy_deep( - tool.Ifc.get(), m, exclude=["IfcGeometricRepresentationContext"], exclude_callback=exclude_callback + tool.Ifc.get(), + m, + exclude=["IfcGeometricRepresentationContext"], + exclude_callback=exclude_callback, + copied_entities=copied_entities, ) for m in source.RepresentationMaps ] + return copied_entities @classmethod def does_type_have_representations(cls, element: ifcopenshell.entity_instance) -> bool: