diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 6f1531121c..d62f3e9e12 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -29,6 +29,7 @@ if bpy is not None: "cost": None, "sequence": None, "group": None, + "system": None, "structural": None, "boundary": None, "material": None, diff --git a/src/blenderbim/blenderbim/bim/module/system/__init__.py b/src/blenderbim/blenderbim/bim/module/system/__init__.py new file mode 100644 index 0000000000..0059a29f4d --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/system/__init__.py @@ -0,0 +1,27 @@ +import bpy +from . import ui, prop, operator + +classes = ( + operator.LoadSystems, + operator.DisableSystemEditingUI, + operator.AddSystem, + operator.EditSystem, + operator.RemoveSystem, + operator.AssignSystem, + operator.UnassignSystem, + operator.EnableEditingSystem, + operator.DisableEditingSystem, + operator.SelectSystemProducts, + prop.System, + prop.BIMSystemProperties, + ui.BIM_PT_systems, + ui.BIM_UL_systems, +) + + +def register(): + bpy.types.Scene.BIMSystemProperties = bpy.props.PointerProperty(type=prop.BIMSystemProperties) + + +def unregister(): + del bpy.types.Scene.BIMSystemProperties diff --git a/src/blenderbim/blenderbim/bim/module/system/operator.py b/src/blenderbim/blenderbim/bim/module/system/operator.py new file mode 100644 index 0000000000..da5a714f09 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/system/operator.py @@ -0,0 +1,196 @@ +import bpy +import ifcopenshell.util.attribute +import ifcopenshell.api +from blenderbim.bim.ifc import IfcStore +from ifcopenshell.api.system.data import Data + + +class LoadSystems(bpy.types.Operator): + bl_idname = "bim.load_systems" + bl_label = "Load Systems" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + props = context.scene.BIMSystemProperties + while len(props.systems) > 0: + props.systems.remove(0) + for ifc_definition_id, system in Data.systems.items(): + new = props.systems.add() + new.ifc_definition_id = ifc_definition_id + new.name = system["Name"] + props.is_editing = True + bpy.ops.bim.disable_editing_system() + return {"FINISHED"} + + +class DisableSystemEditingUI(bpy.types.Operator): + bl_idname = "bim.disable_system_editing_ui" + bl_label = "Disable System Editing UI" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + context.scene.BIMSystemProperties.is_editing = False + return {"FINISHED"} + + +class AddSystem(bpy.types.Operator): + bl_idname = "bim.add_system" + bl_label = "Add System" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + result = ifcopenshell.api.run("system.add_system", IfcStore.get_file()) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_systems() + bpy.ops.bim.enable_editing_system(system=result.id()) + return {"FINISHED"} + + +class EditSystem(bpy.types.Operator): + bl_idname = "bim.edit_system" + bl_label = "Edit System" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + props = context.scene.BIMSystemProperties + attributes = {} + for attribute in props.system_attributes: + if attribute.is_null: + attributes[attribute.name] = None + else: + attributes[attribute.name] = attribute.string_value + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "system.edit_system", self.file, **{"system": self.file.by_id(props.active_system_id), "attributes": attributes} + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_systems() + return {"FINISHED"} + + +class RemoveSystem(bpy.types.Operator): + bl_idname = "bim.remove_system" + bl_label = "Remove System" + bl_options = {"REGISTER", "UNDO"} + system: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + props = context.scene.BIMSystemProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run("system.remove_system", self.file, **{"system": self.file.by_id(self.system)}) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_systems() + return {"FINISHED"} + + +class EnableEditingSystem(bpy.types.Operator): + bl_idname = "bim.enable_editing_system" + bl_label = "Enable Editing System" + bl_options = {"REGISTER", "UNDO"} + system: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMSystemProperties + while len(props.system_attributes) > 0: + props.system_attributes.remove(0) + + data = Data.systems[self.system] + + for attribute in IfcStore.get_schema().declaration_by_name("IfcSystem").all_attributes(): + data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) + if data_type == "entity": + continue + new = props.system_attributes.add() + new.name = attribute.name() + new.is_null = data[attribute.name()] is None + new.is_optional = attribute.optional() + new.string_value = "" if new.is_null else data[attribute.name()] + props.active_system_id = self.system + return {"FINISHED"} + + +class DisableEditingSystem(bpy.types.Operator): + bl_idname = "bim.disable_editing_system" + bl_label = "Disable Editing System" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + context.scene.BIMSystemProperties.active_system_id = 0 + return {"FINISHED"} + + +class AssignSystem(bpy.types.Operator): + bl_idname = "bim.assign_system" + bl_label = "Assign System" + bl_options = {"REGISTER", "UNDO"} + product: bpy.props.StringProperty() + system: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + product = bpy.data.objects.get(self.product) if self.product else context.active_object + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "system.assign_system", + self.file, + **{ + "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), + "system": self.file.by_id(self.system), + } + ) + Data.load(IfcStore.get_file()) + return {"FINISHED"} + + +class UnassignSystem(bpy.types.Operator): + bl_idname = "bim.unassign_system" + bl_label = "Unassign System" + bl_options = {"REGISTER", "UNDO"} + product: bpy.props.StringProperty() + system: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + product = bpy.data.objects.get(self.product) if self.product else context.active_object + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "system.unassign_system", + self.file, + **{ + "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), + "system": self.file.by_id(self.system), + } + ) + Data.load(IfcStore.get_file()) + return {"FINISHED"} + + +class SelectSystemProducts(bpy.types.Operator): + bl_idname = "bim.select_system_products" + bl_label = "Select System Products" + bl_options = {"REGISTER", "UNDO"} + system: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + for obj in bpy.context.visible_objects: + obj.select_set(False) + if not obj.BIMObjectProperties.ifc_definition_id: + continue + product_systems = Data.products.get(obj.BIMObjectProperties.ifc_definition_id, []) + if self.system in product_systems: + obj.select_set(True) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/system/prop.py b/src/blenderbim/blenderbim/bim/module/system/prop.py new file mode 100644 index 0000000000..5bed5a3fd9 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/system/prop.py @@ -0,0 +1,26 @@ +import bpy +from blenderbim.bim.prop import StrProperty, Attribute +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + + +class System(PropertyGroup): + name: StringProperty(name="Name") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + +class BIMSystemProperties(PropertyGroup): + system_attributes: CollectionProperty(name="System Attributes", type=Attribute) + is_editing: BoolProperty(name="Is Editing", default=False) + systems: CollectionProperty(name="Systems", type=System) + active_system_index: IntProperty(name="Active System Index") + active_system_id: IntProperty(name="Active System Id") diff --git a/src/blenderbim/blenderbim/bim/module/system/ui.py b/src/blenderbim/blenderbim/bim/module/system/ui.py new file mode 100644 index 0000000000..067dd038fc --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/system/ui.py @@ -0,0 +1,85 @@ +from bpy.types import Panel, UIList +from blenderbim.bim.ifc import IfcStore +from ifcopenshell.api.system.data import Data + + +class BIM_PT_systems(Panel): + bl_label = "IFC Systems" + bl_idname = "BIM_PT_systems" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + @classmethod + def poll(cls, context): + return IfcStore.get_file() + + def draw(self, context): + if not Data.is_loaded: + Data.load(IfcStore.get_file()) + self.props = context.scene.BIMSystemProperties + + row = self.layout.row(align=True) + row.label(text="{} Systems Found".format(len(Data.systems)), icon="OUTLINER") + if self.props.is_editing: + row.operator("bim.add_system", text="", icon="ADD") + row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL") + else: + row.operator("bim.load_systems", text="", icon="GREASEPENCIL") + + if self.props.is_editing: + self.layout.template_list( + "BIM_UL_systems", + "", + self.props, + "systems", + self.props, + "active_system_index", + ) + + if self.props.active_system_id: + self.draw_editable_ui(context) + + def draw_editable_ui(self, context): + for attribute in self.props.system_attributes: + row = self.layout.row(align=True) + row.prop(attribute, "string_value", text=attribute.name) + if attribute.is_optional: + row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + + + +class BIM_UL_systems(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.label(text=item.name) + + if context.active_object: + oprops = context.active_object.BIMObjectProperties + if ( + oprops.ifc_definition_id in Data.products + and item.ifc_definition_id in Data.products[oprops.ifc_definition_id] + ): + op = row.operator("bim.unassign_system", text="", icon="KEYFRAME_HLT", emboss=False) + op.system = item.ifc_definition_id + else: + op = row.operator("bim.assign_system", text="", icon="KEYFRAME", emboss=False) + op.system = item.ifc_definition_id + + if context.scene.BIMSystemProperties.active_system_id == item.ifc_definition_id: + op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF") + op.system = item.ifc_definition_id + row.operator("bim.edit_system", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_system", text="", icon="CANCEL") + elif context.scene.BIMSystemProperties.active_system_id: + op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF") + op.system = item.ifc_definition_id + row.operator("bim.remove_system", text="", icon="X").system = item.ifc_definition_id + else: + op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF") + op.system = item.ifc_definition_id + op = row.operator("bim.enable_editing_system", text="", icon="GREASEPENCIL") + op.system = item.ifc_definition_id + row.operator("bim.remove_system", text="", icon="X").system = item.ifc_definition_id diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/add_system.py b/src/ifcopenshell-python/ifcopenshell/api/system/add_system.py new file mode 100644 index 0000000000..b3226724c5 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/system/add_system.py @@ -0,0 +1,17 @@ +import ifcopenshell +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + return self.file.create_entity("IfcSystem", **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "Name": "Unnamed" + }) diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py b/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py new file mode 100644 index 0000000000..265afac4bd --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py @@ -0,0 +1,27 @@ +import ifcopenshell +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "product": None, + "system": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + if not self.settings["system"].IsGroupedBy: + return self.file.create_entity("IfcRelAssignsToGroup", **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "RelatedObjects": [self.settings["product"]], + "RelatingGroup": self.settings["system"] + }) + rel = self.settings["system"].IsGroupedBy[0] + related_objects = set(rel.RelatedObjects) or set() + related_objects.add(self.settings["product"]) + rel.RelatedObjects = list(related_objects) + ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/data.py b/src/ifcopenshell-python/ifcopenshell/api/system/data.py new file mode 100644 index 0000000000..2a171f6a50 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/system/data.py @@ -0,0 +1,24 @@ +class Data: + is_loaded = False + products = {} + systems = {} + + @classmethod + def purge(cls): + cls.is_loaded = False + cls.products = {} + cls.systems = {} + + @classmethod + def load(cls, file): + cls.products = {} + cls.systems = {} + for system in file.by_type("IfcSystem", include_subtypes=False): + if system.IsGroupedBy: + for rel in system.IsGroupedBy: + for product in rel.RelatedObjects: + cls.products.setdefault(product.id(), []).append(system.id()) + data = system.get_info() + del data["OwnerHistory"] + cls.systems[system.id()] = data + cls.is_loaded=True diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/edit_system.py b/src/ifcopenshell-python/ifcopenshell/api/system/edit_system.py new file mode 100644 index 0000000000..734e9937c3 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/system/edit_system.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "system": None, + "attributes": {} + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for name, value in self.settings["attributes"].items(): + setattr(self.settings["system"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/remove_system.py b/src/ifcopenshell-python/ifcopenshell/api/system/remove_system.py new file mode 100644 index 0000000000..9cc1a6ec93 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/system/remove_system.py @@ -0,0 +1,11 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"system": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for rel in self.settings["system"].IsGroupedBy or []: + self.file.remove(rel) + self.file.remove(self.settings["system"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/unassign_system.py b/src/ifcopenshell-python/ifcopenshell/api/system/unassign_system.py new file mode 100644 index 0000000000..d072f74623 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/system/unassign_system.py @@ -0,0 +1,25 @@ +import ifcopenshell +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "product": None, + "system": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + if not self.settings["system"].IsGroupedBy: + return + rel = self.settings["system"].IsGroupedBy[0] + related_objects = set(rel.RelatedObjects) or set() + related_objects.remove(self.settings["product"]) + if len(related_objects): + rel.RelatedObjects = list(related_objects) + ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) + else: + self.file.remove(rel) diff --git a/src/ifcopenshell-python/ifcopenshell/api/type/data.py b/src/ifcopenshell-python/ifcopenshell/api/type/data.py index ac8cd509aa..c768bd6cf2 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/type/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/type/data.py @@ -1,18 +1,40 @@ class Data: products = {} + types = {} @classmethod def purge(cls): cls.products = {} + cls.types = {} @classmethod def load(cls, file, product_id): if not file: return + cls.file = file product = file.by_id(product_id) - if file.schema == "IFC2X3" and not hasattr(product, "IsDefinedBy"): + if product.is_a("IfcTypeObject"): + cls.load_type(product_id) + else: + cls.load_product(product_id) + + @classmethod + def load_type(cls, product_id): + product = cls.file.by_id(product_id) + cls.types[product_id] = None + if cls.file.schema == "IFC2X3": + if hasattr(product, "ObjectTypeOf"): + cls.types[product_id] = [o.id() for o in product.ObjectTypeOf[0].RelatedObjects] + else: + if hasattr(product, "Types"): + cls.types[product_id] = [o.id() for o in product.Types[0].RelatedObjects] + + @classmethod + def load_product(cls, product_id): + product = cls.file.by_id(product_id) + if cls.file.schema == "IFC2X3" and not hasattr(product, "IsDefinedBy"): cls.products[product_id] = None - elif file.schema != "IFC2X3" and not hasattr(product, "IsTypedBy"): + elif cls.file.schema != "IFC2X3" and not hasattr(product, "IsTypedBy"): cls.products[product_id] = None elif hasattr(product, "IsTypedBy") and product.IsTypedBy: type = product.IsTypedBy[0].RelatingType