From 6387c03dbce84af6a6cf1100b6e63a9bb07594d6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 4 Jan 2021 13:07:29 +1100 Subject: [PATCH] WIP port reassign and unassign class to partial editing. See #1222. --- .../blenderbim/bim/module/root/__init__.py | 5 +- .../{assign_class.py => create_product.py} | 0 .../blenderbim/bim/module/root/data.py | 17 ++++++ .../blenderbim/bim/module/root/operator.py | 61 ++++++++++++++++--- .../bim/module/root/reassign_class.py | 26 ++++++++ .../bim/module/root/remove_product.py | 11 ++++ .../blenderbim/bim/module/root/ui.py | 51 ++++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 23 ------- src/ifccsv/ifccsv.py | 21 +------ .../ifcopenshell/util/schema.py | 19 ++++++ 10 files changed, 184 insertions(+), 50 deletions(-) rename src/ifcblenderexport/blenderbim/bim/module/root/{assign_class.py => create_product.py} (100%) create mode 100644 src/ifcblenderexport/blenderbim/bim/module/root/data.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/root/reassign_class.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/root/remove_product.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/root/ui.py diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/root/__init__.py index 82c2f2e4ce..32d3c5e695 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/root/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/module/root/__init__.py @@ -1,10 +1,13 @@ import bpy -from . import operator +from . import ui, operator classes = ( + operator.EnableReassignClass, + operator.DisableReassignClass, operator.ReassignClass, operator.AssignClass, operator.UnassignClass, + ui.BIM_PT_class, ) diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/assign_class.py b/src/ifcblenderexport/blenderbim/bim/module/root/create_product.py similarity index 100% rename from src/ifcblenderexport/blenderbim/bim/module/root/assign_class.py rename to src/ifcblenderexport/blenderbim/bim/module/root/create_product.py diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/data.py b/src/ifcblenderexport/blenderbim/bim/module/root/data.py new file mode 100644 index 0000000000..1e0449b93d --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/root/data.py @@ -0,0 +1,17 @@ +from blenderbim.bim.ifc import IfcStore + + +class Data: + products = {} + + @classmethod + def load(cls, product_id): + file = IfcStore.get_file() + if not file: + return + product = file.by_id(product_id) + cls.products[product_id] = { + "type": product.is_a(), + "PredefinedType": product.PredefinedType if hasattr(product, "PredefinedType") else None, + "ObjectType": product.ObjectType or None + } diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py index fa79cc317c..f4450f06a4 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py @@ -1,7 +1,9 @@ import bpy import numpy as np import ifcopenshell -import blenderbim.bim.module.root.assign_class as assign_class +import blenderbim.bim.module.root.create_product as create_product +import blenderbim.bim.module.root.remove_product as remove_product +import blenderbim.bim.module.root.reassign_class as reassign_class import blenderbim.bim.module.geometry.add_representation as add_representation import blenderbim.bim.module.geometry.assign_styles as assign_styles import blenderbim.bim.module.geometry.assign_representation as assign_representation @@ -11,9 +13,9 @@ from blenderbim.bim.ifc import IfcStore # from blenderbim.bim.module.geometry.data import Data -class ReassignClass(bpy.types.Operator): - bl_idname = "bim.reassign_class" - bl_label = "Reassign IFC Class" +class EnableReassignClass(bpy.types.Operator): + bl_idname = "bim.enable_reassign_class" + bl_label = "Enable Reassign IFC Class" def execute(self, context): obj = bpy.context.active_object @@ -40,6 +42,40 @@ class ReassignClass(bpy.types.Operator): return {"FINISHED"} +class DisableReassignClass(bpy.types.Operator): + bl_idname = "bim.disable_reassign_class" + bl_label = "Disable Reassign IFC Class" + + def execute(self, context): + bpy.context.active_object.BIMObjectProperties.is_reassigning_class = False + return {"FINISHED"} + + +class ReassignClass(bpy.types.Operator): + bl_idname = "bim.reassign_class" + bl_label = "Reassign IFC Class" + + def execute(self, context): + obj = bpy.context.active_object + self.file = IfcStore.get_file() + predefined_type = bpy.context.scene.BIMProperties.ifc_predefined_type + if predefined_type == "USERDEFINED": + predefined_type = bpy.context.scene.BIMProperties.ifc_userdefined_type + usecase = reassign_class.Usecase( + self.file, + { + "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), + "ifc_class": bpy.context.scene.BIMProperties.ifc_class, + "predefined_type": predefined_type, + }, + ) + product = usecase.execute() + obj.name = "{}/{}".format(product.is_a(), "/".join(obj.name.split("/")[1:])) + obj.BIMObjectProperties.ifc_definition_id = int(product.id()) + bpy.context.active_object.BIMObjectProperties.is_reassigning_class = False + return {"FINISHED"} + + class AssignClass(bpy.types.Operator): bl_idname = "bim.assign_class" bl_label = "Assign IFC Class" @@ -51,11 +87,14 @@ class AssignClass(bpy.types.Operator): for obj in objects: if obj.BIMObjectProperties.ifc_definition_id: continue - usecase = assign_class.Usecase( + predefined_type = bpy.context.scene.BIMProperties.ifc_predefined_type + if predefined_type == "USERDEFINED": + predefined_type = bpy.context.scene.BIMProperties.ifc_userdefined_type + usecase = create_product.Usecase( self.file, { "ifc_class": bpy.context.scene.BIMProperties.ifc_class, - "predefined_type": bpy.context.scene.BIMProperties.ifc_predefined_type, + "predefined_type": predefined_type, "name": obj.name, }, ) @@ -124,12 +163,20 @@ class UnassignClass(bpy.types.Operator): object_name: bpy.props.StringProperty() def execute(self, context): + self.file = IfcStore.get_file() if self.object_name: objects = [bpy.data.objects.get(self.object_name)] else: objects = bpy.context.selected_objects for obj in objects: - existing_class = None + if not obj.BIMObjectProperties.ifc_definition_id: + continue + usecase = remove_product.Usecase( + self.file, + {"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)}, + ) + usecase.execute() + obj.BIMObjectProperties.ifc_definition_id = 0 if "/" in obj.name and obj.name[0:3] == "Ifc": obj.name = "/".join(obj.name.split("/")[1:]) return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/reassign_class.py b/src/ifcblenderexport/blenderbim/bim/module/root/reassign_class.py new file mode 100644 index 0000000000..2179e3f0fa --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/root/reassign_class.py @@ -0,0 +1,26 @@ +import ifcopenshell +import ifcopenshell.util.schema + + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "product": None, + "ifc_class": "IfcBuildingElementProxy", + "predefined_type": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + element = ifcopenshell.util.schema.reassign_class( + self.file, self.settings["product"], self.settings["ifc_class"] + ) + if self.settings["predefined_type"] and hasattr(element, "PredefinedType"): + try: + element.PredefinedType = self.settings["predefined_type"] + except: + element.PredefinedType = "USERDEFINED" + element.ObjectType = self.settings["predefined_type"] + return element diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/remove_product.py b/src/ifcblenderexport/blenderbim/bim/module/root/remove_product.py new file mode 100644 index 0000000000..31ca4be404 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/root/remove_product.py @@ -0,0 +1,11 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "product": None + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.file.remove(self.settings["product"]) diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/ui.py b/src/ifcblenderexport/blenderbim/bim/module/root/ui.py new file mode 100644 index 0000000000..46e0721b52 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/root/ui.py @@ -0,0 +1,51 @@ +import bpy +from bpy.types import Panel +from blenderbim.bim.module.root.data import Data + +class BIM_PT_class(Panel): + bl_label = "IFC Class" + bl_idname = "BIM_PT_class" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + + def draw(self, context): + props = context.active_object.BIMObjectProperties + if props.ifc_definition_id: + if props.ifc_definition_id not in Data.products: + Data.load(props.ifc_definition_id) + if props.is_reassigning_class: + self.draw_class_dropdowns() + row = self.layout.row(align=True) + row.operator("bim.reassign_class", icon="CHECKMARK") + row.operator("bim.disable_reassign_class", icon="X", text="") + else: + data = Data.products[props.ifc_definition_id] + name = data["type"] + if data["PredefinedType"] and data["PredefinedType"] == "USERDEFINED": + name += "[{}]".format(data["ObjectType"]) + elif data["PredefinedType"]: + name += "[{}]".format(data["PredefinedType"]) + row = self.layout.row(align=True) + row.label(text=name) + row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="") + op = row.operator("bim.unassign_class", icon="X", text="") + op.object_name = context.active_object.name + else: + self.draw_class_dropdowns() + row = self.layout.row(align=True) + op = row.operator("bim.assign_class") + op.object_name = context.active_object.name + + def draw_class_dropdowns(self): + props = bpy.context.scene.BIMProperties + row = self.layout.row() + row.prop(props, "ifc_product") + row = self.layout.row() + row.prop(props, "ifc_class") + if props.ifc_predefined_type: + row = self.layout.row() + row.prop(props, "ifc_predefined_type") + if props.ifc_predefined_type == "USERDEFINED": + row = self.layout.row() + row.prop(props, "ifc_userdefined_type") diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 13ebadb6cb..d29ec12bd1 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -23,29 +23,6 @@ class BIM_PT_object(Panel): props = context.active_object.BIMObjectProperties bim_properties = context.scene.BIMProperties - if props.is_reassigning_class or "/" not in context.active_object.name: - row = layout.row() - row.prop(bim_properties, "ifc_product") - row = layout.row() - row.prop(bim_properties, "ifc_class") - if bim_properties.ifc_predefined_type: - row = layout.row() - row.prop(bim_properties, "ifc_predefined_type") - if bim_properties.ifc_predefined_type == "USERDEFINED": - row = layout.row() - row.prop(bim_properties, "ifc_userdefined_type") - row = layout.row(align=True) - if "Ifc" not in context.active_object.name: - op = row.operator("bim.assign_class") - else: - op = row.operator("bim.assign_class") - op.object_name = context.active_object.name - op = row.operator("bim.unassign_class", icon="X", text="") - op.object_name = context.active_object.name - else: - row = layout.row() - row.operator("bim.reassign_class", text="Reassign IFC Class") - if "Ifc" not in context.active_object.name: return diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 26d66092a0..91e0ced797 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -4,6 +4,7 @@ import ifcopenshell import ifcopenshell.util.selector import ifcopenshell.util.element +import ifcopenshell.util.schema import csv import lark import argparse @@ -13,7 +14,7 @@ class IfcAttributeExtractor: @staticmethod def set_element_key(ifc_file, element, key, value): if key == "type" and element.is_a() != value: - return IfcAttributeExtractor.change_ifc_class(ifc_file, element, value) + return ifcopenshell.util.schema.reassign_class(ifc_file, element, value) if hasattr(element, key): setattr(element, key, value) return element @@ -32,24 +33,6 @@ class IfcAttributeExtractor: return element return element - @staticmethod - def change_ifc_class(ifc_file, element, new_class): - try: - new_element = ifc_file.create_entity(new_class) - except: - print(f"Class of {element} could not be changed to {new_class}") - return element - new_attributes = [new_element.attribute_name(i) for i, attribute in enumerate(new_element)] - for i, attribute in enumerate(element): - try: - new_element[new_attributes.index(element.attribute_name(i))] = attribute - except: - continue - for inverse in ifc_file.get_inverse(element): - ifcopenshell.util.element.replace_attribute(inverse, element, new_element) - ifc_file.remove(element) - return new_element - @staticmethod def get_element_qto(element, name): for relationship in element.IsDefinedBy: diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema.py b/src/ifcopenshell-python/ifcopenshell/util/schema.py index ca10e72246..407e912ba1 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/schema.py +++ b/src/ifcopenshell-python/ifcopenshell/util/schema.py @@ -18,6 +18,25 @@ def is_a(entity, ifc_class): return False +def reassign_class(ifc_file, element, new_class): + try: + new_element = ifc_file.create_entity(new_class) + except: + print(f"Class of {element} could not be changed to {new_class}") + return element + new_attributes = [new_element.attribute_name(i) for i, attribute in enumerate(new_element)] + for i, attribute in enumerate(element): + try: + new_element[new_attributes.index(element.attribute_name(i))] = attribute + except: + continue + for inverse in ifc_file.get_inverse(element): + ifcopenshell.util.element.replace_attribute(inverse, element, new_element) + ifc_file.remove(element) + return new_element + + + class Migrator: def __init__(self): self.migrated_ids = {}