From 5a82d7a9d44c888b58077dba7530daef9e3903b8 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 22 May 2024 20:52:43 +1000 Subject: [PATCH] Refactor enabling pset editing operator into core/tool --- .../blenderbim/bim/module/pset/operator.py | 187 +----------------- src/blenderbim/blenderbim/core/pset.py | 16 ++ src/blenderbim/blenderbim/core/tool.py | 9 + src/blenderbim/blenderbim/tool/pset.py | 165 +++++++++++++++- 4 files changed, 195 insertions(+), 182 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 44f72a8f50..0d4037b9f3 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -31,7 +31,6 @@ import blenderbim.core.qto as QtoCore import blenderbim.bim.module.pset.data from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.pset.qto_calculator import QtoCalculator -from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper class Operator: @@ -41,27 +40,6 @@ class Operator: return {"FINISHED"} -def get_pset_props(context, obj, obj_type): - if obj_type == "Object": - return bpy.data.objects.get(obj).PsetProperties - elif obj_type == "Material": - return bpy.data.materials.get(obj).PsetProperties - elif obj_type == "MaterialSet": - return bpy.data.objects.get(obj).MaterialSetPsetProperties - elif obj_type == "MaterialSetItem": - return bpy.data.objects.get(obj).MaterialSetItemPsetProperties - elif obj_type == "Task": - return context.scene.TaskPsetProperties - elif obj_type == "Resource": - return context.scene.ResourcePsetProperties - elif obj_type == "Profile": - return context.scene.ProfilePsetProperties - elif obj_type == "WorkSchedule": - return context.scene.WorkSchedulePsetProperties - elif obj_type == "Group": - return context.scene.GroupPsetProperties - - class TogglePsetExpansion(bpy.types.Operator, Operator): bl_idname = "bim.toggle_pset_expansion" bl_label = "Toggle Pset Expansion" @@ -84,168 +62,15 @@ class EnablePsetEditing(bpy.types.Operator): obj_type: bpy.props.StringProperty() def execute(self, context): - self.props = get_pset_props(context, self.obj, self.obj_type) - self.props.properties.clear() - if self.pset_id: pset = tool.Ifc.get().by_id(self.pset_id) - self.props.active_pset_name = pset.Name - self.props.active_pset_type = "" - pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(pset.Name) + self.pset_name = pset.Name else: pset = None - self.props.active_pset_name = self.pset_name - self.props.active_pset_type = self.pset_type - pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.pset_name) - if pset_template: - self.load_from_pset_template(pset_template, pset) - self.load_from_pset_data(pset) - - self.props.active_pset_id = self.pset_id + core.enable_pset_editing(tool.Pset, pset, self.pset_name, self.pset_type, self.obj, self.obj_type) return {"FINISHED"} - def load_from_pset_template(self, pset_template, pset): - if pset: - data = ifcopenshell.util.element.get_property_definition(pset) - del data["id"] - else: - data = {} - for prop_template in pset_template.HasPropertyTemplates: - if not prop_template.is_a("IfcSimplePropertyTemplate"): - continue # Other types not yet supported - if prop_template.TemplateType == "P_SINGLEVALUE": - self.load_single_value(pset_template, prop_template, data) - elif prop_template.TemplateType.startswith("Q_"): - self.load_single_value(pset_template, prop_template, data) - elif prop_template.TemplateType == "P_ENUMERATEDVALUE": - self.load_enumerated_value(prop_template, data) - else: - # NOTE: currently unsupported types: - # - P_BOUNDEDVALUE - # - P_LISTVALUE - # - P_REFERENCEVALUE - # - P_TABLEVALUE - pass - - def load_single_value(self, pset_template, prop_template, data): - prop = self.props.properties.add() - prop.name = prop_template.Name - prop.value_type = "IfcPropertySingleValue" - metadata = prop.metadata - metadata.name = prop_template.Name - metadata.is_null = data.get(prop_template.Name, None) is None - metadata.is_optional = True - metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference" - metadata.has_calculator = bool(mapper.get(pset_template.Name, {}).get(prop_template.Name, None)) - metadata.data_type = self.get_data_type(prop_template) - - special_type = "" - if ( - prop_template.PrimaryMeasureType - in ( - "IfcPositiveLengthMeasure", - "IfcLengthMeasure", - ) - or prop_template.TemplateType == "Q_LENGTH" - ): - special_type = "LENGTH" - elif prop_template.PrimaryMeasureType == "IfcAreaMeasure" or prop_template.TemplateType == "Q_AREA": - special_type = "AREA" - elif prop_template.PrimaryMeasureType == "IfcVolumeMeasure" or prop_template.TemplateType == "Q_VOLUME": - special_type = "VOLUME" - metadata.special_type = special_type - - if metadata.data_type == "string": - metadata.string_value = "" if metadata.is_null else str(data[prop_template.Name]) - elif metadata.data_type == "integer": - metadata.int_value = 0 if metadata.is_null else int(data[prop_template.Name]) - elif metadata.data_type == "float": - metadata.float_value = 0.0 if metadata.is_null else float(data[prop_template.Name]) - elif metadata.data_type == "boolean": - metadata.bool_value = False if metadata.is_null else bool(data[prop_template.Name]) - - metadata.ifc_class = pset_template.Name - blenderbim.bim.helper.add_attribute_description(metadata, prop_template) - - def get_data_type(self, prop_template): - if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]: - return "float" - elif prop_template.TemplateType == "Q_COUNT": - return "integer" - return ifcopenshell.util.attribute.get_primitive_type( - IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel") - ) - - def load_enumerated_value(self, prop_template, data): - enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues] - selected_enum_items = data.get(prop_template.Name, []) or [] - - prop = self.props.properties.add() - prop.name = prop_template.Name - prop.value_type = "IfcPropertyEnumeratedValue" - metadata = prop.metadata - metadata.name = prop_template.Name - metadata.is_null = data.get(prop_template.Name, None) is None - metadata.is_optional = True - metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference" - - # Cute hack to abuse the metadata to find the Blender data_type - metadata.set_value(enum_items[0]) - data_type = metadata.get_value_name() - - for enum in enum_items: - new = prop.enumerated_value.enumerated_values.add() - setattr(new, data_type, enum) - new.is_selected = enum in selected_enum_items - - def load_from_pset_data(self, pset): - if pset is None: - return - - props = [] - if pset.is_a("IfcElementQuantity"): - props = pset.Quantities - elif pset.is_a("IfcPropertySet"): - props = pset.HasProperties - elif pset.is_a("IfcMaterialProperties") or pset.is_a("IfcProfileProperties"): - props = pset.Properties - - for prop in props: - if self.props.properties.get(prop.Name): - continue # This property has already been added from a template - if prop.is_a("IfcPropertyEnumeratedValue"): - simple_prop = self.props.properties.add() - simple_prop.name = prop.Name - simple_prop.value_type = "IfcPropertyEnumeratedValue" - metadata = simple_prop.metadata - metadata.name = prop.Name - metadata.is_null = len(simple_prop.enumerated_value.enumerated_values) == 0 - metadata.is_optional = True - metadata.set_value(prop.EnumerationReference.EnumerationValues[0].wrappedValue) - - enum_items = [v.wrappedValue for v in prop.EnumerationReference.EnumerationValues] - selected_enum_items = [v.wrappedValue for v in prop.EnumerationValues] - data_type = metadata.get_value_name(display_only=True) - - for enum in enum_items: - new = simple_prop.enumerated_value.enumerated_values.add() - setattr(new, data_type, enum) - new.is_selected = enum in selected_enum_items - else: - if prop.is_a("IfcPropertySingleValue"): - value = prop.NominalValue.wrappedValue if prop.NominalValue else None - elif prop.is_a("IfcPhysicalSimpleQuantity"): - value = prop[3] - new_prop = self.props.properties.add() - new_prop.name = prop.Name - metadata = new_prop.metadata - metadata.set_value(value) - metadata.name = prop.Name - metadata.is_null = value is None - metadata.is_optional = True - metadata.set_value(metadata.get_value_default() if metadata.is_null else value) - class DisablePsetEditing(bpy.types.Operator, Operator): bl_idname = "bim.disable_pset_editing" @@ -255,7 +80,7 @@ class DisablePsetEditing(bpy.types.Operator, Operator): obj_type: bpy.props.StringProperty() def _execute(self, context): - props = get_pset_props(context, self.obj, self.obj_type) + props = tool.Pset.get_pset_props(self.obj, self.obj_type) if props.active_pset_id: pset = tool.Ifc.get().by_id(props.active_pset_id) ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context) @@ -279,7 +104,7 @@ class EditPset(bpy.types.Operator, Operator): def _execute(self, context): self.file = IfcStore.get_file() - props = get_pset_props(context, self.obj, self.obj_type) + props = tool.Pset.get_pset_props(self.obj, self.obj_type) ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context) element = tool.Ifc.get().by_id(ifc_definition_id) properties = {} @@ -350,7 +175,7 @@ class RemovePset(bpy.types.Operator, Operator): objects = [self.obj] pset_name = tool.Ifc.get().by_id(self.pset_id).Name for obj in objects: - props = get_pset_props(context, obj, self.obj_type) + props = tool.Pset.get_pset_props(obj, self.obj_type) ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context) element = tool.Ifc.get().by_id(ifc_definition_id) pset = ifcopenshell.util.element.get_psets(element, should_inherit=False).get(pset_name, None) @@ -381,7 +206,7 @@ class AddQto(bpy.types.Operator, Operator): def _execute(self, context): self.file = IfcStore.get_file() - props = get_pset_props(context, self.obj, self.obj_type) + props = tool.Pset.get_pset_props(self.obj, self.obj_type) ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context) element = tool.Ifc.get().by_id(ifc_definition_id) bpy.ops.bim.enable_pset_editing( diff --git a/src/blenderbim/blenderbim/core/pset.py b/src/blenderbim/blenderbim/core/pset.py index e394256e67..cd58736cf2 100644 --- a/src/blenderbim/blenderbim/core/pset.py +++ b/src/blenderbim/blenderbim/core/pset.py @@ -42,3 +42,19 @@ def add_pset(ifc, pset, blender, obj_name, obj_type): if not pset.is_pset_applicable(element, pset_name): continue pset.enable_pset_editing(pset_id=0, pset_name=pset_name, pset_type="PSET", obj=obj_name, obj_type=obj_type) + + +def enable_pset_editing(pset_tool, pset, pset_name, pset_type, obj_name, obj_type): + props = pset_tool.get_pset_props(obj_name, obj_type) + pset_tool.clear_blender_pset_properties(props) + + pset_template = pset_tool.get_pset_template(pset_name) + + if pset_template: + pset_tool.import_pset_from_template(pset_template, pset, props) + + if pset: + pset_tool.import_pset_from_existing(pset, props) + pset_tool.set_active_pset(props, pset) + else: + pset_tool.enable_proposed_pset(props, pset_name, pset_type) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index ec5a7550c3..35607cd90e 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -618,9 +618,18 @@ class Profile: @interface class Pset: + def clear_blender_pset_properties(cls, props): pass + def enable_proposed_pset(cls, props, pset_name, pset_type): pass def get_element_pset(cls, element, pset_name): pass + def get_prop_template_primitive_type(cls, prop_template): pass def get_pset_name(cls, obj, obj_type): pass + def get_pset_template(cls, name): pass + def import_enumerated_value_from_template(cls, prop_template, data, props): pass + def import_pset_from_existing(cls, pset, props): pass + def import_pset_from_template(cls, pset_template, pset, props): pass + def import_single_value_from_template(cls, pset_template, prop_template, data, props): pass def is_pset_applicable(cls,element, pset_name): pass + def set_active_pset(cls, props, pset): pass @interface diff --git a/src/blenderbim/blenderbim/tool/pset.py b/src/blenderbim/blenderbim/tool/pset.py index 0b4db9e9cb..783931786f 100644 --- a/src/blenderbim/blenderbim/tool/pset.py +++ b/src/blenderbim/blenderbim/tool/pset.py @@ -23,6 +23,7 @@ import blenderbim.core.tool import blenderbim.tool as tool import blenderbim.bim.schema from typing import Union +from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper class Pset(blenderbim.core.tool.Pset): @@ -82,5 +83,167 @@ class Pset(blenderbim.core.tool.Pset): def enable_pset_editing(cls, pset_id=None, pset_name=None, pset_type=None, obj=None, obj_type=None): # TODO REFACTOR ONCE toll/CORE functions are available bpy.ops.bim.enable_pset_editing( - pset_id=0, pset_name=tool.Pset.get_pset_name(obj, obj_type), pset_type="PSET", obj=obj, obj_type=obj_type + pset_id=0, pset_name=cls.get_pset_name(obj, obj_type), pset_type="PSET", obj=obj, obj_type=obj_type ) + + @classmethod + def import_pset_from_existing(cls, pset, props): + pset_props = [] + if pset.is_a("IfcElementQuantity"): + pset_props = pset.Quantities + elif pset.is_a("IfcPropertySet"): + pset_props = pset.HasProperties + elif pset.is_a("IfcMaterialProperties") or pset.is_a("IfcProfileProperties"): + pset_props = pset.Properties + + for prop in pset_props: + if props.properties.get(prop.Name): + continue # This property has already been added from a template + if prop.is_a("IfcPropertyEnumeratedValue"): + simple_prop = props.properties.add() + simple_prop.name = prop.Name + simple_prop.value_type = "IfcPropertyEnumeratedValue" + metadata = simple_prop.metadata + metadata.name = prop.Name + metadata.is_null = len(simple_prop.enumerated_value.enumerated_values) == 0 + metadata.is_optional = True + metadata.set_value(prop.EnumerationReference.EnumerationValues[0].wrappedValue) + + enum_items = [v.wrappedValue for v in prop.EnumerationReference.EnumerationValues] + selected_enum_items = [v.wrappedValue for v in prop.EnumerationValues] + data_type = metadata.get_value_name(display_only=True) + + for enum in enum_items: + new = simple_prop.enumerated_value.enumerated_values.add() + setattr(new, data_type, enum) + new.is_selected = enum in selected_enum_items + else: + if prop.is_a("IfcPropertySingleValue"): + value = prop.NominalValue.wrappedValue if prop.NominalValue else None + elif prop.is_a("IfcPhysicalSimpleQuantity"): + value = prop[3] + new_prop = props.properties.add() + new_prop.name = prop.Name + metadata = new_prop.metadata + metadata.set_value(value) + metadata.name = prop.Name + metadata.is_null = value is None + metadata.is_optional = True + metadata.set_value(metadata.get_value_default() if metadata.is_null else value) + + @classmethod + def get_prop_template_primitive_type(cls, prop_template): + if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]: + return "float" + elif prop_template.TemplateType == "Q_COUNT": + return "integer" + return ifcopenshell.util.attribute.get_primitive_type( + tool.Ifc.schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel") + ) + + @classmethod + def import_enumerated_value_from_template(cls, prop_template, data, props): + enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues] + selected_enum_items = data.get(prop_template.Name, []) or [] + + prop = props.properties.add() + prop.name = prop_template.Name + prop.value_type = "IfcPropertyEnumeratedValue" + metadata = prop.metadata + metadata.name = prop_template.Name + metadata.is_null = data.get(prop_template.Name, None) is None + metadata.is_optional = True + metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference" + + # Cute hack to abuse the metadata to find the Blender data_type + metadata.set_value(enum_items[0]) + data_type = metadata.get_value_name() + + for enum in enum_items: + new = prop.enumerated_value.enumerated_values.add() + setattr(new, data_type, enum) + new.is_selected = enum in selected_enum_items + + @classmethod + def import_single_value_from_template(cls, pset_template, prop_template, data, props): + prop = props.properties.add() + prop.name = prop_template.Name + prop.value_type = "IfcPropertySingleValue" + metadata = prop.metadata + metadata.name = prop_template.Name + metadata.is_null = data.get(prop_template.Name, None) is None + metadata.is_optional = True + metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference" + metadata.has_calculator = bool(mapper.get(pset_template.Name, {}).get(prop_template.Name, None)) + metadata.data_type = cls.get_prop_template_primitive_type(prop_template) + + special_type = "" + if ( + prop_template.PrimaryMeasureType + in ( + "IfcPositiveLengthMeasure", + "IfcLengthMeasure", + ) + or prop_template.TemplateType == "Q_LENGTH" + ): + special_type = "LENGTH" + elif prop_template.PrimaryMeasureType == "IfcAreaMeasure" or prop_template.TemplateType == "Q_AREA": + special_type = "AREA" + elif prop_template.PrimaryMeasureType == "IfcVolumeMeasure" or prop_template.TemplateType == "Q_VOLUME": + special_type = "VOLUME" + metadata.special_type = special_type + + if metadata.data_type == "string": + metadata.string_value = "" if metadata.is_null else str(data[prop_template.Name]) + elif metadata.data_type == "integer": + metadata.int_value = 0 if metadata.is_null else int(data[prop_template.Name]) + elif metadata.data_type == "float": + metadata.float_value = 0.0 if metadata.is_null else float(data[prop_template.Name]) + elif metadata.data_type == "boolean": + metadata.bool_value = False if metadata.is_null else bool(data[prop_template.Name]) + + metadata.ifc_class = pset_template.Name + blenderbim.bim.helper.add_attribute_description(metadata, prop_template) + + @classmethod + def import_pset_from_template(cls, pset_template, pset, props): + if pset: + data = ifcopenshell.util.element.get_property_definition(pset) + del data["id"] + else: + data = {} + for prop_template in pset_template.HasPropertyTemplates: + if not prop_template.is_a("IfcSimplePropertyTemplate"): + continue # Other types not yet supported + if prop_template.TemplateType == "P_SINGLEVALUE": + cls.import_single_value_from_template(pset_template, prop_template, data, props) + elif prop_template.TemplateType.startswith("Q_"): + cls.import_single_value_from_template(pset_template, prop_template, data, props) + elif prop_template.TemplateType == "P_ENUMERATEDVALUE": + cls.import_enumerated_value_from_template(prop_template, data, props) + else: + # NOTE: currently unsupported types: + # - P_BOUNDEDVALUE + # - P_LISTVALUE + # - P_REFERENCEVALUE + # - P_TABLEVALUE + pass + + @classmethod + def clear_blender_pset_properties(cls, props): + props.properties.clear() + + @classmethod + def set_active_pset(cls, props, pset): + props.active_pset_id = pset.id() + props.active_pset_name = pset.Name + + @classmethod + def enable_proposed_pset(cls, props, pset_name, pset_type): + props.active_pset_id = 0 + props.active_pset_name = pset_name + props.active_pset_type = pset_type + + @classmethod + def get_pset_template(cls, name): + return blenderbim.bim.schema.ifc.psetqto.get_by_name(name)