From 084823164e3014b93eabb14185be8fdafb67f5ce Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 6 Jul 2022 18:25:29 +1000 Subject: [PATCH] Fix #2264 refactor property data structures to be separate from attributes --- src/blenderbim/blenderbim/bim/helper.py | 20 +- .../blenderbim/bim/module/pset/__init__.py | 4 +- .../blenderbim/bim/module/pset/operator.py | 190 ++++++++++-------- .../blenderbim/bim/module/pset/prop.py | 24 +-- .../blenderbim/bim/module/pset/ui.py | 42 +++- 5 files changed, 158 insertions(+), 122 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 8dc2442216..f0dab98427 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -38,21 +38,11 @@ def draw_attribute(attribute, layout, copy_operator=None): if not value_name: layout.label(text=attribute.name) return - if len(attribute.enumerated_values) != 0: - layout.label(text=attribute.name) - grid = layout.column_flow(columns=3) - for e in attribute.enumerated_values: - grid.prop( - e, - "is_selected", - text=str(e[value_name]) - ) - else: - layout.prop( - attribute, - value_name, - text=attribute.name, - ) + layout.prop( + attribute, + value_name, + text=attribute.name, + ) if attribute.is_optional: layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") if copy_operator: diff --git a/src/blenderbim/blenderbim/bim/module/pset/__init__.py b/src/blenderbim/blenderbim/bim/module/pset/__init__.py index c2d845661d..5e04e16662 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/pset/__init__.py @@ -35,8 +35,8 @@ classes = ( operator.BIM_OT_rename_parameters, operator.BIM_OT_add_edit_custom_property, operator.BIM_OT_bulk_remove_psets, - prop.EnumerationValues, - prop.IfcSimpleProperty, + prop.IfcPropertyEnumeratedValue, + prop.IfcProperty, prop.PsetProperties, prop.MaterialPsetProperties, prop.TaskPsetProperties, diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 72d03570ad..1758806560 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -122,85 +122,91 @@ class EnablePsetEditing(bpy.types.Operator): 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": - try: - data_type = ifcopenshell.util.attribute.get_primitive_type( - IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel") - ) - except: - # TODO: Occurs if the data type is something that exists in - # IFC4 and not in IFC2X3. To fully fix this we need to - # generate the IFC2X3 pset template definitions. - continue - elif prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]: - data_type = "float" - elif prop_template.TemplateType == "Q_COUNT": - data_type = "integer" + self.load_single_value(prop_template, data) elif prop_template.TemplateType == "P_ENUMERATEDVALUE": - enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues] - #selected_enum_items = [e.EnumerationValues.wrappedValue for e in Data.properties[e]] - data_type = "" - else: - continue # Other types not yet supported + self.load_enumerated_value(prop_template, data) - new = self.props.properties.add() - new.name = prop_template.Name - new.is_null = data.get(prop_template.Name, None) is None - new.is_optional = True - new.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference" - new.data_type = data_type - - if data_type == "string": - new.string_value = "" if new.is_null else data[prop_template.Name] - elif data_type == "integer": - new.int_value = 0 if new.is_null else data[prop_template.Name] - elif data_type == "float": - new.float_value = 0.0 if new.is_null else data[prop_template.Name] - elif data_type == "boolean": - new.bool_value = False if new.is_null else data[prop_template.Name] - if prop_template.TemplateType == "P_ENUMERATEDVALUE": + def load_single_value(self, prop_template, data): + try: + data_type = ifcopenshell.util.attribute.get_primitive_type( + IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel") + ) + except: + # TODO: Occurs if the data type is something that exists in + # IFC4 and not in IFC2X3. To fully fix this we need to + # generate the IFC2X3 pset template definitions. + return - new.set_value(prop_template.Enumerators.EnumerationValues[0].wrappedValue) - - for enum in enum_items: - new_enum = new.enumerated_values.add() - data_type = new.get_value_name() - setattr(new_enum, data_type, enum) - if data.get(prop_template.Name): - new_enum.is_selected = enum in data[prop_template.Name] + prop = self.props.properties.add() + 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.data_type = data_type + + if data_type == "string": + metadata.string_value = "" if metadata.is_null else data[prop_template.Name] + elif data_type == "integer": + metadata.int_value = 0 if metadata.is_null else data[prop_template.Name] + elif data_type == "float": + metadata.float_value = 0.0 if metadata.is_null else data[prop_template.Name] + elif data_type == "boolean": + metadata.bool_value = False if metadata.is_null else data[prop_template.Name] + + 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, []) + + prop = self.props.properties.add() + 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_data): for prop_id in pset_data["Properties"]: prop = Data.properties[prop_id] - + if prop["type"] == "IfcPropertyEnumeratedValue": - new = self.props.properties.add() - new.name = prop["Name"] - new.is_null = new.enumerated_values is None - new.is_optional = True - new.set_value(prop["EnumerationReference"].EnumerationValues[0].wrappedValue) - - enum_ref = [v.wrappedValue for v in prop["EnumerationReference"].EnumerationValues] - enum_vals = [v.wrappedValue for v in prop["EnumerationValues"]] - for enum in enum_ref: - new_enum = new.enumerated_values.add() - data_type = new.get_value_name() - setattr(new_enum, data_type, enum) - if enum in enum_vals: - new_enum.is_selected = True - else: - new_enum.is_selected = False - + simple_prop = self.props.properties.add() + 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() + + 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: value = prop["NominalValue"] - new = self.props.properties.add() - new.set_value(value) - new.name = prop["Name"] - new.is_null = value is None - new.is_optional = True - new.set_value(new.get_value_default() if new.is_null else value) - + prop = self.props.properties.add() + metadata = 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): @@ -236,11 +242,13 @@ class EditPset(bpy.types.Operator, Operator): else: data = Data.psets if pset_id in Data.psets else Data.qtos for prop in props.properties: - if len(prop.enumerated_values) != 0: - value_name = prop.get_value_name() - properties[prop.name] = [e[value_name] for e in prop.enumerated_values if e.is_selected] - else: - properties[prop.name] = prop.get_value() + if prop.value_type == "IfcPropertySingleValue": + properties[prop.metadata.name] = prop.metadata.get_value() + elif prop.value_type == "IfcPropertyEnumeratedValue": + value_name = prop.metadata.get_value_name() + properties[prop.metadata.name] = [ + e[value_name] for e in prop.enumerated_value.enumerated_values if e.is_selected + ] if pset_id in Data.psets: ifcopenshell.api.run( @@ -507,15 +515,15 @@ class BIM_OT_add_edit_custom_property(bpy.types.Operator): continue ifc_element = tool.Ifc.get().by_id(ifc_definition_id) - for prop in props: + for prop in props: value = getattr(prop, prop.get_value_name()) - primary_measure_type = prop.primary_measure_type - + primary_measure_type = prop.primary_measure_type + if prop.template_type == "IfcPropertyEnumeratedValue": value_ifc_entity = self.generate_enum_entity(prop) elif prop.template_type == "IfcPropertySingleValue": value_ifc_entity = getattr(self.file, f"create{primary_measure_type}")(value) - + new_pset = ifcopenshell.api.run("pset.add_pset", self.file, product=ifc_element, name=prop.pset_name) ifcopenshell.api.run( "pset.edit_pset", self.file, pset=new_pset, properties={prop.property_name: value_ifc_entity} @@ -523,22 +531,26 @@ class BIM_OT_add_edit_custom_property(bpy.types.Operator): Data.load(IfcStore.get_file(), ifc_definition_id) self.report({"INFO"}, "Finished applying changes") return {"FINISHED"} - + def generate_enum_entity(self, prop): prop_type = prop.get_value_name() prop_enum = self.file.create_entity( - "IFCPROPERTYENUMERATION", - Name=prop.property_name, - EnumerationValues=tuple(self.file.create_entity( - prop.primary_measure_type, ev[prop_type]) for ev in prop.enum_values) - ) + "IFCPROPERTYENUMERATION", + Name=prop.property_name, + EnumerationValues=tuple( + self.file.create_entity(prop.primary_measure_type, ev[prop_type]) for ev in prop.enum_values + ), + ) prop_enum_value = self.file.create_entity( - "IFCPROPERTYENUMERATEDVALUE", - Name=prop.property_name, - EnumerationValues=tuple(self.file.create_entity( - prop.primary_measure_type, ev[prop_type]) for ev in prop.enum_values if ev.is_selected == True), - EnumerationReference=prop_enum - ) + "IFCPROPERTYENUMERATEDVALUE", + Name=prop.property_name, + EnumerationValues=tuple( + self.file.create_entity(prop.primary_measure_type, ev[prop_type]) + for ev in prop.enum_values + if ev.is_selected == True + ), + EnumerationReference=prop_enum, + ) return prop_enum_value diff --git a/src/blenderbim/blenderbim/bim/module/pset/prop.py b/src/blenderbim/blenderbim/bim/module/pset/prop.py index 9510329013..7bf754e389 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset/prop.py @@ -139,26 +139,22 @@ def get_primary_measure_type(self, context): return AddEditCustomPropertiesData.data["primary_measure_type"] -class EnumerationValues(PropertyGroup): - string_value: StringProperty(name="Value") - bool_value: BoolProperty(name="Value") - int_value: IntProperty(name="Value") - float_value: FloatProperty(name="Value") - is_selected: BoolProperty(default=False) +class IfcPropertyEnumeratedValue(PropertyGroup): + enumerated_values: CollectionProperty(type=Attribute) -class IfcSimpleProperty(Attribute): - # bounded_values: - enumerated_values: CollectionProperty(type=EnumerationValues) - # list_values: - # reference_values: - # table_values: +class IfcProperty(PropertyGroup): + metadata: PointerProperty(type=Attribute) + value_type: EnumProperty( + items=[(v, v, v) for v in ("IfcPropertySingleValue", "IfcPropertyEnumeratedValue")], name="Value Type" + ) + enumerated_value: PointerProperty(type=IfcPropertyEnumeratedValue) class PsetProperties(PropertyGroup): active_pset_id: IntProperty(name="Active Pset ID") active_pset_name: StringProperty(name="Pset Name") - properties: CollectionProperty(name="Properties", type=IfcSimpleProperty) + properties: CollectionProperty(name="Properties", type=IfcProperty) pset_name: EnumProperty(items=get_pset_names, name="Pset Name") qto_name: EnumProperty(items=get_qto_names, name="Qto Name") @@ -225,7 +221,7 @@ class AddEditProperties(PropertyGroup): ], name="Template Type", ) - enum_values: CollectionProperty(name="Enum Values", type=EnumerationValues) + enum_values: CollectionProperty(name="Enum Values", type=Attribute) getter_enum = { "primary_measure_type": get_primary_measure_type, diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index ebc421420c..7f6f4d7cf0 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -19,7 +19,6 @@ from bpy.types import Panel from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ui import prop_with_search -from blenderbim.bim.helper import draw_attribute from blenderbim.bim.module.pset.data import ( ObjectPsetsData, ObjectQtosData, @@ -32,6 +31,45 @@ from blenderbim.bim.module.pset.data import ( ) +def draw_property(prop, layout, copy_operator=None): + if prop.value_type == "IfcPropertySingleValue": + draw_single_property(prop, layout, copy_operator) + elif prop.value_type == "IfcPropertyEnumeratedValue": + draw_enumerated_property(prop, layout, copy_operator) + + +def draw_single_property(prop, layout, copy_operator=None): + value_name = prop.metadata.get_value_name() + if not value_name: + layout.label(text=prop.metadata.name) + return + layout.prop( + prop.metadata, + value_name, + text=prop.metadata.name, + ) + if prop.metadata.is_optional: + layout.prop(prop.metadata, "is_null", icon="RADIOBUT_OFF" if prop.metadata.is_null else "RADIOBUT_ON", text="") + if copy_operator: + op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN") + op.name = prop.metadata.name + if prop.metadata.is_uri: + op = layout.operator("bim.select_uri_prop", text="", icon="FILE_FOLDER") + op.data_path = prop.metadata.path_from_id("string_value") + + +def draw_enumerated_property(prop, layout, copy_operator=None): + value_name = prop.metadata.get_value_name() + if not value_name: + layout.label(text=prop.metadata.name) + return + if len(prop.enumerated_value.enumerated_values) != 0: + layout.label(text=prop.metadata.name) + grid = layout.column_flow(columns=3) + for e in prop.enumerated_value.enumerated_values: + grid.prop(e, "is_selected", text=str(e[value_name])) + + def get_active_pset_obj_name(context, obj_type): if obj_type == "Object": return context.active_object.name @@ -96,7 +134,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type): def draw_psetqto_editable_ui(box, props, prop): row = box.row(align=True) - draw_attribute(prop, row, copy_operator="bim.copy_property_to_selection") + draw_property(prop, row, copy_operator="bim.copy_property_to_selection") if ( "length" in prop.name.lower() or "width" in prop.name.lower()