mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-19 19:54:07 +00:00
See #1848. Refactor group data class. Add support for multiple subgroup relationships.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
GroupsData.is_loaded = False
|
||||
ObjectGroupsData.is_loaded = False
|
||||
|
||||
|
||||
class GroupsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_groups": cls.total_groups()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_groups(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcGroup", include_subtypes=False))
|
||||
|
||||
|
||||
class ObjectGroupsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_groups": cls.total_groups(), "groups": cls.groups()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_groups(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcGroup", include_subtypes=False))
|
||||
|
||||
@classmethod
|
||||
def groups(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element:
|
||||
return []
|
||||
results = []
|
||||
for rel in getattr(element, "HasAssignments", []) or []:
|
||||
if rel.is_a("IfcRelAssignsToGroup"):
|
||||
results.append({"id": rel.RelatingGroup.id(), "name": rel.RelatingGroup.Name or "Unnamed"})
|
||||
return results
|
||||
@@ -22,71 +22,48 @@ import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
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):
|
||||
class LoadGroups(bpy.types.Operator, tool.Ifc.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):
|
||||
def _execute(self, context):
|
||||
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():
|
||||
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.tree_depth = 0
|
||||
|
||||
if group["IsGroupedBy"]:
|
||||
# assumes 1:1 cardinality, will need to be updated to reflect IFC4 changes
|
||||
# where the cardinality is 0:? - vulevukusej
|
||||
sub_groups = [g for g in group["IsGroupedBy"][0].RelatedObjects if g.is_a("IfcGroup")]
|
||||
new.has_children = True if len(sub_groups) != 0 else False
|
||||
|
||||
if self.is_refresh:
|
||||
self.recursively_load_sub_groups(ifc_definition_id, new)
|
||||
for group in tool.Ifc.get().by_type("IfcGroup", include_subtypes=False):
|
||||
if not group.HasAssignments:
|
||||
self.load_group(group)
|
||||
|
||||
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
|
||||
parent.has_children = 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)
|
||||
def load_group(self, group, tree_depth=0):
|
||||
new = self.props.groups.add()
|
||||
new.ifc_definition_id = group.id()
|
||||
new.name = group.Name or "Unnamed"
|
||||
new.selection_query = group.Description.split("*selector*")[1] if group.Description else ""
|
||||
new.tree_depth = tree_depth
|
||||
new.has_children = False
|
||||
new.is_expanded = group.id() in self.expanded_groups
|
||||
|
||||
for rel in group.IsGroupedBy or []:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if not related_object.is_a("IfcGroup"):
|
||||
continue
|
||||
new.has_children = True
|
||||
if not new.is_expanded:
|
||||
return
|
||||
self.load_group(related_object, tree_depth=tree_depth + 1)
|
||||
|
||||
|
||||
class ToggleGroup(bpy.types.Operator):
|
||||
class ToggleGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.toggle_group"
|
||||
bl_label = "Toggle Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -94,58 +71,50 @@ class ToggleGroup(bpy.types.Operator):
|
||||
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)
|
||||
def _execute(self, context):
|
||||
expanded_groups = set(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)
|
||||
expanded_groups.add(self.ifc_definition_id)
|
||||
elif self.ifc_definition_id in expanded_groups:
|
||||
expanded_groups.remove(self.ifc_definition_id)
|
||||
context.scene.ExpandedGroups.json_string = json.dumps(list(expanded_groups))
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableGroupEditingUI(bpy.types.Operator):
|
||||
class DisableGroupEditingUI(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_group_editing_ui"
|
||||
bl_label = "Disable Group Editing UI"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
context.scene.BIMGroupProperties.is_editing = False
|
||||
context.scene.BIMGroupProperties.active_group_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddGroup(bpy.types.Operator):
|
||||
class AddGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_group"
|
||||
bl_label = "Add New Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("group.add_group", tool.Ifc.get())
|
||||
if self.group:
|
||||
ifcopenshell.api.run(
|
||||
"group.assign_group", tool.Ifc.get(), products=[result], group=tool.Ifc.get().by_id(self.group)
|
||||
)
|
||||
Data.load(tool.Ifc.get())
|
||||
bpy.ops.bim.load_groups(is_refresh=True)
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditGroup(bpy.types.Operator):
|
||||
class EditGroup(bpy.types.Operator, tool.Ifc.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)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
attributes = {}
|
||||
@@ -162,73 +131,65 @@ class EditGroup(bpy.types.Operator):
|
||||
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(is_refresh=True)
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveGroup(bpy.types.Operator):
|
||||
class RemoveGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_group"
|
||||
bl_label = "Remove Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
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(is_refresh=True)
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingGroup(bpy.types.Operator):
|
||||
class EnableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_group"
|
||||
bl_label = "Enable Editing Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
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])
|
||||
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.group), props.group_attributes)
|
||||
props.active_group_id = self.group
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingGroup(bpy.types.Operator):
|
||||
class DisableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_group"
|
||||
bl_label = "Disable Editing Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
context.scene.BIMGroupProperties.active_group_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleAssigningGroup(bpy.types.Operator):
|
||||
class ToggleAssigningGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.toggle_assigning_group"
|
||||
bl_label = "Toggle Assigning Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
context.scene.BIMGroupProperties.is_adding = not context.scene.BIMGroupProperties.is_adding
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignGroup(bpy.types.Operator):
|
||||
class AssignGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_group"
|
||||
bl_label = "Assign Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
product: bpy.props.StringProperty()
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
|
||||
@@ -241,20 +202,16 @@ class AssignGroup(bpy.types.Operator):
|
||||
products=[self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
|
||||
group=self.file.by_id(self.group),
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignGroup(bpy.types.Operator):
|
||||
class UnassignGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unassign_group"
|
||||
bl_label = "Unassign Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
product: bpy.props.StringProperty()
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
|
||||
@@ -269,38 +226,39 @@ class UnassignGroup(bpy.types.Operator):
|
||||
"group": self.file.by_id(self.group),
|
||||
}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectGroupProducts(bpy.types.Operator):
|
||||
class SelectGroupProducts(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_group_products"
|
||||
bl_label = "Select Group Products"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
product_groups = Data.products.get(obj.BIMObjectProperties.ifc_definition_id, [])
|
||||
product_groups = [
|
||||
r.RelatingGroup.id()
|
||||
for r in getattr(element, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
]
|
||||
if self.group in product_groups:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UpdateGroup(bpy.types.Operator):
|
||||
class UpdateGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.update_group"
|
||||
bl_label = "Update Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
query: bpy.props.StringProperty()
|
||||
group_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
group = self.file.by_id(self.group_id)
|
||||
@@ -315,6 +273,5 @@ class UpdateGroup(bpy.types.Operator):
|
||||
"products": new_products,
|
||||
}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_groups(is_refresh=True)
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
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 (
|
||||
@@ -35,7 +34,7 @@ from bpy.props import (
|
||||
|
||||
|
||||
class ExpandedGroups(StrProperty):
|
||||
json_string: StringProperty(name="JSON String", default="{}")
|
||||
json_string: StringProperty(name="JSON String", default="[]")
|
||||
|
||||
|
||||
class Group(PropertyGroup):
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.group.data import Data
|
||||
from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData
|
||||
|
||||
|
||||
class BIM_PT_groups(Panel):
|
||||
@@ -35,12 +35,12 @@ class BIM_PT_groups(Panel):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if not GroupsData.is_loaded:
|
||||
GroupsData.load()
|
||||
self.props = context.scene.BIMGroupProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{len(Data.groups)} Groups Found", icon="OUTLINER")
|
||||
row.label(text=f"{GroupsData.data['total_groups']} Groups Found", icon="OUTLINER")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.add_group", text="", icon="ADD").group = 0
|
||||
row.operator("bim.disable_group_editing_ui", text="", icon="CANCEL")
|
||||
@@ -84,8 +84,8 @@ class BIM_PT_object_groups(Panel):
|
||||
return IfcStore.get_file() and context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if not ObjectGroupsData.is_loaded:
|
||||
ObjectGroupsData.load()
|
||||
self.props = context.scene.BIMGroupProperties
|
||||
row = self.layout.row(align=True)
|
||||
if self.props.is_adding:
|
||||
@@ -100,15 +100,14 @@ class BIM_PT_object_groups(Panel):
|
||||
"active_group_index",
|
||||
)
|
||||
else:
|
||||
row.label(text=f"{len(Data.groups)} Groups in IFC Project", icon="OUTLINER")
|
||||
row.label(text=f"{ObjectGroupsData.data['total_groups']} Groups in IFC Project", icon="OUTLINER")
|
||||
row.operator("bim.toggle_assigning_group", text="", icon="ADD")
|
||||
|
||||
groups_object = Data.products.get(context.active_object.BIMObjectProperties.ifc_definition_id, [])
|
||||
for group_id in groups_object:
|
||||
for group in ObjectGroupsData.data['groups']:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=Data.groups[group_id].get("Name", "Unnamed"))
|
||||
row.label(text=group["name"])
|
||||
op = row.operator("bim.unassign_group", text="", icon="X")
|
||||
op.group = group_id
|
||||
op.group = group["id"]
|
||||
|
||||
if not groups_object:
|
||||
self.layout.label(text="No Group associated with Active Object")
|
||||
|
||||
Reference in New Issue
Block a user