diff --git a/src/blenderbim/blenderbim/bim/module/group/__init__.py b/src/blenderbim/blenderbim/bim/module/group/__init__.py index 1b765a0a94..bc16858fd2 100644 --- a/src/blenderbim/blenderbim/bim/module/group/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/group/__init__.py @@ -21,8 +21,10 @@ from . import ui, prop, operator classes = ( operator.LoadGroups, + operator.ToggleGroup, operator.DisableGroupEditingUI, operator.AddGroup, + operator.AddGroupToGroup, operator.EditGroup, operator.RemoveGroup, operator.ToggleAssigningGroup, @@ -32,6 +34,7 @@ classes = ( operator.DisableEditingGroup, operator.SelectGroupProducts, operator.UpdateGroup, + prop.ExpandedGroups, prop.Group, prop.BIMGroupProperties, ui.BIM_PT_groups, @@ -43,7 +46,9 @@ classes = ( def register(): bpy.types.Scene.BIMGroupProperties = bpy.props.PointerProperty(type=prop.BIMGroupProperties) + bpy.types.Scene.ExpandedGroups = bpy.props.PointerProperty(type=prop.ExpandedGroups) def unregister(): del bpy.types.Scene.BIMGroupProperties + del bpy.types.Scene.ExpandedGroups diff --git a/src/blenderbim/blenderbim/bim/module/group/operator.py b/src/blenderbim/blenderbim/bim/module/group/operator.py index c48ea81bcd..aee07af28a 100644 --- a/src/blenderbim/blenderbim/bim/module/group/operator.py +++ b/src/blenderbim/blenderbim/bim/module/group/operator.py @@ -23,23 +23,73 @@ import blenderbim.bim.helper from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.group.data import Data from ifcopenshell.util.selector import Selector +import json class LoadGroups(bpy.types.Operator): bl_idname = "bim.load_groups" bl_label = "Load Groups" bl_options = {"REGISTER", "UNDO"} + is_refresh: bpy.props.BoolProperty(default=False) def execute(self, context): - props = context.scene.BIMGroupProperties - props.groups.clear() + self.props = context.scene.BIMGroupProperties + self.expanded_groups = json.loads(context.scene.ExpandedGroups.json_string) + self.props.groups.clear() + self.ifc = IfcStore.get_file() + if not self.is_refresh: + context.scene.ExpandedGroups.json_string = "{}" + for ifc_definition_id, group in Data.groups.items(): - new = props.groups.add() - new.ifc_definition_id = ifc_definition_id - new.name = group["Name"] - new.selection_query = group["Description"].split("*selector*")[1] if group["Description"] else "" - props.is_editing = True + if not group["HasAssignments"]: + new = self.props.groups.add() + new.ifc_definition_id = ifc_definition_id + new.name = group["Name"] + new.selection_query = group["Description"].split("*selector*")[1] if group["Description"] else "" + new.has_children = True if len(group["IsGroupedBy"]) != 0 else False + new.tree_depth = 0 + + if self.is_refresh: + self.recursively_load_sub_groups(ifc_definition_id, new) + + self.props.is_editing = True bpy.ops.bim.disable_editing_group() + Data.load(IfcStore.get_file()) + return {"FINISHED"} + + def recursively_load_sub_groups(self, ifc_definition_id, parent): + sub_groups = ([k for k,v in Data.products.items() if self.ifc.by_id(k).is_a("IfcGroup") and ifc_definition_id in v]) + if str(ifc_definition_id) not in self.expanded_groups or sub_groups == []: + return + else: + parent.is_expanded = True + for group in sub_groups: + new = self.props.groups.add() + new.ifc_definition_id = group + new.name = Data.groups[group]["Name"] + new.selection_query = Data.groups[group]["Description"].split("*selector*")[1] if Data.groups[group]["Description"] else "" + new.has_children = True if len(Data.groups[group]["IsGroupedBy"]) != 0 else False + new.tree_depth = parent.tree_depth + 1 + self.recursively_load_sub_groups(new.ifc_definition_id, new) + + +class ToggleGroup(bpy.types.Operator): + bl_idname = "bim.toggle_group" + bl_label = "Toggle Group" + bl_options = {"REGISTER", "UNDO"} + ifc_definition_id: bpy.props.IntProperty() + index: bpy.props.IntProperty() + option: bpy.props.StringProperty(name="Expand or Collapse") + + def execute(self, context): + context.scene.BIMGroupProperties.groups[self.index].is_expanded = True if self.option == "Expand" else False + json_string = json.loads(context.scene.ExpandedGroups.json_string) + if self.option == "Expand": + json_string.setdefault(str(self.ifc_definition_id), None) + else: + json_string.pop(str(self.ifc_definition_id), None) + context.scene.ExpandedGroups.json_string = json.dumps(json_string) + bpy.ops.bim.load_groups(is_refresh=True) return {"FINISHED"} @@ -56,7 +106,7 @@ class DisableGroupEditingUI(bpy.types.Operator): class AddGroup(bpy.types.Operator): bl_idname = "bim.add_group" - bl_label = "Add Group" + bl_label = "Add New Group" bl_options = {"REGISTER", "UNDO"} def execute(self, context): @@ -65,15 +115,43 @@ class AddGroup(bpy.types.Operator): def _execute(self, context): result = ifcopenshell.api.run("group.add_group", IfcStore.get_file()) Data.load(IfcStore.get_file()) - bpy.ops.bim.load_groups() + + bpy.ops.bim.load_groups(is_refresh=True) bpy.ops.bim.enable_editing_group(group=result.id()) return {"FINISHED"} + + +class AddGroupToGroup(bpy.types.Operator): + bl_idname = "bim.add_group_to_group" + bl_label = "Add Group to Group" + bl_options = {"REGISTER", "UNDO"} + group: bpy.props.IntProperty(name="Group ID") + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + self.file = IfcStore.get_file() + result = ifcopenshell.api.run("group.add_group", self.file) + ifcopenshell.api.run( + "group.assign_group", + IfcStore.get_file(), + **{ + "product": [result], + "group" : self.file.by_id(self.group) + } + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_groups(is_refresh=True) + bpy.ops.bim.disable_group_editing_ui() + return {"FINISHED"} class EditGroup(bpy.types.Operator): bl_idname = "bim.edit_group" bl_label = "Edit Group" bl_options = {"REGISTER", "UNDO"} + copy_from_selector: bpy.props.BoolProperty(name="Copy from Selector", default=False) def execute(self, context): return IfcStore.execute_ifc_operator(self, context) @@ -86,12 +164,16 @@ class EditGroup(bpy.types.Operator): attributes[attribute.name] = None else: attributes[attribute.name] = attribute.string_value + + if self.copy_from_selector: + attributes["Description"] = context.scene.IFCSelector.selection_query + self.file = IfcStore.get_file() ifcopenshell.api.run( "group.edit_group", self.file, **{"group": self.file.by_id(props.active_group_id), "attributes": attributes} ) Data.load(IfcStore.get_file()) - bpy.ops.bim.load_groups() + bpy.ops.bim.load_groups(is_refresh=True) return {"FINISHED"} @@ -109,7 +191,7 @@ class RemoveGroup(bpy.types.Operator): self.file = IfcStore.get_file() ifcopenshell.api.run("group.remove_group", self.file, **{"group": self.file.by_id(self.group)}) Data.load(IfcStore.get_file()) - bpy.ops.bim.load_groups() + bpy.ops.bim.load_groups(is_refresh=True) return {"FINISHED"} @@ -122,9 +204,7 @@ class EnableEditingGroup(bpy.types.Operator): def execute(self, context): props = context.scene.BIMGroupProperties props.group_attributes.clear() - blenderbim.bim.helper.import_attributes("IfcGroup", props.group_attributes, Data.groups[self.group]) - props.active_group_id = self.group return {"FINISHED"} @@ -247,5 +327,5 @@ class UpdateGroup(bpy.types.Operator): } ) Data.load(IfcStore.get_file()) - bpy.ops.bim.load_groups() + bpy.ops.bim.load_groups(is_refresh=True) return {"FINISHED"} \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/module/group/prop.py b/src/blenderbim/blenderbim/bim/module/group/prop.py index e97f024584..0f00a985a8 100644 --- a/src/blenderbim/blenderbim/bim/module/group/prop.py +++ b/src/blenderbim/blenderbim/bim/module/group/prop.py @@ -18,7 +18,10 @@ import bpy from blenderbim.bim.prop import StrProperty, Attribute +from blenderbim.bim.helper import import_attributes +from ifcopenshell.api.group.data import Data from bpy.types import PropertyGroup +import json from bpy.props import ( PointerProperty, StringProperty, @@ -31,12 +34,17 @@ from bpy.props import ( ) +class ExpandedGroups(StrProperty): + json_string: StringProperty(name="JSON String", default="{}") + class Group(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") selection_query: StringProperty(name="Selection Query") + is_expanded: BoolProperty(name="Is Expanded", default=False) + has_children: BoolProperty(name="Has Children") + tree_depth: IntProperty(name="Tree Depth") - class BIMGroupProperties(PropertyGroup): group_attributes: CollectionProperty(name="Group Attributes", type=Attribute) is_editing: BoolProperty(name="Is Editing", default=False) @@ -44,3 +52,4 @@ class BIMGroupProperties(PropertyGroup): groups: CollectionProperty(name="Groups", type=Group) active_group_index: IntProperty(name="Active Group Index") active_group_id: IntProperty(name="Active Group Id") + \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/module/group/ui.py b/src/blenderbim/blenderbim/bim/module/group/ui.py index 2c7525132d..4f747e0867 100644 --- a/src/blenderbim/blenderbim/bim/module/group/ui.py +++ b/src/blenderbim/blenderbim/bim/module/group/ui.py @@ -115,9 +115,23 @@ class BIM_PT_object_groups(Panel): class BIM_UL_groups(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): if item: row = layout.row(align=True) + for i in range(0, item.tree_depth): + row.label(text="", icon="BLANK1") + if item.has_children: + op = row.operator( + "bim.toggle_group", + icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", + text="", + emboss=False) + op.ifc_definition_id = item.ifc_definition_id + op.index = index + op.option = "Collapse" if item.is_expanded else "Expand" + else: + row.label(text="", icon="BLANK1") + row.label(text=f"*{item.name}") if item.selection_query != "" else row.label(text=item.name) group_id = item.ifc_definition_id if context.scene.BIMGroupProperties.active_group_id == group_id: @@ -128,6 +142,8 @@ class BIM_UL_groups(UIList): elif context.scene.BIMGroupProperties.active_group_id: op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF") op.group = group_id + op = row.operator("bim.add_group_to_group", text="", icon="ADD") + op.group = group_id op = row.operator("bim.remove_group", text="", icon="X") op.group = group_id if item.selection_query != "": @@ -139,6 +155,8 @@ class BIM_UL_groups(UIList): op.group = group_id op = row.operator("bim.enable_editing_group", text="", icon="GREASEPENCIL") op.group = group_id + op = row.operator("bim.add_group_to_group", text="", icon="ADD") + op.group = group_id op = row.operator("bim.remove_group", text="", icon="X") op.group = group_id if item.selection_query != "": @@ -148,10 +166,23 @@ class BIM_UL_groups(UIList): class BIM_UL_object_groups(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): if item: row = layout.row(align=True) - row.label(text=f"*{item.name}") if item.selection_query != "" else row.label(text=item.name) + for i in range(0, item.tree_depth): + row.label(text="", icon="BLANK1") + if item.has_children: + op = row.operator( + "bim.toggle_group", + icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", + text="", + emboss=False) + op.ifc_definition_id = item.ifc_definition_id + op.index = index + op.option = "Collapse" if item.is_expanded else "Expand" + else: + row.label(text="", icon="BLANK1") + row.label(text=item.name) op = row.operator("bim.remove_group", text="", icon="X") op.group = item.ifc_definition_id op = row.operator("bim.assign_group", text="", icon="ADD") diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index 5ff9547041..76c87499fe 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -25,6 +25,7 @@ from ifcopenshell.util.selector import Selector import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import close_operator_panel +from blenderbim.bim.module.group import ui from itertools import cycle from bpy.types import PropertyGroup, Operator from bpy.props import ( @@ -646,23 +647,48 @@ class AddToIfcGroup(Operator): bl_idname = "bim.add_to_ifc_group" bl_label = "Add to IFC Group" group_name: StringProperty(name="Group Name") - + def invoke(self, context, event): + bpy.ops.bim.load_groups() return context.window_manager.invoke_props_dialog(self, width=400) def draw(self, context): - layout = self.layout - layout.prop(self, "group_name") + self.props = context.scene.BIMGroupProperties + row = self.layout.row() + row.operator("bim.add_group") + + self.layout.template_list( + "BIM_UL_groups", + "", + self.props, + "groups", + self.props, + "active_group_index", + ) + + if self.props.active_group_id: + for attribute in self.props.group_attributes: + if attribute.name in ["Name", "Description"]: + row = self.layout.row(align=True) + row.prop(attribute, "string_value", text=attribute.name) def execute(self, context): - self.file = IfcStore.get_file() - ifc_selector = context.scene.IfcSelectorProperties - selector_query_syntax = ifc_selector.selector_query_syntax + active_group_index = self.props.active_group_index + ifc_definition_id = self.props.groups[active_group_index].ifc_definition_id - group = ifcopenshell.api.run("group.add_group", self.file, **{"Name": self.group_name, "Description": f'*selector*{selector_query_syntax}*selector*'}) - objects = Selector.parse(self.file, selector_query_syntax) - - ifcopenshell.api.run("group.assign_group", self.file, **{"product": objects, "group": group}) - Data.load(IfcStore.get_file()) + bpy.ops.bim.enable_editing_group(group=ifc_definition_id) + selector_query_syntax = context.scene.IfcSelectorProperties.selector_query_syntax + + for attribute in self.props.group_attributes: + if attribute.name == "Description": + if "*selector*" not in attribute.string_value: + attribute.string_value += f" *selector*{selector_query_syntax}*selector*" + else: + new_description = attribute.string_value.split("*selector*") + new_description[1] = selector_query_syntax + attribute.string_value = "*selector*".join(new_description) + + bpy.ops.bim.edit_group() + bpy.ops.bim.disable_group_editing_ui() return {"FINISHED"} diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/data.py b/src/ifcopenshell-python/ifcopenshell/api/group/data.py index bce76c6a59..ccf548b0ad 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/group/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/group/data.py @@ -38,6 +38,8 @@ class Data: for product in rel.RelatedObjects: cls.products.setdefault(product.id(), []).append(group.id()) data = group.get_info() + data["HasAssignments"] = group.HasAssignments + data["IsGroupedBy"] = group.IsGroupedBy del data["OwnerHistory"] cls.groups[group.id()] = data cls.is_loaded = True