From 755d178b6b4a3b65de1da095ff810229f063ea16 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 24 Jan 2025 17:28:45 +1100 Subject: [PATCH] Fix bug where adding / removing layers never triggered a regeneration of walls. Continue our slow, ongoing purge of listeners. --- .../bonsai/bim/module/material/operator.py | 48 +++++++++---------- src/bonsai/bonsai/bim/module/model/handler.py | 15 ++---- src/bonsai/bonsai/bim/module/model/slab.py | 40 +++++----------- src/bonsai/bonsai/bim/module/model/wall.py | 36 +++++++------- 4 files changed, 56 insertions(+), 83 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index 307047af4a..bf96f99fe4 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -30,6 +30,7 @@ import bonsai.core.material as core import bonsai.bim.module.model.profile as model_profile from bonsai.bim.ifc import IfcStore from typing import Any, Union, TYPE_CHECKING +from bonsai.bim.module.model import wall, slab class LoadMaterials(bpy.types.Operator): @@ -361,15 +362,14 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - self.file = IfcStore.get_file() - layer = ifcopenshell.api.run( - "material.add_layer", - self.file, - **{ - "layer_set": self.file.by_id(self.layer_set), - "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)), - }, + layer_set = tool.Ifc.get().by_id(self.layer_set) + ifcopenshell.api.material.add_layer( + tool.Ifc.get(), + layer_set=layer_set, + material=tool.Ifc.get().by_id(int(obj.BIMObjectMaterialProperties.material)), ) + slab.DumbSlabPlaner().regenerate_from_layer_set(layer_set) + wall.DumbWallPlaner().regenerate_from_layer_set(layer_set) class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator): @@ -405,11 +405,15 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): layer = tool.Ifc.get().by_id(self.layer) + material_sets = set(layer.ToMaterialLayerSet) for material_set in layer.ToMaterialLayerSet: if len(material_set.MaterialLayers) == 1: self.report({"ERROR"}, "At least one layer must exist") return {"CANCELLED"} ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer) + for material_set in material_sets: + slab.DumbSlabPlaner().regenerate_from_layer_set(material_set) + wall.DumbWallPlaner().regenerate_from_layer_set(material_set) class DuplicateLayer(bpy.types.Operator, tool.Ifc.Operator): @@ -707,32 +711,28 @@ class EditMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator): attributes = bonsai.bim.helper.export_attributes(props.material_set_item_attributes) if material.is_a("IfcMaterialConstituentSet"): - ifcopenshell.api.run( - "material.edit_constituent", + ifcopenshell.api.material.edit_constituent( self.file, - **{ - "constituent": self.file.by_id(self.material_set_item), - "attributes": attributes, - "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), - }, + constituent=self.file.by_id(self.material_set_item), + attributes=attributes, + material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), ) elif material.is_a("IfcMaterialLayerSet"): - ifcopenshell.api.run( - "material.edit_layer", + layer = self.file.by_id(self.material_set_item) + ifcopenshell.api.material.edit_layer( self.file, - **{ - "layer": self.file.by_id(self.material_set_item), - "attributes": attributes, - "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), - }, + layer=layer, + attributes=attributes, + material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), ) + slab.DumbSlabPlaner().regenerate_from_layer(layer) + wall.DumbWallPlaner().regenerate_from_layer(layer) elif material.is_a("IfcMaterialProfileSet"): profile_def = None if mprops.profiles: profile_def = tool.Ifc.get().by_id(int(mprops.profiles)) - ifcopenshell.api.run( - "material.edit_profile", + ifcopenshell.api.material.edit_profile( self.file, profile=self.file.by_id(self.material_set_item), attributes=attributes, diff --git a/src/bonsai/bonsai/bim/module/model/handler.py b/src/bonsai/bonsai/bim/module/model/handler.py index 305a76262b..3539a243ae 100644 --- a/src/bonsai/bonsai/bim/module/model/handler.py +++ b/src/bonsai/bonsai/bim/module/model/handler.py @@ -26,6 +26,9 @@ from bpy.app.handlers import persistent @persistent def load_post(*args): + # TODO: the goal is to slowly remove these API listeners. In hindsight it + # isn't a good idea because it leads to domino events being triggered. It's + # less buggy to explicitly code the logic in core. ifcopenshell.api.add_pre_listener("style.edit_presentation_style", "Bonsai.Root.SyncStyleName", root.sync_name) ifcopenshell.api.add_post_listener( @@ -42,22 +45,10 @@ def load_post(*args): product.regenerate_profile_usage, ) - ifcopenshell.api.add_post_listener( - "material.edit_layer", "Bonsai.DumbWall.RegenerateFromLayer", wall.DumbWallPlaner().regenerate_from_layer - ) ifcopenshell.api.add_post_listener( "type.assign_type", "Bonsai.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type ) - ifcopenshell.api.add_post_listener( - "material.add_layer", "Bonsai.DumbSlab.RegenerateFromLayer", slab.DumbSlabPlaner().regenerate_from_layer - ) - ifcopenshell.api.add_post_listener( - "material.remove_layer", "Bonsai.DumbSlab.RegenerateFromLayer", slab.DumbSlabPlaner().regenerate_from_layer - ) - ifcopenshell.api.add_post_listener( - "material.edit_layer", "Bonsai.DumbSlab.RegenerateFromLayer", slab.DumbSlabPlaner().regenerate_from_layer - ) ifcopenshell.api.add_post_listener( "material.edit_layer_usage", "Bonsai.DumbSlab.RegenerateFromLayerSetUsage", diff --git a/src/bonsai/bonsai/bim/module/model/slab.py b/src/bonsai/bonsai/bim/module/model/slab.py index 1a0cf97399..3f866e70e0 100644 --- a/src/bonsai/bonsai/bim/module/model/slab.py +++ b/src/bonsai/bonsai/bim/module/model/slab.py @@ -202,10 +202,7 @@ class DumbSlabPlaner: obj = bpy.context.active_object element = tool.Ifc.get_entity(obj) - if tool.Model.get_usage_type(element) != "LAYER3": - return - - # Called from materil.add_layer or material.remove_layer + # Called from material.add_layer or material.remove_layer material = ifcopenshell.util.element.get_material(element) material_set_usage = tool.Ifc.get().by_id(material.id()) if not getattr(material_set_usage, "ForLayerSet", False): @@ -218,38 +215,21 @@ class DumbSlabPlaner: self.change_thickness(element, total_thickness) - def regenerate_from_layer(self, usecase_path, ifc_file, settings): - self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) - - try: - # Called from materil.edit_layer - layer = settings["layer"] - thickness = settings["attributes"].get("LayerThickness") - if thickness is None: - return - layer_set = layer.ToMaterialLayerSet[0] - - except: - # Called from materil.add_layer or material.remove_layer - obj = bpy.context.active_object - element = tool.Ifc.get_entity(obj) - if not element: - return - material = ifcopenshell.util.element.get_material(element) - material_set_usage = tool.Ifc.get().by_id(material.id()) - if not getattr(material_set_usage, "ForLayerSet", False): - return - layer_set = material_set_usage.ForLayerSet + def regenerate_from_layer(self, layer: ifcopenshell.entity_instance) -> None: + for layer_set in layer.ToMaterialLayerSet: + self.regenerate_from_layer_set(layer_set) + def regenerate_from_layer_set(self, layer_set: ifcopenshell.entity_instance) -> None: + self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) total_thickness = sum([l.LayerThickness for l in layer_set.MaterialLayers]) if not total_thickness: return - for inverse in ifc_file.get_inverse(layer_set): + for inverse in tool.Ifc.get().get_inverse(layer_set): if not inverse.is_a("IfcMaterialLayerSetUsage") or inverse.LayerSetDirection != "AXIS3": continue - if ifc_file.schema == "IFC2X3": - for rel in ifc_file.get_inverse(inverse): + if tool.Ifc.get().schema == "IFC2X3": + for rel in tool.Ifc.get().get_inverse(inverse): if not rel.is_a("IfcRelAssociatesMaterial"): continue for element in rel.RelatedObjects: @@ -300,6 +280,8 @@ class DumbSlabPlaner: self.change_thickness(element, total_thickness) def change_thickness(self, element: ifcopenshell.entity_instance, thickness: float) -> None: + if tool.Model.get_usage_type(element) != "LAYER3": + return layer_params = tool.Model.get_material_layer_parameters(element) body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") obj = tool.Ifc.get_object(element) diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index 3f92331d78..a80c200556 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -860,26 +860,26 @@ class DumbWallGenerator: class DumbWallPlaner: - def regenerate_from_layer(self, usecase_path, ifc_file, settings): - if settings["attributes"].get("LayerThickness") is None: - return - walls = [] - layer = settings["layer"] + def regenerate_from_layer(self, layer: ifcopenshell.entity_instance) -> None: for layer_set in layer.ToMaterialLayerSet: - total_thickness = sum([l.LayerThickness for l in layer_set.MaterialLayers]) - if not total_thickness: + self.regenerate_from_layer_set(layer_set) + + def regenerate_from_layer_set(self, layer_set: ifcopenshell.entity_instance) -> None: + walls = [] + total_thickness = sum([l.LayerThickness for l in layer_set.MaterialLayers]) + if not total_thickness: + return + for inverse in tool.Ifc.get().get_inverse(layer_set): + if not inverse.is_a("IfcMaterialLayerSetUsage") or inverse.LayerSetDirection != "AXIS2": continue - for inverse in ifc_file.get_inverse(layer_set): - if not inverse.is_a("IfcMaterialLayerSetUsage") or inverse.LayerSetDirection != "AXIS2": - continue - if ifc_file.schema == "IFC2X3": - for rel in ifc_file.get_inverse(inverse): - if not rel.is_a("IfcRelAssociatesMaterial"): - continue - walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects]) - else: - for rel in inverse.AssociatedTo: - walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects]) + if tool.Ifc.get().schema == "IFC2X3": + for rel in tool.Ifc.get().get_inverse(inverse): + if not rel.is_a("IfcRelAssociatesMaterial"): + continue + walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects]) + else: + for rel in inverse.AssociatedTo: + walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects]) DumbWallRecalculator().recalculate([w for w in set(walls) if w]) def regenerate_from_type(self, usecase_path, ifc_file, settings):