From ab0549daa622f710e0a58dc17eb131e03222cebc Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 28 Jan 2025 17:19:08 +0500 Subject: [PATCH] Support editing representation's presentation layers from UI Example - https://imgur.com/a/QgtSUoq --- .../bonsai/bim/module/geometry/__init__.py | 2 + .../bonsai/bim/module/geometry/operator.py | 49 +++++++++++++++++++ src/bonsai/bonsai/bim/module/geometry/prop.py | 34 +++++++++++++ src/bonsai/bonsai/bim/module/geometry/ui.py | 40 ++++++++++++--- src/bonsai/bonsai/bim/module/layer/data.py | 13 +++-- 5 files changed, 129 insertions(+), 9 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/geometry/__init__.py b/src/bonsai/bonsai/bim/module/geometry/__init__.py index ba3143233b..533cdcd6bf 100644 --- a/src/bonsai/bonsai/bim/module/geometry/__init__.py +++ b/src/bonsai/bonsai/bim/module/geometry/__init__.py @@ -26,6 +26,7 @@ classes = ( operator.AddMeshlikeItem, operator.AddRepresentation, operator.AddSweptAreaSolidItem, + operator.AssignRepresentationLayer, operator.CopyRepresentation, operator.DisableEditingRepresentationItemShapeAspect, operator.DisableEditingRepresentationItemStyle, @@ -70,6 +71,7 @@ classes = ( operator.SwitchRepresentation, operator.UnassignRepresentationItemLayer, operator.UnassignRepresentationItemStyle, + operator.UnassignRepresentationLayer, operator.UpdateItemAttributes, operator.UpdateParametricRepresentation, operator.UpdateRepresentation, diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index ddd66baa1d..c2a65e68d5 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -3259,3 +3259,52 @@ class UnassignRepresentationItemLayer(bpy.types.Operator, tool.Ifc.Operator): ifcopenshell.api.layer.unassign_layer(ifc_file, [item], layer) bpy.ops.bim.enable_editing_representation_items() return {"FINISHED"} + + +class AssignRepresentationLayer(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.assign_representation_layer" + bl_label = "Assign Representation Layer" + bl_description = "Assign presentation layer to the active representation." + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + ifc_file = tool.Ifc.get() + obj = context.active_object + assert obj + props = tool.Geometry.get_object_geometry_props(obj) + new_layer = ifc_file.by_id(int(props.representation_layer)) + + representation = tool.Geometry.get_active_representation(obj) + assert representation + item_layers = representation.LayerAssignments + + # On practice enum doesn't display already assigned layers + # but let's double check. + if new_layer in item_layers: + props.is_adding_representation_layer = False + return {"FINISHED"} + + ifcopenshell.api.layer.assign_layer(ifc_file, [representation], new_layer) + props.is_adding_representation_layer = False + return {"FINISHED"} + + +class UnassignRepresentationLayer(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.unassign_representation_layer" + bl_label = "Unassign Representation Layer" + bl_description = "Unassign presentation layer from the active representation." + bl_options = {"REGISTER", "UNDO"} + + layer_id: bpy.props.IntProperty() + + def _execute(self, context): + ifc_file = tool.Ifc.get() + obj = context.active_object + assert obj + + representation = tool.Geometry.get_active_representation(obj) + assert representation + + layer = ifc_file.by_id(self.layer_id) + ifcopenshell.api.layer.unassign_layer(ifc_file, [representation], layer) + return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/geometry/prop.py b/src/bonsai/bonsai/bim/module/geometry/prop.py index 5407c24739..e18653a95b 100644 --- a/src/bonsai/bonsai/bim/module/geometry/prop.py +++ b/src/bonsai/bonsai/bim/module/geometry/prop.py @@ -113,6 +113,14 @@ def get_layers(self, context): return LayersData.data["layers_enum"] +def get_layers_no_active(self, context): + from bonsai.bim.module.layer.data import LayersData + + if not LayersData.is_loaded: + LayersData.load() + return LayersData.data["layers_enum_no_active"] + + def update_shape_aspect(self, context): shape_aspect_id = self.representation_item_shape_aspect attrs = self.shape_aspect_attrs @@ -169,6 +177,15 @@ class ShapeAspect(PropertyGroup): ) +def update_is_editing_representation_layer(self: "BIMObjectGeometryProperties", context: bpy.types.Context) -> None: + if self.is_adding_representation_layer: + return + + if "representation_layer" in self: + del self["representation_layer"] + del self["is_adding_representation_layer"] + + def update_is_editing_item_layer(self: "BIMObjectGeometryProperties", context: bpy.types.Context) -> None: if self.is_editing_item_layer: ifc_file = tool.Ifc.get() @@ -181,10 +198,25 @@ def update_is_editing_item_layer(self: "BIMObjectGeometryProperties", context: b if "representation_item_layer" in self: del self["representation_item_layer"] + del self["is_editing_item_layer"] class BIMObjectGeometryProperties(PropertyGroup): + # Representations UI. contexts: EnumProperty(items=get_contexts, name="Contexts") + is_adding_representation_layer: BoolProperty( + name="Is Adding Representation's Layer", + description="Toggle adding presentation layer for the representation.", + default=False, + ) + representation_layer: EnumProperty( + items=get_layers_no_active, + name="Representation's Presentation Layer", + description="Presentation layer to add.", + update=update_is_editing_representation_layer, + ) + + # Representation items UI. is_editing: BoolProperty(name="Is Editing", default=False) items: CollectionProperty(name="Representation Items", type=RepresentationItem) active_item_index: IntProperty(name="Active Representation Item Index") @@ -210,6 +242,8 @@ class BIMObjectGeometryProperties(PropertyGroup): if TYPE_CHECKING: contexts: str + is_adding_representation_layer: bool + representation_layer: str is_editing: bool items: bpy.types.bpy_prop_collection_idprop[RepresentationItem] active_item_index: int diff --git a/src/bonsai/bonsai/bim/module/geometry/ui.py b/src/bonsai/bonsai/bim/module/geometry/ui.py index 7bf70afb9a..80f5751eec 100644 --- a/src/bonsai/bonsai/bim/module/geometry/ui.py +++ b/src/bonsai/bonsai/bim/module/geometry/ui.py @@ -122,11 +122,16 @@ class BIM_PT_representations(Panel): return tool.Ifc.get() and (obj := tool.Blender.get_active_object()) and tool.Ifc.get_entity(obj) def draw(self, context): + layout = self.layout if not RepresentationsData.is_loaded: RepresentationsData.load() + obj = context.active_object + assert obj + props = tool.Geometry.get_object_geometry_props(obj) + row = self.layout.row(align=True) - prop_with_search(row, context.active_object.BIMGeometryProperties, "contexts", text="") + prop_with_search(row, props, "contexts", text="") row.operator("bim.add_representation", icon="ADD", text="") if not RepresentationsData.data["representations"]: @@ -150,15 +155,38 @@ class BIM_PT_representations(Panel): op.disable_opening_subtractions = False row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"] + # Presentation layers. self.layout.separator() if not LayersData.is_loaded: LayersData.load() - if LayersData.data["active_layers"]: - self.layout.label(text="Representation Presentation Layers:") - for layer_name in LayersData.data["active_layers"].values(): - self.layout.label(text=layer_name, icon="STICKY_UVS_LOC") + + if active_layers := LayersData.data["active_layers"]: + row = layout.row(align=True) + row.label(text="Representation Presentation Layers:") + if props.is_adding_representation_layer: + row = layout.row(align=True) + row.prop(props, "representation_layer", text="") + op = row.operator("bim.assign_representation_layer", icon="CHECKMARK", text="") + row.prop(props, "is_adding_representation_layer", icon="CANCEL", text="") + else: + row.prop(props, "is_adding_representation_layer", icon="ADD", text="") + + for layer_id, layer_name in active_layers.items(): + row = layout.row(align=True) + row.label(text=layer_name, icon="STICKY_UVS_LOC") + op = row.operator("bim.layer_ui_select", icon="ZOOM_SELECTED", text="") + op.layer_id = layer_id + op = row.operator("bim.unassign_representation_layer", icon="X", text="") + op.layer_id = layer_id else: - self.layout.label(text="Representation Has No Presentation Layers", icon="STICKY_UVS_LOC") + row = layout.row(align=True) + if props.is_adding_representation_layer: + row.prop(props, "representation_layer", text="") + op = row.operator("bim.assign_representation_layer", icon="CHECKMARK", text="") + row.prop(props, "is_adding_representation_layer", icon="CANCEL", text="") + else: + row.label(text="Representation Has No Presentation Layers", icon="STICKY_UVS_LOC") + row.prop(props, "is_adding_representation_layer", icon="GREASEPENCIL", text="") class BIM_PT_representation_items(Panel): diff --git a/src/bonsai/bonsai/bim/module/layer/data.py b/src/bonsai/bonsai/bim/module/layer/data.py index 4de8a9687f..f55b4c2900 100644 --- a/src/bonsai/bonsai/bim/module/layer/data.py +++ b/src/bonsai/bonsai/bim/module/layer/data.py @@ -38,7 +38,9 @@ class LayersData: } # After .layers(). cls.data["total_layers"] = cls.total_layers() + # After .layers() and .active_layers(). cls.data["layers_enum"] = cls.layers_enum() + cls.data["layers_enum_no_active"] = cls.layers_enum(skip_active=True) cls.is_loaded = True @@ -55,8 +57,13 @@ class LayersData: return results @classmethod - def layers_enum(cls) -> list[tuple[str, str, str]]: - return list((str(data["id"]), data["Name"], data["Description"] or "") for data in cls.data["layers"].values()) + def layers_enum(cls, skip_active: bool = False) -> list[tuple[str, str, str]]: + active_layers = cls.data["active_layers"] + return list( + (str(data["id"]), data["Name"], data["Description"] or "") + for data in cls.data["layers"].values() + if not skip_active or (data["id"] not in active_layers) + ) @classmethod def active_layers(cls) -> dict[int, str]: @@ -71,4 +78,4 @@ class LayersData: attr_name = "LayerAssignment" if attr_name is None: return results - return {layer.id(): layer.Name for layer in getattr(shape, attr_name)} + return {layer.id(): layer.Name or "Unnamed" for layer in getattr(shape, attr_name)}