diff --git a/src/bonsai/bonsai/bim/module/drawing/workspace.py b/src/bonsai/bonsai/bim/module/drawing/workspace.py index 5796c20303..bc7463073e 100644 --- a/src/bonsai/bonsai/bim/module/drawing/workspace.py +++ b/src/bonsai/bonsai/bim/module/drawing/workspace.py @@ -168,7 +168,7 @@ def create_annotation_occurrence(context): ) assert element - bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) + bonsai.core.type.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type) ifcopenshell.api.group.assign_group(ifc_file, group=tool.Drawing.get_drawing_group(drawing), products=[element]) tool.Collector.assign(obj) diff --git a/src/bonsai/bonsai/bim/module/model/handler.py b/src/bonsai/bonsai/bim/module/model/handler.py index 26a4863837..47075cdcfa 100644 --- a/src/bonsai/bonsai/bim/module/model/handler.py +++ b/src/bonsai/bonsai/bim/module/model/handler.py @@ -37,24 +37,11 @@ def load_post(*args): "sequence.edit_task_time", "Bonsai.Task.CalculateQuantities", task.calculate_quantities ) - ifcopenshell.api.add_post_listener( - "type.assign_type", "Bonsai.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type - ) - - ifcopenshell.api.add_post_listener( - "type.assign_type", "Bonsai.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type - ) - ifcopenshell.api.add_post_listener( "material.edit_profile", "Bonsai.DumbProfile.RegenerateFromProfile", profile.DumbProfileRegenerator().regenerate_from_profile, ) - ifcopenshell.api.add_post_listener( - "type.assign_type", - "Bonsai.DumbProfile.RegenerateFromType", - profile.DumbProfileRegenerator().regenerate_from_type, - ) ifcopenshell.api.add_post_listener( "type.assign_type", diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index 822342ab75..8ca5712116 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -380,7 +380,7 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator): ifc_class=instance_class, should_add_representation=False, ) - bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) + bonsai.core.type.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type) rprops = tool.Root.get_root_props() ifc_context = None @@ -433,7 +433,7 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator): ) element = tool.Ifc.get_entity(obj) - bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) + bonsai.core.type.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type) if existing_context: representation = ifcopenshell.util.representation.get_representation(element, existing_context) diff --git a/src/bonsai/bonsai/bim/module/model/profile.py b/src/bonsai/bonsai/bim/module/model/profile.py index 124a70707f..9271f2acc1 100644 --- a/src/bonsai/bonsai/bim/module/model/profile.py +++ b/src/bonsai/bonsai/bim/module/model/profile.py @@ -267,22 +267,6 @@ class DumbProfileRegenerator: results.extend(inverse.RelatedObjects) return results - def regenerate_from_type(self, usecase_path, ifc_file, settings): - relating_type = settings["relating_type"] - - new_material = ifcopenshell.util.element.get_material(relating_type) - if not new_material or not new_material.is_a("IfcMaterialProfileSet"): - return - - for related_object in settings["related_objects"]: - self._regenerate_from_type(related_object) - - def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None: - obj = tool.Ifc.get_object(related_object) - if not obj or not tool.Geometry.get_active_representation(obj): - return - DumbProfileRecalculator().recalculate([obj]) - class ExtendProfile(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.extend_profile" diff --git a/src/bonsai/bonsai/bim/module/model/slab.py b/src/bonsai/bonsai/bim/module/model/slab.py index 1d673aca8e..509d24a60c 100644 --- a/src/bonsai/bonsai/bim/module/model/slab.py +++ b/src/bonsai/bonsai/bim/module/model/slab.py @@ -243,38 +243,6 @@ class DumbSlabPlaner: for element in rel.RelatedObjects: self.change_thickness(element, total_thickness, preserve_offset=True) - def regenerate_from_type(self, usecase_path, ifc_file, settings): - relating_type = settings["relating_type"] - - new_material = ifcopenshell.util.element.get_material(relating_type) - if not new_material or not new_material.is_a("IfcMaterialLayerSet"): - return - - parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric") - layer_set_direction = None - if parametric: - layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction) - new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers]) - - self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) - for related_object in settings["related_objects"]: - self._regenerate_from_type(related_object, layer_set_direction, new_thickness) - - def _regenerate_from_type( - self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float - ) -> None: - obj = tool.Ifc.get_object(related_object) - if not obj or not tool.Geometry.get_active_representation(obj): - return - - material = ifcopenshell.util.element.get_material(related_object) - if not material or not material.is_a("IfcMaterialLayerSetUsage"): - return - if layer_set_direction: - material.LayerSetDirection = layer_set_direction - if material.LayerSetDirection == "AXIS3": - self.change_thickness(related_object, new_thickness) - def regenerate_from_occurence(self, element, material_set_usage): self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) layer_set = material_set_usage.ForLayerSet diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index 9c94a27af0..5276e64692 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -1132,37 +1132,6 @@ class DumbWallPlaner: walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects]) tool.Model.recalculate_walls([w for w in set(walls) if w]) - def regenerate_from_type(self, usecase_path, ifc_file, settings): - relating_type = settings["relating_type"] - - new_material = ifcopenshell.util.element.get_material(relating_type) - if not new_material or not new_material.is_a("IfcMaterialLayerSet"): - return - - parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric") - layer_set_direction = None - if parametric: - layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction) - - self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) - for related_object in settings["related_objects"]: - self._regenerate_from_type(related_object, layer_set_direction) - - def _regenerate_from_type( - self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str] - ) -> None: - obj = tool.Ifc.get_object(related_object) - if not obj or not tool.Geometry.get_active_representation(obj): - return - - material = ifcopenshell.util.element.get_material(related_object) - if not material or not material.is_a("IfcMaterialLayerSetUsage"): - return - if layer_set_direction: - material.LayerSetDirection = layer_set_direction - if material.LayerSetDirection == "AXIS2": - tool.Model.recalculate_walls([obj]) - class DumbWallJoiner: def __init__(self): diff --git a/src/bonsai/bonsai/bim/module/type/operator.py b/src/bonsai/bonsai/bim/module/type/operator.py index 66c87177f9..8d24989f12 100644 --- a/src/bonsai/bonsai/bim/module/type/operator.py +++ b/src/bonsai/bonsai/bim/module/type/operator.py @@ -74,7 +74,7 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator): element = tool.Ifc.get_entity(obj) if not element or not element.is_a("IfcObject"): continue - core.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) + core.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type) # Switch to the drawing's target view if available if active_target_view and element.Representation: @@ -384,7 +384,7 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator): for selected_obj in selected_objects: selected_element = tool.Ifc.get_entity(selected_obj) if selected_element and selected_element.is_a("IfcObject"): - core.assign_type(tool.Ifc, tool.Type, element=selected_element, type=new) + core.assign_type(tool.Ifc, tool.Model, tool.Type, element=selected_element, type=new) if prefs.occurrence_name_style == "TYPE": selected_obj.name = tool.Model.generate_occurrence_name(new, selected_element.is_a()) diff --git a/src/bonsai/bonsai/core/type.py b/src/bonsai/bonsai/core/type.py index 2467f124d3..a203b9ff76 100644 --- a/src/bonsai/bonsai/core/type.py +++ b/src/bonsai/bonsai/core/type.py @@ -31,14 +31,22 @@ if TYPE_CHECKING: def assign_type( ifc: type[tool.Ifc], + model: type[tool.Model], type_tool: type[tool.Type], element: ifcopenshell.entity_instance, type: ifcopenshell.entity_instance, ) -> None: + usage_attributes = type_tool.record_material_usage_attributes(element) ifc.run("type.assign_type", related_objects=[element], relating_type=type) obj = ifc.get_object(element) - if type_tool.has_material_usage(element): - pass # for now, representation regeneration handled by API listeners + if (usage := model.get_usage_type(type)) and usage_attributes: + type_tool.restore_material_usage_attributes(element, usage_attributes) + if (usage := model.get_usage_type(type)) == "PROFILE": + model.regenerate_profile(obj) + elif usage == "LAYER2": + model.recalculate_walls([obj]) + elif usage == "LAYER3": + model.regenerate_slab(obj) else: type_data = type_tool.get_object_data(ifc.get_object(type)) if type_data: diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index f268151faa..51d06c1c18 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -1279,7 +1279,7 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def run_type_assign_type(cls, element: ifcopenshell.entity_instance, relating_type: ifcopenshell.entity_instance): - return bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) + return bonsai.core.type.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type) @classmethod def reload_representation(cls, obj: bpy.types.Object, representation: ifcopenshell.entity_instance): diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index bc6357cb0d..08a0a0fd85 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -2743,6 +2743,21 @@ class Model(bonsai.core.tool.Model): cls.recreate_wall(element, wall) + @classmethod + def regenerate_slab(cls, obj: bpy.types.Object) -> None: + from bonsai.bim.module.model.slab import DumbSlabPlaner + + element = tool.Ifc.get_entity(obj) + material_set = ifcopenshell.util.element.get_material(element, should_skip_usage=True) + new_thickness = sum([l.LayerThickness for l in material_set.MaterialLayers]) + DumbSlabPlaner().change_thickness(element, new_thickness) + + @classmethod + def regenerate_profile(cls, obj: bpy.types.Object) -> None: + from bonsai.bim.module.model.profile import DumbProfileRecalculator + + DumbProfileRecalculator().recalculate([obj]) + @classmethod def run_ifcsverchok_graph_on_bonsai_file(cls, node_tree: sverchok.node_tree.SverchCustomTree) -> None: from ifcsverchok.ifcstore import SvIfcStore diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index 922c6cc9ad..70a92f4cff 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -1177,6 +1177,7 @@ class Spatial(bonsai.core.tool.Spatial): @classmethod def assign_type_to_obj(cls, obj: bpy.types.Object) -> None: + # TODO this code looks in the wrong spot and suspicious props = tool.Model.get_model_props() ifc_file = tool.Ifc.get() relating_type_id = props.relating_type_id @@ -1196,7 +1197,7 @@ class Spatial(bonsai.core.tool.Spatial): element: ifcopenshell.entity_instance, relating_type: ifcopenshell.entity_instance, ) -> None: - bonsai.core.type.assign_type(ifc, type, element=element, type=relating_type) + bonsai.core.type.assign_type(ifc, tool.Model, type, element=element, type=relating_type) @classmethod def regen_obj_representation(cls, obj: bpy.types.Object, body: ifcopenshell.entity_instance) -> None: diff --git a/src/bonsai/bonsai/tool/type.py b/src/bonsai/bonsai/tool/type.py index 95474e57b7..84a349f288 100644 --- a/src/bonsai/bonsai/tool/type.py +++ b/src/bonsai/bonsai/tool/type.py @@ -143,3 +143,20 @@ class Type(bonsai.core.tool.Type): obj=obj, representation=representation, ) + + @classmethod + def record_material_usage_attributes(cls, element: ifcopenshell.entity_instance) -> dict | None: + if (material := ifcopenshell.util.element.get_material(element)) and "Usage" in material.is_a(): + return material.get_info() + + @classmethod + def restore_material_usage_attributes(cls, element: ifcopenshell.entity_instance, usage_attributes: dict) -> None: + if (material := ifcopenshell.util.element.get_material(element)) and material.is_a() == usage_attributes[ + "type" + ]: + if usage_attributes["type"] == "IfcMaterialLayerSetUsage": + for attr in ("LayerSetDirection", "DirectionSense", "OffsetFromReferenceLine", "ReferenceExtent"): + setattr(material, attr, usage_attributes.get(attr)) + elif usage_attributes["type"] == "IfcMaterialProfileSetUsage": + for attr in ("CardinalPoint", "ReferenceExtent"): + setattr(material, attr, usage_attributes.get(attr)) diff --git a/src/bonsai/test/core/test_type.py b/src/bonsai/test/core/test_type.py index ffdb571148..0987ab3d9e 100644 --- a/src/bonsai/test/core/test_type.py +++ b/src/bonsai/test/core/test_type.py @@ -17,11 +17,11 @@ # along with Bonsai. If not, see . import bonsai.core.type as subject -from test.core.bootstrap import geometry, ifc, type +from test.core.bootstrap import ifc, model, type, geometry class TestAssignType: - def test_assigning_and_switching_to_an_existing_type_data(self, ifc, type): + def test_assigning_and_switching_to_an_existing_type_data(self, ifc, model, type): ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called() type.has_material_usage("element").should_be_called().will_return(False) ifc.get_object("type").should_be_called().will_return("type_obj") @@ -29,7 +29,7 @@ class TestAssignType: type.change_object_data("obj", "type_obj_data", is_global=False).should_be_called() ifc.get_object("element").should_be_called().will_return("obj") type.disable_editing("obj").should_be_called() - subject.assign_type(ifc, type, element="element", type="type") + subject.assign_type(ifc, model, type, element="element", type="type") def test_assigning_and_not_changing_data_if_the_type_has_no_data(self, ifc, type): ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called() @@ -38,7 +38,7 @@ class TestAssignType: type.get_object_data("type_obj").should_be_called().will_return(None) ifc.get_object("element").should_be_called().will_return("obj") type.disable_editing("obj").should_be_called() - subject.assign_type(ifc, type, element="element", type="type") + subject.assign_type(ifc, model, type, element="element", type="type") class TestPurgeUnusedTypes: