From d23cfcb2cfab6b556d0ee9675e17386114ed4764 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 15 Feb 2025 18:31:25 +1100 Subject: [PATCH] Fix #2981. Standard case windows can now have different materials and implement shape aspects. --- src/bonsai/bonsai/bim/module/model/prop.py | 5 ++ src/bonsai/bonsai/bim/module/model/ui.py | 8 +++- src/bonsai/bonsai/bim/module/model/window.py | 46 +++++++++++-------- .../api/geometry/add_window_representation.py | 35 +++++++++++++- 4 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index 779de2b553..abf6fa9517 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -521,6 +521,11 @@ class BIMWindowProperties(PropertyGroup): name="Frame Thickness", size=3, default=[0.035] * 3, subtype="TRANSLATION" ) + # Material properties + lining_material: bpy.props.EnumProperty(name="Lining Material", items=get_materials, options=set()) + framing_material: bpy.props.EnumProperty(name="Framing Material", items=get_materials, options=set()) + glazing_material: bpy.props.EnumProperty(name="Glazing Material", items=get_materials, options=set()) + def get_general_kwargs(self, convert_to_project_units=False): kwargs = { "window_type": self.window_type, diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index f7a80ef616..91c4e3a0d1 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -448,8 +448,13 @@ class BIM_PT_window(bpy.types.Panel): for panel_i in range(number_of_panels): cols[panel_i + 1].prop(props, prop, index=panel_i, text="") - update_window_modifier_bmesh(context) + self.layout.use_property_split = True + self.layout.label(text="Material Properties") + self.layout.prop(props, "lining_material") + self.layout.prop(props, "framing_material", text="Panel Material") + self.layout.prop(props, "glazing_material") + update_window_modifier_bmesh(context) else: row.operator("bim.enable_editing_window", icon="GREASEPENCIL", text="") row.operator("bim.remove_window", icon="X", text="") @@ -492,7 +497,6 @@ class BIM_PT_window(bpy.types.Panel): prop_value = panel_props[prop_name][panel_i] r.label(text=str(prop_value)) r = cols[panel_i + 1].row() - else: row = self.layout.row() row.label(text="No Window Found") diff --git a/src/bonsai/bonsai/bim/module/model/window.py b/src/bonsai/bonsai/bim/module/model/window.py index c82847eacd..e940ec194d 100644 --- a/src/bonsai/bonsai/bim/module/model/window.py +++ b/src/bonsai/bonsai/bim/module/model/window.py @@ -73,7 +73,7 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None: } representation_data["panel_properties"].append(panel_data) - previously_active_context = tool.Geometry.get_active_representation_context(obj) + active_context = tool.Geometry.get_active_representation_context(obj) # ELEVATION_VIEW representation profile = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Profile", "ELEVATION_VIEW") @@ -88,8 +88,25 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None: # (Model/Body defined only BEFORE Plan/Body to prevent #2744) body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW") representation_data["context"] = body + representation_data["part_of_product"] = ifcopenshell.util.representation.get_part_of_product(element, body) model_representation = ifcopenshell.api.run("geometry.add_window_representation", ifc_file, **representation_data) + representation_data["part_of_product"] = None tool.Model.replace_object_ifc_representation(body, obj, model_representation) + if fallback_material := (int(props.lining_material) or int(props.framing_material) or int(props.glazing_material)): + ifcopenshell.api.material.set_shape_aspect_constituents( + ifc_file, + element=element, + context=body, + materials={ + "Lining": tool.Ifc.get().by_id(int(props.lining_material) or fallback_material), + "Framing": tool.Ifc.get().by_id(int(props.framing_material) or fallback_material), + "Glazing": tool.Ifc.get().by_id(int(props.glazing_material) or fallback_material), + }, + ) + elif material := ifcopenshell.util.element.get_material(element): + ifcopenshell.api.material.unassign_material(ifc_file, products=[element]) + if not material.is_a("IfcMaterial") and not ifc_file.get_total_inverses(material): + ifcopenshell.api.material.remove_material_set(ifc_file, material=material) # PLAN_VIEW representation plan = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "Body", "PLAN_VIEW") @@ -100,24 +117,15 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None: ) tool.Model.replace_object_ifc_representation(plan, obj, plan_representation) - # adding switch representation at the end instead of changing order of representations - # to prevent #2744 - if tool.Geometry.get_active_representation_context(obj) != previously_active_context: - previously_active_representation = ifcopenshell.util.representation.get_representation( - element, - previously_active_context.ContextType, - previously_active_context.ContextIdentifier, - previously_active_context.TargetView, - ) - bonsai.core.geometry.switch_representation( - tool.Ifc, - tool.Geometry, - obj=obj, - representation=previously_active_representation, - should_reload=True, - is_global=True, - should_sync_changes_first=True, - ) + bonsai.core.geometry.switch_representation( + tool.Ifc, + tool.Geometry, + obj=obj, + representation=ifcopenshell.util.representation.get_representation(element, active_context), + should_reload=True, + is_global=True, + should_sync_changes_first=True, + ) # type attributes if tool.Ifc.get_schema() != "IFC2X3": diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py index b6ba723695..22fb99d583 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py @@ -232,7 +232,7 @@ def create_ifc_window( output_items = (lining_items, frame_extruded_items, [glass]) builder.translate(chain(*output_items), position) - return output_items + return {"Lining": lining_items, "Framing": frame_extruded_items, "Glazing": [glass]} # we use dataclass as we need default values for arguments @@ -363,6 +363,7 @@ def add_window_representation( partition_type: WINDOW_TYPE = "SINGLE_PANEL", lining_properties: Optional[Union[WindowLiningProperties, dict[str, Any]]] = None, panel_properties: Optional[list[Union[WindowPanelProperties, dict[str, Any]]]] = None, + part_of_product: Optional[ifcopenshell.entity_instance] = None, unit_scale: Optional[float] = None, ) -> ifcopenshell.entity_instance: """units in usecase_settings expected to be in ifc project units @@ -415,6 +416,7 @@ def add_window_representation( "partition_type": partition_type, "lining_properties": lining_properties, "panel_properties": panel_properties, + "part_of_product": part_of_product, } ) @@ -440,6 +442,9 @@ class Usecase: accumulated_height = [0] * len(panel_schema[0]) built_panels: list[int] = [] window_items: list[ifcopenshell.entity_instance] = [] + lining_items: list[ifcopenshell.entity_instance] = [] + framing_items: list[ifcopenshell.entity_instance] = [] + glazing_items: list[ifcopenshell.entity_instance] = [] lining_props: dict[str, Any] = self.settings["lining_properties"] lining_thickness: float = lining_props["LiningThickness"] @@ -728,13 +733,39 @@ class Usecase: x_offsets, ) built_panels.append(panel_i) - window_items.extend(chain(*current_window_items)) + window_items.extend(chain(*current_window_items.values())) + lining_items.extend(current_window_items["Lining"]) + framing_items.extend(current_window_items["Framing"]) + glazing_items.extend(current_window_items["Glazing"]) accumulated_height[column_i] += panel_height accumulated_width += panel_width builder.translate(window_items, (0, lining_offset, 0)) # wall offset representation = builder.get_representation(self.settings["context"], window_items) + if self.settings["part_of_product"]: + ifcopenshell.api.geometry.add_shape_aspect( + self.file, + "Lining", + items=lining_items, + representation=representation, + part_of_product=self.settings["part_of_product"], + ) + ifcopenshell.api.geometry.add_shape_aspect( + self.file, + "Framing", + items=framing_items, + representation=representation, + part_of_product=self.settings["part_of_product"], + ) + ifcopenshell.api.geometry.add_shape_aspect( + self.file, + "Glazing", + items=glazing_items, + representation=representation, + part_of_product=self.settings["part_of_product"], + ) + return representation @overload