diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index f0dab98427..1934a088ab 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -33,16 +33,26 @@ def draw_attributes(props, layout, copy_operator=None): draw_attribute(attribute, row, copy_operator) -def draw_attribute(attribute, layout, copy_operator=None): +def draw_attribute(box, attribute, layout, copy_operator=None): value_name = attribute.get_value_name() if not value_name: layout.label(text=attribute.name) return - layout.prop( - attribute, - value_name, - text=attribute.name, - ) + if len(attribute.enumerated_values) != 0: + layout.label(text=attribute.name) + grid = layout.column_flow(columns=4) + 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, + ) 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 f86950c3b2..c2d845661d 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/pset/__init__.py @@ -35,6 +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.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 15cb559978..1829ebee78 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -133,23 +133,24 @@ class EnablePsetEditing(bpy.types.Operator): # IFC4 and not in IFC2X3. To fully fix this we need to # generate the IFC2X3 pset template definitions. continue - elif prop_template.TemplateType == "P_ENUMERATEDVALUE": - data_type = "enum" - enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues] 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" + 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 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.data_type = data_type + 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": @@ -158,22 +159,48 @@ class EnablePsetEditing(bpy.types.Operator): 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] - elif data_type == "enum": - new.enum_items = json.dumps(enum_items) - if data.get(prop_template.Name): - new.enum_value = str(data[prop_template.Name]) + if prop_template.TemplateType == "P_ENUMERATEDVALUE": + + 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] def load_from_pset_data(self, pset_data): for prop_id in pset_data["Properties"]: prop = Data.properties[prop_id] - - 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) + + 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 + + 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) + class DisablePsetEditing(bpy.types.Operator, Operator): @@ -209,7 +236,11 @@ class EditPset(bpy.types.Operator, Operator): else: data = Data.psets if pset_id in Data.psets else Data.qtos for prop in props.properties: - properties[prop.name] = prop.get_value() + 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 pset_id in Data.psets: ifcopenshell.api.run( @@ -381,9 +412,13 @@ class BIM_OT_add_property_to_edit(bpy.types.Operator): bl_idname = "bim.add_property_to_edit" bl_options = {"REGISTER", "UNDO"} option: bpy.props.StringProperty() + index: bpy.props.IntProperty() def execute(self, context): - getattr(context.scene, self.option).add() + if self.index == -1: + getattr(context.scene, self.option).add() + else: + getattr(context.scene, self.option)[self.index].enum_values.add() return {"FINISHED"} @@ -392,10 +427,14 @@ class BIM_OT_remove_property_to_edit(bpy.types.Operator): bl_idname = "bim.remove_property_to_edit" bl_options = {"REGISTER", "UNDO"} index: bpy.props.IntProperty() + index2: bpy.props.IntProperty(default=-1) option: bpy.props.StringProperty() def execute(self, context): - getattr(context.scene, self.option).remove(self.index) + if self.index2 == -1: + getattr(context.scene, self.option).remove(self.index) + else: + getattr(context.scene, self.option)[self.index].enum_values.remove(self.index2) return {"FINISHED"} @@ -468,18 +507,39 @@ 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 - value_ifc_entity = getattr(self.file, f"create{primary_measure_type}")(value) - + 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} ) - + 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) + ) + 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 + ) + return prop_enum_value class BIM_OT_bulk_remove_psets(bpy.types.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/pset/prop.py b/src/blenderbim/blenderbim/bim/module/pset/prop.py index 0ba32460b9..14fedf0f33 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset/prop.py @@ -139,10 +139,26 @@ 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 IfcSimpleProperty(Attribute): + #bounded_values: + enumerated_values: CollectionProperty(type=EnumerationValues) + #list_values: + #reference_values: + #table_values: + + class PsetProperties(PropertyGroup): active_pset_id: IntProperty(name="Active Pset ID") active_pset_name: StringProperty(name="Pset Name") - properties: CollectionProperty(name="Properties", type=Attribute) + properties: CollectionProperty(name="Properties", type=IfcSimpleProperty) pset_name: EnumProperty(items=getPsetNames, name="Pset Name") qto_name: EnumProperty(items=getQtoNames, name="Qto Name") @@ -197,6 +213,14 @@ class AddEditProperties(PropertyGroup): int_value: IntProperty(name="Value") float_value: FloatProperty(name="Value") primary_measure_type: EnumProperty(items=get_primary_measure_type, name="Primary Measure Type") + template_type: EnumProperty( + items=[ + ("IfcPropertySingleValue","IfcPropertySingleValue","IfcPropertySingleValue"), + ("IfcPropertyEnumeratedValue","IfcPropertyEnumeratedValue","IfcPropertyEnumeratedValue") + ], + name="Template Type" + ) + enum_values: CollectionProperty(name="Enum Values", type=EnumerationValues) def get_value_name(self): ifc_data_type = IfcStore.get_schema().declaration_by_name(self.primary_measure_type) diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 15d9937d24..25adeb2156 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -95,7 +95,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_attribute(box, prop, row, copy_operator="bim.copy_property_to_selection") if ( "length" in prop.name.lower() or "width" in prop.name.lower() @@ -441,17 +441,35 @@ class BIM_PT_add_edit_custom_properties(Panel): row = layout.row() op = row.operator("bim.add_property_to_edit", icon="ADD") op.option = "AddEditProperties" + op.index = -1 if props: for index, prop in enumerate(props): row = layout.row(align=True) row.prop(prop, "pset_name", text="") row.prop(prop, "property_name", text="") - row.prop(prop, prop.get_value_name(), text="") + if prop.template_type == "IfcPropertySingleValue": + row.prop(prop, prop.get_value_name(), text="") row.prop(prop, "primary_measure_type", text="") + row.prop(prop, "template_type", text="") op = row.operator("bim.remove_property_to_edit", icon="X", text="") op.index = index op.option = "AddEditProperties" + + if prop.template_type == "IfcPropertyEnumeratedValue": + op = row.operator("bim.add_property_to_edit", icon="ADD", text="Add Enum") + op.option = "AddEditProperties" + op.index = index + for index2, prop2 in enumerate(prop.enum_values): + row = layout.row() + row.separator() + row.separator() + row.prop(prop2, prop.get_value_name(), text=f"#{index2}") + row.prop(prop2, "is_selected") + op = row.operator("bim.remove_property_to_edit", icon="X", text="") + op.index = index + op.index2 = index2 + op.option = "AddEditProperties" if props: row = layout.row(align=True) @@ -476,7 +494,7 @@ class BIM_PT_delete_psets(Panel): row = layout.row() op = row.operator("bim.add_property_to_edit", icon="ADD") op.option = "DeletePsets" - + if props: for index, prop in enumerate(props): row = layout.row(align=True) diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/data.py b/src/ifcopenshell-python/ifcopenshell/api/pset/data.py index 76a2d610e2..1bb3a2d73b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/data.py @@ -94,13 +94,15 @@ class Data: elif hasattr(pset, "Properties"): props = pset.Properties or [] # TODO: support more than single values - data["Properties"] = [p.id() for p in props if p.is_a("IfcPropertySingleValue")] + data["Properties"] = [p.id() for p in props if p.is_a("IfcPropertyEnumeratedValue") or p.is_a("IfcPropertySingleValue")] cls.psets[pset.id()] = data cls.products[product_id]["psets"].add(pset.id()) for prop in props: # TODO: support more than single values if prop.is_a("IfcPropertySingleValue"): cls.load_prop(prop) + elif prop.is_a("IfcPropertyEnumeratedValue"): + cls.load_prop_enum(prop) @classmethod def load_prop(cls, prop): @@ -116,7 +118,14 @@ class Data: # For convenience, which trumps correctness in this case data["NominalValue"] = prop[3] cls.properties[prop.id()] = data - + + @classmethod + def load_prop_enum(cls, prop): + data = prop.get_info() + enumerations = [enum.wrappedValue for enum in data["EnumerationValues"]] + data["NominalValue"] = str(enumerations) + cls.properties[prop.id()] = data + @classmethod def add_qto(cls, qto, product_id): data = qto.get_info() diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py index 06a8aec31e..d6b234071d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . +from distutils.command.sdist import sdist import ifcopenshell import ifcopenshell.util.pset @@ -46,11 +47,40 @@ class Usecase: self.psetqto = ifcopenshell.util.pset.get_template("IFC4") self.pset_template = self.psetqto.get_by_name(self.settings["pset"].Name) + #TODO - Add support for changing property types? + # For example - IfcPropertyEnumeratedValue to + # IfcPropertySingleValue. Or maybe the user should + # just delete the property first? - vulevukusej def update_existing_properties(self): for prop in self.get_properties(): - self.update_existing_property(prop) + if prop.is_a("IfcPropertyEnumeratedValue"): + self.update_existing_enum(prop) + else: + self.update_existing_property(prop) + + def update_existing_enum(self, prop): + if prop.Name not in self.settings["properties"]: + return + value = self.settings["properties"][prop.Name] + if isinstance(value, list): + sel_vals = [] + for val in value: + primary_measure_type = prop.EnumerationReference.EnumerationValues[0].is_a() #Only need the first enum type since all enums are of the same type + ifc_val = self.file.create_entity(primary_measure_type, val) + sel_vals.append(ifc_val) + prop.EnumerationValues = tuple(sel_vals) - def update_existing_property(self, prop): + else: + if value.EnumerationReference.EnumerationValues == (): + prop.EnumerationReference.EnumerationValues = () + prop.EnumerationValues = () + + elif isinstance(value, ifcopenshell.entity_instance): + prop.EnumerationReference.EnumerationValues = value.EnumerationReference.EnumerationValues + prop.EnumerationValues = value.EnumerationValues + del self.settings["properties"][prop.Name] + + def update_existing_property(self, prop): if prop.Name not in self.settings["properties"]: return value = self.settings["properties"][prop.Name] @@ -73,16 +103,36 @@ class Usecase: continue if isinstance(value, ifcopenshell.entity_instance): nominal_value = value + if value.is_a(True) == "IFC4.IfcPropertyEnumeratedValue": + properties.append(value) + #TODO-The following "elif" is temporary code, will need to refactor at some point - vulevukusej + elif isinstance(value, list): + for pset_template in self.settings["pset_template"].HasPropertyTemplates: + if pset_template.Name == name: + prop_enum = self.file.create_entity( + "IFCPROPERTYENUMERATION", + Name=name, + EnumerationValues=pset_template.Enumerators.EnumerationValues + ) + prop_enum_value = self.file.create_entity( + "IFCPROPERTYENUMERATEDVALUE", + Name=name, + EnumerationValues=tuple(self.file.create_entity( + pset_template.PrimaryMeasureType, v) for v in value), + EnumerationReference=prop_enum + ) + properties.append(prop_enum_value) else: primary_measure_type = self.get_primary_measure_type(name, new_value=value) value = self.cast_value_to_primary_measure_type(value, primary_measure_type) nominal_value = self.file.create_entity(primary_measure_type, value) - properties.append( - self.file.create_entity( - "IfcPropertySingleValue", - **{"Name": name, "NominalValue": nominal_value}, + + properties.append( + self.file.create_entity( + "IfcPropertySingleValue", + **{"Name": name, "NominalValue": nominal_value}, + ) ) - ) return properties def extend_pset_with_new_properties(self, new_properties): diff --git a/src/ifcopenshell-python/ifcopenshell/mvd b/src/ifcopenshell-python/ifcopenshell/mvd index 83c12fa494..667894b629 160000 --- a/src/ifcopenshell-python/ifcopenshell/mvd +++ b/src/ifcopenshell-python/ifcopenshell/mvd @@ -1 +1 @@ -Subproject commit 83c12fa494b9d6a5a370d8525dc1f76aa5c89d8d +Subproject commit 667894b6295ea5ccc489d97ff2064bebcd67bda8 diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 7c75ec98d9..341db2d5ee 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -80,6 +80,13 @@ def get_properties(properties): for prop in properties or []: if prop.is_a("IfcPropertySingleValue"): results[prop.Name] = prop.NominalValue.wrappedValue if prop.NominalValue else None + for enum in prop.EnumerationValues: + values.append(enum.wrappedValue) + if values: + results[prop.Name] = str(values) if len(values)>1 else values[0] + else: + results[prop.Name] = None + elif prop.is_a("IfcComplexProperty"): data = {k: v for k, v in prop.get_info().items() if v is not None and k != "Name"} data["properties"] = get_properties(prop.HasProperties) diff --git a/src/svgfill b/src/svgfill index f9102b1e9f..01a2fb4943 160000 --- a/src/svgfill +++ b/src/svgfill @@ -1 +1 @@ -Subproject commit f9102b1e9f58e2c3bf9923c1eafa0373eeb872f5 +Subproject commit 01a2fb49434920eb5cce789d14fcb425b9fe82c2