diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index eb28ff5d73..897459da2c 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -13,6 +13,7 @@ if bpy is not None: "aggregate": None, "attribute": None, "bcf": None, + "classification": None, "cobie": None, "context": None, "covetool": None, @@ -87,12 +88,6 @@ if bpy is not None: operator.SelectGlobalId, operator.SelectAttribute, operator.SelectPset, - operator.LoadClassification, - operator.AddClassification, - operator.RemoveClassification, - operator.AssignClassification, - operator.UnassignClassification, - operator.RemoveClassificationReference, operator.FetchLibraryInformation, operator.FetchExternalMaterial, operator.FetchObjectPassport, @@ -119,7 +114,6 @@ if bpy is not None: operator.SelectSmartGroup, operator.LoadSmartGroupsForActiveClashSet, operator.OpenUpstream, - operator.BIM_OT_ChangeClassificationLevel, operator.AddPropertySetTemplate, operator.RemovePropertySetTemplate, operator.EditPropertySetTemplate, @@ -181,9 +175,6 @@ if bpy is not None: prop.StrProperty, prop.Attribute, prop.Variable, - prop.Classification, - prop.ClassificationReference, - prop.ClassificationView, prop.PropertySetTemplate, prop.PropertyTemplate, prop.DocumentInformation, @@ -217,7 +208,6 @@ if bpy is not None: ui.BIM_PT_schedules, ui.BIM_PT_sheets, ui.BIM_PT_psets, - ui.BIM_PT_classifications, ui.BIM_PT_document_information, ui.BIM_PT_constraints, ui.BIM_PT_search, @@ -227,7 +217,6 @@ if bpy is not None: ui.BIM_PT_patch, ui.BIM_PT_mvd, ui.BIM_PT_presentation_layer_data, - ui.BIM_PT_classification_references, ui.BIM_PT_documents, ui.BIM_PT_constraint_relations, ui.BIM_PT_object_structural, @@ -246,7 +235,6 @@ if bpy is not None: ui.BIM_UL_document_information, ui.BIM_UL_document_references, ui.BIM_UL_topics, - ui.BIM_UL_classifications, ui.BIM_ADDON_preferences, ] diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/classification/__init__.py new file mode 100644 index 0000000000..f486b4e3fa --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/__init__.py @@ -0,0 +1,35 @@ +import bpy +from . import ui, prop, operator + +classes = ( + operator.LoadClassificationLibrary, + operator.AddClassification, + operator.RemoveClassification, + operator.EnableEditingClassification, + operator.DisableEditingClassification, + operator.EditClassification, + operator.RemoveClassificationReference, + operator.EnableEditingClassificationReference, + operator.DisableEditingClassificationReference, + operator.EditClassificationReference, + operator.AddClassificationReference, + operator.ChangeClassificationLevel, + prop.ClassificationReference, + prop.BIMClassificationProperties, + prop.BIMClassificationReferenceProperties, + ui.BIM_PT_classifications, + ui.BIM_PT_classification_references, + ui.BIM_UL_classifications, +) + + +def register(): + bpy.types.Scene.BIMClassificationProperties = bpy.props.PointerProperty(type=prop.BIMClassificationProperties) + bpy.types.Object.BIMClassificationReferenceProperties = bpy.props.PointerProperty( + type=prop.BIMClassificationReferenceProperties + ) + + +def unregister(): + del bpy.types.Scene.BIMClassificationProperties + del bpy.types.Object.BIMClassificationReferenceProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/add_classification.py b/src/ifcblenderexport/blenderbim/bim/module/classification/add_classification.py new file mode 100644 index 0000000000..7c3c473557 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/add_classification.py @@ -0,0 +1,26 @@ +import ifcopenshell +import ifcopenshell.util.schema + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "classification": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + migrator = ifcopenshell.util.schema.Migrator() + result = migrator.migrate(self.settings["classification"], self.file) + self.file.create_entity("IfcRelAssociatesClassification", **{ + "GlobalId": ifcopenshell.guid.new(), + "RelatedObjects": [self.file.by_type("IfcProject")[0]], + "RelatingClassification": result + }) + return # See bug #1272 + try: + result = self.file.add(self.settings["classification"]) + except: + migrator = ifcopenshell.util.schema.Migrator() + result = migrator.migrate(self.settings["classification"], self.file) diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/add_reference.py b/src/ifcblenderexport/blenderbim/bim/module/classification/add_reference.py new file mode 100644 index 0000000000..d6dff741ef --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/add_reference.py @@ -0,0 +1,53 @@ +import ifcopenshell +import ifcopenshell.util.schema + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "product": None, + "reference": None, + "classification": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + relating_classification = None + for reference in self.file.by_type("IfcClassificationReference"): + if self.file.schema == "IFC2X3": + if reference.ItemReference == self.settings["reference"].ItemReference: + relating_classification = reference + break + else: + if reference.Identification == self.settings["reference"].Identification: + relating_classification = reference + break + + if relating_classification: + association = self.get_association(relating_classification) + related_objects = set(association.RelatedObjects) + related_objects.add(self.settings["product"]) + association.RelatedObjects = list(related_objects) + return + + migrator = ifcopenshell.util.schema.Migrator() + # This removal patch is to support a lightweight classification + old_referenced_source = self.settings["reference"].ReferencedSource + self.settings["reference"].ReferencedSource = None + relating_classification = migrator.migrate(self.settings["reference"], self.file) + relating_classification.ReferencedSource = self.settings["classification"] + self.settings["reference"].ReferencedSource = old_referenced_source + self.file.create_entity("IfcRelAssociatesClassification", **{ + "GlobalId": ifcopenshell.guid.new(), + "RelatedObjects": [self.settings["product"]], + "RelatingClassification": relating_classification + }) + + def get_association(self, reference): + if self.file.schema == "IFC2X3": + for association in self.file.by_type("IfcRelAssociatesClassification"): + if relating_classification == reference: + return association + elif reference.ClassificationRefForObjects: + return reference.ClassificationRefForObjects[0] diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/data.py b/src/ifcblenderexport/blenderbim/bim/module/classification/data.py new file mode 100644 index 0000000000..b51711342f --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/data.py @@ -0,0 +1,71 @@ +import ifcopenshell +from blenderbim.bim.ifc import IfcStore +from datetime import datetime + + +class Data: + is_loaded = False + products = {} + classifications = {} + references = {} + library_file = None + library_classifications = {} + library_references = {} + + @classmethod + def load(cls, product_id=None): + cls._file = IfcStore.get_file() + if not cls._file: + return + if product_id: + return cls.load_product_classifications(product_id) + cls.load_classifications() + cls.load_references() + cls.is_loaded = True + + @classmethod + def load_product_classifications(cls, product_id): + product = cls._file.by_id(product_id) + cls.products[product_id] = [] + if not product.HasAssociations: + return + for association in product.HasAssociations: + if association.is_a("IfcRelAssociatesClassification"): + cls.products[product_id].append(association.RelatingClassification.id()) + + @classmethod + def load_classifications(cls): + cls.classifications = {} + for classification in cls._file.by_type("IfcClassification"): + data = classification.get_info() + if cls._file.schema == "IFC2X3" and data["EditionDate"]: + data["EditionDate"] = datetime( + classification.EditionDate.YearComponent, + classification.EditionDate.MonthComponent, + classification.EditionDate.DayComponent, + ).isoformat() + cls.classifications[classification.id()] = data + + @classmethod + def load_references(cls): + cls.references = {} + for reference in cls._file.by_type("IfcClassificationReference"): + data = reference.get_info() + if reference.ReferencedSource: + #data["ReferencedSource"] = cls.get_referenced_source(reference.ReferencedSource) + data["ReferencedSource"] = reference.ReferencedSource.id() + cls.references[reference.id()] = data + + @classmethod + def get_referenced_source(cls, reference): + if reference.is_a("IfcClassification"): + return reference + elif reference.is_a("IfcClassificationReference") and reference.ReferencedSource: + return cls.get_referenced_source(reference.ReferencedSource) + + @classmethod + def load_library(cls, filepath): + cls.library_file = ifcopenshell.open(filepath) + cls.library_classifications = {} + for classification in cls.library_file.by_type("IfcClassification"): + cls.library_classifications[classification.id()] = classification.Name diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/edit_classification.py b/src/ifcblenderexport/blenderbim/bim/module/classification/edit_classification.py new file mode 100644 index 0000000000..58fd59ea9a --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/edit_classification.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "classification": 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["classification"], name, value) diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/edit_reference.py b/src/ifcblenderexport/blenderbim/bim/module/classification/edit_reference.py new file mode 100644 index 0000000000..b538bafb46 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/edit_reference.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "reference": 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["reference"], name, value) diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/operator.py b/src/ifcblenderexport/blenderbim/bim/module/classification/operator.py new file mode 100644 index 0000000000..1150f138f3 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/operator.py @@ -0,0 +1,229 @@ +import bpy +import json +import blenderbim.bim.module.classification.add_classification as add_classification +import blenderbim.bim.module.classification.remove_classification as remove_classification +import blenderbim.bim.module.classification.edit_classification as edit_classification +import blenderbim.bim.module.classification.add_reference as add_reference +import blenderbim.bim.module.classification.remove_reference as remove_reference +import blenderbim.bim.module.classification.edit_reference as edit_reference +from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.classification.data import Data +from blenderbim.bim.module.classification.prop import getClassifications, getReferences + + +class LoadClassificationLibrary(bpy.types.Operator): + bl_idname = "bim.load_classification_library" + bl_label = "Load Classification Library" + filename_ext = ".ifc" + filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + Data.load_library(self.filepath) + getClassifications(self, context) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class AddClassification(bpy.types.Operator): + bl_idname = "bim.add_classification" + bl_label = "Add Classification" + + def execute(self, context): + props = context.scene.BIMClassificationProperties + add_classification.Usecase( + IfcStore.get_file(), {"classification": Data.library_file.by_id(int(props.available_classifications))} + ).execute() + Data.load() + return {"FINISHED"} + + +class EnableEditingClassification(bpy.types.Operator): + bl_idname = "bim.enable_editing_classification" + bl_label = "Enable Editing Classification" + classification: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMClassificationProperties + while len(props.classification_attributes) > 0: + props.classification_attributes.remove(0) + classification_data = Data.classifications[self.classification] + for attribute in IfcStore.get_schema().declaration_by_name("IfcClassification").all_attributes(): + new = props.classification_attributes.add() + new.name = attribute.name() + new.is_null = classification_data[attribute.name()] is None + new.is_optional = attribute.optional() + if attribute.name() == "ReferenceTokens": + new.string_value = "" if new.is_null else json.dumps(classification_data[attribute.name()]) + else: + new.string_value = "" if new.is_null else classification_data[attribute.name()] + props.active_classification_id = self.classification + return {"FINISHED"} + + +class DisableEditingClassification(bpy.types.Operator): + bl_idname = "bim.disable_editing_classification" + bl_label = "Disable Editing Classification" + + def execute(self, context): + context.scene.BIMClassificationProperties.active_classification_id = 0 + return {"FINISHED"} + + +class RemoveClassification(bpy.types.Operator): + bl_idname = "bim.remove_classification" + bl_label = "Remove Classification" + classification: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + remove_classification.Usecase(self.file, {"classification": self.file.by_id(self.classification)}).execute() + Data.load() + return {"FINISHED"} + + +class EditClassification(bpy.types.Operator): + bl_idname = "bim.edit_classification" + bl_label = "Edit Classification" + + def execute(self, context): + props = context.scene.BIMClassificationProperties + attributes = {} + for attribute in props.classification_attributes: + if attribute.is_null: + attributes[attribute.name] = None + elif attribute.name == "ReferenceTokens": + attributes[attribute.name] = json.loads(attribute.string_value) + else: + attributes[attribute.name] = attribute.string_value + self.file = IfcStore.get_file() + edit_classification.Usecase( + self.file, {"classification": self.file.by_id(props.active_classification_id), "attributes": attributes} + ).execute() + Data.load() + bpy.ops.bim.disable_editing_classification() + return {"FINISHED"} + + +class EnableEditingClassificationReference(bpy.types.Operator): + bl_idname = "bim.enable_editing_classification_reference" + bl_label = "Enable Editing Classification Reference" + reference: bpy.props.IntProperty() + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + props = obj.BIMClassificationReferenceProperties + while len(props.reference_attributes) > 0: + props.reference_attributes.remove(0) + reference_data = Data.references[self.reference] + for attribute in IfcStore.get_schema().declaration_by_name("IfcClassificationReference").all_attributes(): + if attribute.name() == "ReferencedSource": + continue + new = props.reference_attributes.add() + new.name = attribute.name() + new.is_null = reference_data[attribute.name()] is None + new.is_optional = attribute.optional() + new.string_value = "" if new.is_null else reference_data[attribute.name()] + props.active_reference_id = self.reference + return {"FINISHED"} + + +class DisableEditingClassificationReference(bpy.types.Operator): + bl_idname = "bim.disable_editing_classification_reference" + bl_label = "Disable Editing Classification Reference" + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + obj.BIMClassificationReferenceProperties.active_reference_id = 0 + return {"FINISHED"} + + +class RemoveClassificationReference(bpy.types.Operator): + bl_idname = "bim.remove_classification_reference" + bl_label = "Remove Classification Reference" + reference: bpy.props.IntProperty() + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + self.file = IfcStore.get_file() + remove_reference.Usecase( + self.file, + { + "reference": self.file.by_id(self.reference), + "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), + }, + ).execute() + Data.load(obj.BIMObjectProperties.ifc_definition_id) + Data.load() + return {"FINISHED"} + + +class EditClassificationReference(bpy.types.Operator): + bl_idname = "bim.edit_classification_reference" + bl_label = "Edit Classification Reference" + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + props = obj.BIMClassificationReferenceProperties + attributes = {} + for attribute in props.reference_attributes: + if attribute.is_null: + attributes[attribute.name] = None + else: + attributes[attribute.name] = attribute.string_value + self.file = IfcStore.get_file() + edit_reference.Usecase( + self.file, {"reference": self.file.by_id(props.active_reference_id), "attributes": attributes} + ).execute() + Data.load() + bpy.ops.bim.disable_editing_classification_reference() + return {"FINISHED"} + + +class AddClassificationReference(bpy.types.Operator): + bl_idname = "bim.add_classification_reference" + bl_label = "Add Classification Reference" + reference: bpy.props.IntProperty() + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + self.file = IfcStore.get_file() + + classification = None + + props = context.scene.BIMClassificationProperties + classification_name = Data.library_classifications[int(props.available_classifications)] + for classification_id, classification in Data.classifications.items(): + if classification["Name"] == classification_name: + classification = self.file.by_id(classification_id) + break + + add_reference.Usecase( + self.file, + { + "reference": Data.library_file.by_id(self.reference), + "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), + "classification": classification + }, + ).execute() + Data.load(obj.BIMObjectProperties.ifc_definition_id) + Data.load() + return {"FINISHED"} + + +class ChangeClassificationLevel(bpy.types.Operator): + bl_idname = "bim.change_classification_level" + bl_label = "Change Classification Level" + parent_id: bpy.props.IntProperty() + + def execute(self, context): + getReferences(self, context, parent_id=self.parent_id) + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/prop.py b/src/ifcblenderexport/blenderbim/bim/module/classification/prop.py new file mode 100644 index 0000000000..3793b29ec3 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/prop.py @@ -0,0 +1,71 @@ +import bpy +from blenderbim.bim.prop import StrProperty, Attribute +from blenderbim.bim.module.classification.data import Data +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + +classification_enum = [] + + +def getClassifications(self, context): + global classification_enum + if len(classification_enum) < 1: + classification_enum.clear() + classification_enum.extend([(str(i), n, "") for i, n in Data.library_classifications.items()]) + if classification_enum: + getReferences(self, context, parent_id=int(classification_enum[0][0])) + return classification_enum + + +def updateClassification(self, context): + getReferences(self, context, parent_id=int(self.available_classifications)) + + +def getReferences(self, context, parent_id=None): + props = context.scene.BIMClassificationProperties + while len(props.available_library_references) > 0: + props.available_library_references.remove(0) + for reference in Data.library_file.by_id(parent_id).HasReferences: + new = props.available_library_references.add() + new.identification = reference.Identification or "" + new.name = reference.Name or "" + new.ifc_definition_id = reference.id() + new.has_references = bool(reference.HasReferences) + new.referenced_source + if reference.ReferencedSource.is_a("IfcClassificationReference"): + props.active_library_referenced_source = reference.ReferencedSource.ReferencedSource.id() + else: + props.active_library_referenced_source = 0 + + +class ClassificationReference(PropertyGroup): + name: StringProperty(name="Name") + identification: StringProperty(name="Identification") + ifc_definition_id: IntProperty(name="IFC Definition ID") + has_references: BoolProperty(name="Has References") + referenced_source: IntProperty(name="IFC Definition ID") + + +class BIMClassificationProperties(PropertyGroup): + available_classifications: EnumProperty( + items=getClassifications, name="Available Classifications", update=updateClassification + ) + classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute) + active_classification_id: IntProperty(name="Active Classification Id") + available_library_references: CollectionProperty(name="Available Library References", type=ClassificationReference) + active_library_referenced_source: IntProperty(name="Active Library Referenced Source") + active_library_reference_index: IntProperty(name="Active Library Reference Index") + + +class BIMClassificationReferenceProperties(PropertyGroup): + reference_attributes: CollectionProperty(name="Reference Attributes", type=Attribute) + active_reference_id: IntProperty(name="Active Reference Id") diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/remove_classification.py b/src/ifcblenderexport/blenderbim/bim/module/classification/remove_classification.py new file mode 100644 index 0000000000..dccd8c2e62 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/remove_classification.py @@ -0,0 +1,27 @@ +import ifcopenshell.util.schema + + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = {"classification": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + references = self.get_references(self.settings["classification"]) + for reference in references: + self.file.remove(reference) + self.file.remove(self.settings["classification"]) + for rel in self.file.by_type("IfcRelAssociatesClassification"): + if not rel.RelatingClassification: + self.file.remove(rel) + + def get_references(self, classification): + results = [] + if not classification.HasReferences: + return results + for reference in classification.HasReferences: + results.append(reference) + results.extend(self.get_references(reference)) + return results diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/remove_reference.py b/src/ifcblenderexport/blenderbim/bim/module/classification/remove_reference.py new file mode 100644 index 0000000000..5ca45a19cf --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/remove_reference.py @@ -0,0 +1,28 @@ +import ifcopenshell.util.schema + + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = {"reference": None, "product": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + total_related_objects = 0 + for association in self.file.by_type("IfcRelAssociatesClassification"): + if association.RelatingClassification == self.settings["reference"] and association.RelatedObjects: + total_related_objects += len(association.RelatedObjects) + related_objects = list(association.RelatedObjects) + try: + related_objects.remove(self.settings["product"]) + except: + continue + if len(related_objects): + association.RelatedObjects = related_objects + else: + self.file.remove(association) + + # TODO: we only handle lightweight classifications here + if total_related_objects == 1: + self.file.remove(self.settings["reference"]) diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/ui.py b/src/ifcblenderexport/blenderbim/bim/module/classification/ui.py new file mode 100644 index 0000000000..122201c8e9 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/ui.py @@ -0,0 +1,159 @@ +from bpy.types import Panel, UIList +from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.classification.data import Data + + +class BIM_PT_classifications(Panel): + bl_label = "IFC Classifications" + bl_idname = "BIM_PT_classifications" + 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() + + self.props = context.scene.BIMClassificationProperties + + if Data.library_file: + row = self.layout.row(align=True) + row.prop(self.props, "available_classifications", text="") + row.operator("bim.load_classification_library", text="", icon="IMPORT") + row.operator("bim.add_classification", text="", icon="ADD") + else: + row = self.layout.row(align=True) + row.label(text="No Active Classification Library") + row.operator("bim.load_classification_library", text="", icon="IMPORT") + + for classification_id, classification in Data.classifications.items(): + if self.props.active_classification_id == classification_id: + self.draw_editable_ui(classification) + else: + self.draw_ui(classification_id, classification) + + def draw_editable_ui(self, classification): + row = self.layout.row(align=True) + row.prop(self.props.classification_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER") + row.operator("bim.edit_classification", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_classification", text="", icon="X") + + for attribute in self.props.classification_attributes: + if attribute.name == "Name": + continue + 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="") + + def draw_ui(self, classification_id, classification): + row = self.layout.row(align=True) + row.label(text=classification["Name"], icon="ASSET_MANAGER") + if not self.props.active_classification_id: + op = row.operator("bim.enable_editing_classification", text="", icon="GREASEPENCIL") + op.classification = classification_id + row.operator("bim.remove_classification", text="", icon="X").classification = classification_id + + +class BIM_PT_classification_references(Panel): + bl_label = "IFC Classification References" + bl_idname = "BIM_PT_classification_references" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + + @classmethod + def poll(cls, context): + return bool(context.active_object.BIMObjectProperties.ifc_definition_id) + + def draw(self, context): + obj = context.active_object + self.oprops = obj.BIMObjectProperties + self.sprops = context.scene.BIMClassificationProperties + self.props = obj.BIMClassificationReferenceProperties + self.file = IfcStore.get_file() + if not Data.is_loaded: + Data.load() + if self.oprops.ifc_definition_id not in Data.products: + Data.load(self.oprops.ifc_definition_id) + + self.draw_add_ui() + + reference_ids = Data.products[self.oprops.ifc_definition_id] + if not reference_ids: + row = self.layout.row(align=True) + row.label(text="No References") + + for reference_id in reference_ids: + reference = Data.references[reference_id] + if self.props.active_reference_id == reference_id: + self.draw_editable_ui(reference) + else: + self.draw_ui(reference_id, reference) + + def draw_add_ui(self): + if not self.sprops.available_classifications: + return + + name = Data.library_classifications[int(self.sprops.available_classifications)] + if name in [c["Name"] for c in Data.classifications.values()]: + row = self.layout.row(align=True) + row.prop(self.sprops, "available_classifications", text="") + if self.sprops.active_library_referenced_source: + op = row.operator("bim.change_classification_level", text="", icon="FRAME_PREV") + op.parent_id = self.sprops.active_library_referenced_source + op = row.operator("bim.add_classification_reference", text="", icon="ADD") + op.reference = self.sprops.available_library_references[ + self.sprops.active_library_reference_index + ].ifc_definition_id + self.layout.template_list( + "BIM_UL_classifications", + "", + self.sprops, + "available_library_references", + self.sprops, + "active_library_reference_index", + ) + + def draw_editable_ui(self, reference): + row = self.layout.row(align=True) + row.prop(self.props.reference_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER") + row.operator("bim.edit_classification_reference", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_classification_reference", text="", icon="X") + + for attribute in self.props.reference_attributes: + if attribute.name == "Name": + continue + 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="") + + def draw_ui(self, reference_id, reference): + row = self.layout.row(align=True) + if self.file.schema == "IFC2X3": + name = reference["ItemReference"] or "No Identification" + else: + name = reference["Identification"] or "No Identification" + row.label(text=name, icon="ASSET_MANAGER") + row.label(text=reference["Name"] or "") + if not self.props.active_reference_id: + op = row.operator("bim.enable_editing_classification_reference", text="", icon="GREASEPENCIL") + op.reference = reference_id + row.operator("bim.remove_classification_reference", text="", icon="X").reference = reference_id + + +class BIM_UL_classifications(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + if item.has_references: + op = layout.operator("bim.change_classification_level", text="", icon="DISCLOSURE_TRI_RIGHT") + op.parent_id = item.ifc_definition_id + layout.label(text=item.identification) + layout.label(text=item.name) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index dd95539cdf..eb4e0aceef 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1156,106 +1156,6 @@ class SelectSchemaDir(bpy.types.Operator): return {"RUNNING_MODAL"} -class LoadClassification(bpy.types.Operator): - bl_idname = "bim.load_classification" - bl_label = "Load Classification" - is_file: bpy.props.BoolProperty() - classification_index: bpy.props.IntProperty() - - def execute(self, context): - from . import prop - - if self.is_file: - prop.ClassificationView.raw_data = schema.ifc.load_classification( - context.scene.BIMProperties.classification - ) - else: - prop.ClassificationView.raw_data = schema.ifc.load_classification( - context.scene.BIMProperties.classifications[self.classification_index].name, self.classification_index - ) - context.scene.BIMProperties.classification_references.root = "" - return {"FINISHED"} - - -class AddClassification(bpy.types.Operator): - bl_idname = "bim.add_classification" - bl_label = "Add Classification" - - def execute(self, context): - if context.scene.BIMProperties.classification not in schema.ifc.classifications: - return {"FINISHED"} - data = schema.ifc.classifications[context.scene.BIMProperties.classification] - classification = context.scene.BIMProperties.classifications.add() - data_map = { - "name": "Name", - "source": "Source", - "edition": "Edition", - "edition_date": "EditionDate", - "description": "Description", - "location": "Location", - "reference_tokens": "ReferenceTokens", - } - for key, value in data_map.items(): - if hasattr(data, value) and getattr(data, value): - setattr(classification, key, str(getattr(data, value))) - classification.data = schema.ifc.classification_files[context.scene.BIMProperties.classification].to_string() - return {"FINISHED"} - - -class RemoveClassification(bpy.types.Operator): - bl_idname = "bim.remove_classification" - bl_label = "Remove Classification" - classification_index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.scene.BIMProperties.classifications.remove(self.classification_index) - return {"FINISHED"} - - -class AssignClassification(bpy.types.Operator): - bl_idname = "bim.assign_classification" - bl_label = "Assign Classification" - - def execute(self, context): - for obj in bpy.context.selected_objects: - classification = obj.BIMObjectProperties.classifications.add() - refs = bpy.context.scene.BIMProperties.classification_references - data = refs.root["children"][refs.children[refs.active_index].name] - if data["identification"]: - classification.name = data["identification"] - if data["name"]: - classification.human_name = data["name"] - for key in ["location", "description"]: - if data[key]: - setattr(classification, key, data[key]) - classification.referenced_source = bpy.context.scene.BIMProperties.active_classification_name - return {"FINISHED"} - - -class UnassignClassification(bpy.types.Operator): - bl_idname = "bim.unassign_classification" - bl_label = "Unassign Classification" - - def execute(self, context): - refs = bpy.context.scene.BIMProperties.classification_references - key = refs.children[refs.active_index].name - for obj in bpy.context.selected_objects: - index = obj.BIMObjectProperties.classifications.find(key) - if index != -1: - obj.BIMObjectProperties.classifications.remove(index) - return {"FINISHED"} - - -class RemoveClassificationReference(bpy.types.Operator): - bl_idname = "bim.remove_classification_reference" - bl_label = "Remove Classification Reference" - classification_index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.BIMObjectProperties.classifications.remove(self.classification_index) - return {"FINISHED"} - - class FetchExternalMaterial(bpy.types.Operator): bl_idname = "bim.fetch_external_material" bl_label = "Fetch External Material" @@ -1735,27 +1635,6 @@ class CopyAttributeToSelection(bpy.types.Operator): return self.applicable_attributes_cache[ifc_class] -class BIM_OT_ChangeClassificationLevel(bpy.types.Operator): - bl_idname = "bim.change_classification_level" - bl_label = "Change Classification Level" - - # string representing the id-data (e.g. the scene). - path_sid: bpy.props.StringProperty() - # path from the id-data to the classification view object - path_lst: bpy.props.StringProperty() - # name of child entity to enter (empty = go up one level) - path_itm: bpy.props.StringProperty() - - def invoke(self, context, event): - id_data = eval(self.path_sid) - lst = id_data.path_resolve(self.path_lst) - if self.path_itm: - lst.root = self.path_itm - else: - lst.root = "" - return {"FINISHED"} - - class AddPropertySetTemplate(bpy.types.Operator): bl_idname = "bim.add_property_set_template" bl_label = "Add Property Set Template" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index bf67b2e656..58da0035d5 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -37,7 +37,6 @@ materialpsetnames_enum = [] psetfiles_enum = [] psettemplatefiles_enum = [] propertysettemplates_enum = [] -classification_enum = [] attributes_enum = [] materialattributes_enum = [] contexts_enum = [] @@ -357,21 +356,6 @@ def getPropertySetTemplates(self, context): return propertysettemplates_enum -def getClassifications(self, context): - global classification_enum - if len(classification_enum) < 1: - classification_enum.clear() - files = os.listdir(os.path.join(self.schema_dir, "classifications")) - classification_enum.extend([(f.replace(".ifc", ""), f.replace(".ifc", ""), "") for f in files]) - return classification_enum - - -def refreshReferences(self, context): - context.scene.BIMProperties.classification_references.root = None - ClassificationView.raw_data = schema.ifc.load_classification(context.scene.BIMProperties.classification) - context.scene.BIMProperties.classification_references.root = "" - - def getMaterialPsetNames(self, context): global materialpsetnames_enum materialpsetnames_enum.clear() @@ -922,76 +906,6 @@ class PropertyTemplate(PropertyGroup): ) -class Classification(PropertyGroup): - name: StringProperty(name="Name") - source: StringProperty(name="Source") - edition: StringProperty(name="Edition") - edition_date: StringProperty(name="Edition Date") - description: StringProperty(name="Description") - location: StringProperty(name="Location") - reference_tokens: StringProperty(name="Reference Tokens") - data: StringProperty(name="Data") - - -class ClassificationReference(PropertyGroup): - name: StringProperty(name="Identification") - location: StringProperty(name="Location") - human_name: StringProperty(name="Name") - referenced_source: StringProperty(name="Source") - description: StringProperty(name="Description") - sort: StringProperty(name="Sort") - - -class ClassificationView(PropertyGroup): - crumbs: None - children: None - active_index: bpy.props.IntProperty() - raw_data = {} - - @property - def root(self): - data = self.raw_data - for crumb in self.crumbs: - data = data["children"].get(crumb.name) - if not data: - raise TypeError("Cannot resolve crumb path") - return data - - @root.setter - def root(self, rt): - if rt == None: - self.crumbs.clear() - self.children.clear() - elif rt == "": - if self.crumbs: - self.crumbs.remove(len(self.crumbs) - 1) - self.children.clear() - for child in self.root["children"].keys(): - self.children.add().name = child - else: - data = self.root - if rt in data["children"].keys(): - self.crumbs.add().name = rt - self.children.clear() - for child in data["children"][rt]["children"].keys(): - self.children.add().name = child - - def draw_stub(self, context, layout): - if not self.children: - op = layout.operator("bim.change_classification_level", text="@Toplevel") - else: - op = layout.operator("bim.change_classification_level", text=self.root["name"]) - op.path_sid = "%r" % self.id_data - op.path_lst = self.path_from_id() - op.path_itm = "" - layout.template_list("BIM_UL_classifications", self.path_from_id(), self, "children", self, "active_index") - - -# Monkey-patched, just to keep registration in one block -ClassificationView.__annotations__["crumbs"] = bpy.props.CollectionProperty(type=StrProperty) -ClassificationView.__annotations__["children"] = bpy.props.CollectionProperty(type=StrProperty) - - class BIMProperties(PropertyGroup): schema_dir: StringProperty(default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory") data_dir: StringProperty(default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory") @@ -1042,14 +956,10 @@ class BIMProperties(PropertyGroup): search_pset_name: StringProperty(name="Search Pset Name") search_prop_name: StringProperty(name="Search Prop Name") search_pset_value: StringProperty(name="Search Pset Value") - classification: EnumProperty(items=getClassifications, name="Classification", update=refreshReferences) - active_classification_name: StringProperty(name="Active Classification Name") - classifications: CollectionProperty(name="Classifications", type=Classification) contexts: EnumProperty(items=getContexts, name="Contexts") available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts") available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts") available_target_views: EnumProperty(items=getTargetViews, name="Available Target Views") - classification_references: PointerProperty(type=ClassificationView) pset_template_files: EnumProperty( items=getPsetTemplateFiles, name="Pset Template Files", update=refreshPropertySetTemplates ) @@ -1188,7 +1098,6 @@ class BIMObjectProperties(PropertyGroup): active_document_reference_index: IntProperty(name="Active Document Reference Index") constraints: CollectionProperty(name="Constraints", type=Constraint) active_constraint_index: IntProperty(name="Active Constraint Index") - classifications: CollectionProperty(name="Classifications", type=ClassificationReference) has_boundary_condition: BoolProperty(name="Has Boundary Condition") boundary_condition: PointerProperty(name="Boundary Condition", type=BoundaryCondition) structural_member_connection: PointerProperty(name="Structural Member Connection", type=bpy.types.Object) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index ec0de71387..291184a569 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -266,36 +266,6 @@ class BIM_PT_constraint_relations(Panel): layout.label(text="Constraint is invalid") -class BIM_PT_classification_references(Panel): - bl_label = "IFC Classification References" - bl_idname = "BIM_PT_classification_references" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "object" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - props = context.active_object.BIMObjectProperties - - if not props.classifications: - layout.label(text="No classifications found") - - for index, classification in enumerate(props.classifications): - row = layout.row(align=True) - row.prop(classification, "name") - row.operator("bim.remove_classification_reference", icon="X", text="").classification_index = index - row = layout.row(align=True) - row.prop(classification, "human_name") - row = layout.row(align=True) - row.prop(classification, "location") - row = layout.row(align=True) - row.prop(classification, "description") - row = layout.row(align=True) - row.prop(classification, "referenced_source") - - class BIM_PT_psets(Panel): bl_label = "IFC Property Sets" bl_idname = "BIM_PT_psets" @@ -340,58 +310,6 @@ class BIM_PT_psets(Panel): row.operator("bim.remove_property_template", icon="X", text="").index = index -class BIM_PT_classifications(Panel): - bl_label = "IFC Classifications" - bl_idname = "BIM_PT_classifications" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - props = context.scene.BIMProperties - - row = layout.row(align=True) - row.prop(props, "classification", text="") - row.operator("bim.add_classification", text="", icon="ADD") - - if context.scene.BIMProperties.classification_references.raw_data: - context.scene.BIMProperties.classification_references.draw_stub(context, layout) - row = layout.row(align=True) - row.operator("bim.assign_classification") - row.operator("bim.unassign_classification") - else: - row = layout.row(align=True) - row.operator("bim.load_classification").is_file = True - - if not props.classifications: - return - - layout.label(text="Classifications:") - - for index, classification in enumerate(props.classifications): - row = layout.row(align=True) - row.prop(classification, "name") - row.operator("bim.load_classification", icon="IMPORT", text="").classification_index = index - row.operator("bim.remove_classification", icon="X", text="").classification_index = index - row = layout.row(align=True) - row.prop(classification, "source") - row = layout.row(align=True) - row.prop(classification, "edition") - row = layout.row(align=True) - row.prop(classification, "edition_date") - row = layout.row(align=True) - row.prop(classification, "description") - row = layout.row(align=True) - row.prop(classification, "location") - row = layout.row(align=True) - row.prop(classification, "reference_tokens") - - row = layout.row() - row.prop(props, "classifications") - - class BIM_PT_presentation_layer_data(Panel): bl_label = "IFC Presentation Layers" bl_idname = "BIM_PT_presentation" @@ -1015,25 +933,6 @@ class BIM_UL_document_references(bpy.types.UIList): layout.label(text="", translate=False) -class BIM_UL_classifications(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - if self.layout_type in {"DEFAULT", "COMPACT"}: - rt = data.root - ch = rt["children"] - itemdata = ch[item.name] - if itemdata.get("children", {}): - op = layout.operator( - "bim.change_classification_level", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" - ) - op.path_sid = "%r" % active_data.id_data # get id-data - op.path_lst = active_data.path_from_id() # path to view - op.path_itm = item.name # name of child. empty = go up - else: - layout.label(text="", icon="BLANK1") - layout.prop(item, "name", text="", emboss=False) - layout.label(text=itemdata["name"]) - - class BIM_ADDON_preferences(bpy.types.AddonPreferences): bl_idname = "blenderbim" svg2pdf_command: StringProperty(name="SVG to PDF Command", description="E.g. [['inkscape', svg, '-o', pdf]]")