diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 43b4f4cbd9..ede7d1d841 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -269,6 +269,7 @@ if bpy is not None: prop.BIMObjectProperties, prop.BIMMaterialProperties, prop.SweptSolid, + prop.ItemSlotMap, prop.BIMMeshProperties, prop.BIMCameraProperties, prop.BIMTextProperties, diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 4728bc3e04..3928df32ee 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -1323,6 +1323,7 @@ class IfcExporter: self.template_file = "{}template.ifc".format(ifc_export_settings.schema_dir) self.ifc_export_settings = ifc_export_settings self.ifc_parser = ifc_parser + self.roundtrip_id_map = {} def export(self, selected_objects): self.schema_version = self.ifc_export_settings.schema @@ -2002,27 +2003,41 @@ class IfcExporter: def create_styled_items(self): for styled_item in self.ifc_parser.styled_items: - product = self.ifc_parser.products[ - self.ifc_parser.get_product_index_from_raw_name(styled_item["related_product_name"]) - ] - material_slots = {} - if product["ifc"].Representation: - # This is a simplification, which works since we are currently in a controlled environment where the - # BlenderBIM Add-on controls how data is structured during export. When we implement full IFC - # round-tripping, this simplification can no longer apply. - for representation in product["ifc"].Representation.Representations: - # At the moment, until we implement full support for round-tripping contexts, we assume that styled - # items only apply to the body context. This is therefore an incomplete implementation and may break - # in edge cases. - if representation.RepresentationIdentifier != "Body": - continue - for i, item in enumerate(self.get_geometric_representation_items(representation)): - if i >= len(product["raw"].material_slots): - i = 0 - material_slots[product["raw"].material_slots[i].name] = item - for styled_item_name, representation_item in material_slots.items(): - if styled_item_name == styled_item["attributes"]["Name"]: - styled_item["ifc"] = self.create_styled_item(styled_item, representation_item) + self.process_styled_item(styled_item) + + def process_styled_item(self, styled_item): + product = self.ifc_parser.products[ + self.ifc_parser.get_product_index_from_raw_name(styled_item["related_product_name"]) + ] + + if not product["ifc"].Representation: + return + + material_slots = [] + # This is a simplification, which works since we are currently in a controlled environment where the + # BlenderBIM Add-on controls how data is structured during export. When we implement full IFC + # round-tripping, this simplification can no longer apply. + for representation in product["ifc"].Representation.Representations: + # At the moment, until we implement full support for round-tripping contexts, we assume that styled + # items only apply to the body context. This is therefore an incomplete implementation and may break + # in edge cases. + if representation.RepresentationIdentifier != "Body": + continue + if product["raw"].data.BIMMeshProperties.ifc_definition_id: + # For native roundtripping, each slot could be a one to many relationship to items + for item in self.get_geometric_representation_items(representation): + original_id = self.roundtrip_id_map[item.id()] + i = product["raw"].data.BIMMeshProperties.ifc_item_ids.get(str(original_id)).slot_index + material_slots.append((product["raw"].material_slots[i].name, item)) + else: + # For Blender, each slot represents a geometric representation item + for i, item in enumerate(self.get_geometric_representation_items(representation)): + if i >= len(product["raw"].material_slots): + i = 0 + material_slots.append((product["raw"].material_slots[i].name, item)) + for styled_item_name, representation_item in material_slots: + if styled_item_name == styled_item["attributes"]["Name"]: + styled_item["ifc"] = self.create_styled_item(styled_item, representation_item) def get_geometric_representation_items(self, representation): results = [] @@ -2389,6 +2404,8 @@ class IfcExporter: added_element = migrator.migrate(element, self.file) if added_element.is_a("IfcGeometricRepresentationContext"): substitutions["contexts"].append(added_element) + elif added_element.is_a("IfcGeometricRepresentationItem"): + self.roundtrip_id_map[added_element.id()] = element.id() for element in substitutions["contexts"]: new_element = self.ifc_rep_context[representation["context"]][representation["subcontext"]][ diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 1a3cc8249c..8adf6a1c1e 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -38,13 +38,11 @@ class MaterialCreator: def __init__(self, ifc_import_settings, ifc_importer): self.mesh = None self.materials = {} - self.current_object_styles = [] self.parsed_meshes = [] self.ifc_import_settings = ifc_import_settings self.ifc_importer = ifc_importer def create(self, element, obj, mesh): - self.current_object_styles = [] self.obj = obj self.mesh = mesh if (hasattr(element, "Representation") and not element.Representation) or ( @@ -84,10 +82,14 @@ class MaterialCreator: if not item.StyledByItem: return + item_id = self.mesh.BIMMeshProperties.ifc_item_ids.add() + item_id.name = str(item.id()) + styled_item = item.StyledByItem[0] style_name = self.get_style_name(styled_item) - if style_name in self.current_object_styles: + if self.mesh.materials.get(style_name): + item_id.slot_index = self.mesh.materials.find(style_name) return True style = bpy.data.materials.get(style_name) @@ -97,8 +99,7 @@ class MaterialCreator: self.parse_styled_item(styled_item, style) self.assign_style_to_mesh(style) - item_id = self.mesh.BIMMeshProperties.ifc_item_ids.add() - item_id.name = str(item.id()) + item_id.slot_index = len(self.mesh.materials) - 1 return True def assign_material_slots_to_faces(self, obj): @@ -327,7 +328,6 @@ class MaterialCreator: if not self.mesh: return self.mesh.materials.append(material) - self.current_object_styles.append(material.name) class IfcImporter: diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 6ade7ab6d7..577c4b9870 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1716,6 +1716,11 @@ class RepresentationItem(PropertyGroup): vgroup: StringProperty(name="Vertex Group") +class ItemSlotMap(PropertyGroup): + name: StringProperty(name="Item Element ID") + slot_index: IntProperty(name="Material Slot Index") + + class BIMMeshProperties(PropertyGroup): is_native: BoolProperty(name="Is Native", default=False) is_swept_solid: BoolProperty(name="Is Swept Solid") @@ -1727,4 +1732,4 @@ class BIMMeshProperties(PropertyGroup): ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) active_representation_item_index: IntProperty(name="Active Representation Item Index") presentation_layer_index: IntProperty(name="Presentation Layer Index", default=-1) - ifc_item_ids: CollectionProperty(name="IFC Definition ID", type=StrProperty) + ifc_item_ids: CollectionProperty(name="IFC Definition ID", type=ItemSlotMap)