From ab38e6bf7cacc34beac7f42dfd18f9a63491b422 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 10 Aug 2021 17:35:18 +1000 Subject: [PATCH] You can now add and edit monetary units, and basic editing of other unit types. --- .../blenderbim/bim/module/drawing/operator.py | 4 +- .../blenderbim/bim/module/root/prop.py | 15 +--- .../blenderbim/bim/module/unit/__init__.py | 4 + .../blenderbim/bim/module/unit/operator.py | 75 ++++++++++++++++++- .../blenderbim/bim/module/unit/prop.py | 23 ++++++ .../blenderbim/bim/module/unit/ui.py | 48 +++++++++--- src/ifc5d/ifc5d/csv2ifc.py | 4 +- .../ifcopenshell/api/cost/edit_cost_value.py | 2 +- .../api/unit/add_monetary_unit.py | 9 +++ .../ifcopenshell/api/unit/assign_unit.py | 53 ++++++++----- .../api/unit/edit_derived_unit.py | 10 +++ .../api/unit/edit_monetary_unit.py | 10 +++ .../ifcopenshell/api/unit/edit_named_unit.py | 10 +++ .../ifcopenshell/util/schema.py | 11 +++ 14 files changed, 230 insertions(+), 48 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/unit/add_monetary_unit.py create mode 100644 src/ifcopenshell-python/ifcopenshell/api/unit/edit_derived_unit.py create mode 100644 src/ifcopenshell-python/ifcopenshell/api/unit/edit_monetary_unit.py create mode 100644 src/ifcopenshell-python/ifcopenshell/api/unit/edit_named_unit.py diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index ee6ef0303d..54181dff45 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -236,7 +236,7 @@ class CreateDrawing(bpy.types.Operator): return svg_path # This is a work in progress. See #1153 and #1564. # Switch from old to new if you are testing v0.7.0 - self.generate_linework_old(svg_path) + self.generate_linework_old(context, svg_path) # self.generate_linework_new(svg_path) return svg_path @@ -266,7 +266,7 @@ class CreateDrawing(bpy.types.Operator): with open(svg_path, "w") as svg: svg.write(buffer.get_value()) - def generate_linework_old(self, svg_path): + def generate_linework_old(self, context, svg_path): ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert") subprocess.run( [ diff --git a/src/blenderbim/blenderbim/bim/module/root/prop.py b/src/blenderbim/blenderbim/bim/module/root/prop.py index 80ba2a20e4..b79a2a94ea 100644 --- a/src/blenderbim/blenderbim/bim/module/root/prop.py +++ b/src/blenderbim/blenderbim/bim/module/root/prop.py @@ -1,4 +1,6 @@ import bpy +import ifcopenshell +import ifcopenshell.util.schema from blenderbim.bim.ifc import IfcStore from bpy.types import PropertyGroup from bpy.props import ( @@ -82,17 +84,8 @@ def getIfcClasses(self, context): file = IfcStore.get_file() if len(classes_enum) < 1 and file: declaration = IfcStore.get_schema().declaration_by_name(context.scene.BIMRootProperties.ifc_product) - - def get_classes(declaration): - results = [] - if not declaration.is_abstract(): - results.append(declaration.name()) - for subtype in declaration.subtypes(): - results.extend(get_classes(subtype)) - return results - - classes = get_classes(declaration) - classes_enum.extend([(c, c, "") for c in sorted(classes)]) + declarations = ifcopenshell.util.schema.get_subtypes(declaration) + classes_enum.extend([(c, c, "") for c in sorted([d.name() for d in declarations])]) return classes_enum diff --git a/src/blenderbim/blenderbim/bim/module/unit/__init__.py b/src/blenderbim/blenderbim/bim/module/unit/__init__.py index 03a4a4e873..f4a0f265ec 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/unit/__init__.py @@ -6,6 +6,10 @@ classes = ( operator.LoadUnits, operator.DisableUnitEditingUI, operator.RemoveUnit, + operator.AddMonetaryUnit, + operator.EnableEditingUnit, + operator.DisableEditingUnit, + operator.EditUnit, prop.Unit, prop.BIMUnitProperties, ui.BIM_PT_units, diff --git a/src/blenderbim/blenderbim/bim/module/unit/operator.py b/src/blenderbim/blenderbim/bim/module/unit/operator.py index baae5ffab9..78c738e2b9 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/operator.py +++ b/src/blenderbim/blenderbim/bim/module/unit/operator.py @@ -1,5 +1,6 @@ import bpy import ifcopenshell.api +import blenderbim.bim.helper from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.unit.data import Data @@ -78,6 +79,8 @@ class LoadUnits(bpy.types.Operator): unit_type = unit.get("UserDefinedType", None) if not unit_type: unit_type = unit.get("UnitType", None) + if unit["type"] == "IfcMonetaryUnit": + unit_type = "CURRENCY" new = props.units.add() new.ifc_definition_id = ifc_definition_id @@ -86,7 +89,7 @@ class LoadUnits(bpy.types.Operator): new.icon = icon props.is_editing = True - # bpy.ops.bim.disable_editing_unit() + bpy.ops.bim.disable_editing_unit() return {"FINISHED"} @@ -116,3 +119,73 @@ class RemoveUnit(bpy.types.Operator): Data.load(self.file) bpy.ops.bim.load_units() return {"FINISHED"} + + +class AddMonetaryUnit(bpy.types.Operator): + bl_idname = "bim.add_monetary_unit" + bl_label = "Add Monetary Unit" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + props = context.scene.BIMUnitProperties + self.file = IfcStore.get_file() + unit = ifcopenshell.api.run("unit.add_monetary_unit", self.file) + ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit]) + Data.load(self.file) + bpy.ops.bim.load_units() + return {"FINISHED"} + + +class EnableEditingUnit(bpy.types.Operator): + bl_idname = "bim.enable_editing_unit" + bl_label = "Enable Editing Unit" + bl_options = {"REGISTER", "UNDO"} + unit: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMUnitProperties + while len(props.unit_attributes) > 0: + props.unit_attributes.remove(0) + + data = Data.units[self.unit] + + blenderbim.bim.helper.import_attributes(data["type"], props.unit_attributes, data) + props.active_unit_id = self.unit + return {"FINISHED"} + + +class DisableEditingUnit(bpy.types.Operator): + bl_idname = "bim.disable_editing_unit" + bl_label = "Disable Editing Unit" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + context.scene.BIMUnitProperties.active_unit_id = 0 + return {"FINISHED"} + + +class EditUnit(bpy.types.Operator): + bl_idname = "bim.edit_unit" + bl_label = "Edit Unit" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + props = context.scene.BIMUnitProperties + attributes = blenderbim.bim.helper.export_attributes(props.unit_attributes) + self.file = IfcStore.get_file() + unit = self.file.by_id(props.active_unit_id) + if unit.is_a("IfcMonetaryUnit"): + ifcopenshell.api.run("unit.edit_monetary_unit", self.file, **{"unit": unit, "attributes": attributes}) + elif unit.is_a("IfcDerivedUnit"): + ifcopenshell.api.run("unit.edit_derived_unit", self.file, **{"unit": unit, "attributes": attributes}) + elif unit.is_a("IfcNamedUnit"): + ifcopenshell.api.run("unit.edit_named_unit", self.file, **{"unit": unit, "attributes": attributes}) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_units() + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/unit/prop.py b/src/blenderbim/blenderbim/bim/module/unit/prop.py index df960f47aa..c43f2ec833 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/prop.py +++ b/src/blenderbim/blenderbim/bim/module/unit/prop.py @@ -1,4 +1,7 @@ import bpy +import ifcopenshell +import ifcopenshell.util.schema +from blenderbim.bim.ifc import IfcStore from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup from bpy.props import ( @@ -13,6 +16,23 @@ from bpy.props import ( ) +unitclasses_enum = [] + + +def purge(): + global unitclasses_enum + unitclasses_enum = [] + + +def getUnitClasses(self, context): + global unitclasses_enum + if not len(unitclasses_enum) and IfcStore.get_file(): + declarations = ifcopenshell.util.schema.get_subtypes(IfcStore.get_schema().declaration_by_name("IfcNamedUnit")) + unitclasses_enum.extend([(c, c, "") for c in sorted([d.name() for d in declarations])]) + unitclasses_enum.extend([("IfcDerivedUnit", "IfcDerivedUnit", ""), ("IfcMonetaryUnit", "IfcMonetaryUnit", "")]) + return unitclasses_enum + + class Unit(PropertyGroup): name: StringProperty(name="Name") unit_type: StringProperty(name="Unit Type") @@ -24,3 +44,6 @@ class BIMUnitProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing") units: CollectionProperty(name="Units", type=Unit) active_unit_index: IntProperty(name="Active Unit Index") + active_unit_id: IntProperty(name="Active Unit Id") + unit_classes: EnumProperty(items=getUnitClasses, name="Unit Classes") + unit_attributes: CollectionProperty(name="Unit Attributes", type=Attribute) diff --git a/src/blenderbim/blenderbim/bim/module/unit/ui.py b/src/blenderbim/blenderbim/bim/module/unit/ui.py index 8e8ec3a3da..c61902b0d0 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/ui.py +++ b/src/blenderbim/blenderbim/bim/module/unit/ui.py @@ -26,20 +26,37 @@ class BIM_PT_units(Panel): row = self.layout.row(align=True) row.label(text="{} Units Found".format(len(Data.unit_assignment)), icon="SNAP_GRID") if self.props.is_editing: - # row.operator("bim.add_unit", text="", icon="ADD") row.operator("bim.disable_unit_editing_ui", text="", icon="CANCEL") else: row.operator("bim.load_units", text="", icon="GREASEPENCIL") - if self.props.is_editing: - self.layout.template_list( - "BIM_UL_units", - "", - self.props, - "units", - self.props, - "active_unit_index", - ) + if not self.props.is_editing: + return + + row = self.layout.row(align=True) + row.prop(self.props, "unit_classes", text="") + + if self.props.unit_classes == "IfcMonetaryUnit": + row.operator("bim.add_monetary_unit", text="", icon="ADD") + elif self.props.unit_classes == "IfcDerivedUnit": + pass # TODO + else: + pass # TODO + + self.layout.template_list( + "BIM_UL_units", + "", + self.props, + "units", + self.props, + "active_unit_index", + ) + + if self.props.active_unit_id: + self.draw_editable_ui(context) + + def draw_editable_ui(self, context): + blenderbim.bim.helper.draw_attributes(self.props.unit_attributes, self.layout) class BIM_UL_units(UIList): @@ -49,4 +66,13 @@ class BIM_UL_units(UIList): row = layout.row(align=True) row.label(text=item.unit_type or "No Type", icon=item.icon) row.label(text=item.name or "Unnamed") - row.operator("bim.remove_unit", text="", icon="X").unit = item.ifc_definition_id + + if props.active_unit_id == item.ifc_definition_id: + row.operator("bim.edit_unit", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_unit", text="", icon="CANCEL") + elif props.active_unit_id: + row.operator("bim.remove_unit", text="", icon="X").unit = item.ifc_definition_id + else: + op = row.operator("bim.enable_editing_unit", text="", icon="GREASEPENCIL") + op.unit = item.ifc_definition_id + row.operator("bim.remove_unit", text="", icon="X").unit = item.ifc_definition_id diff --git a/src/ifc5d/ifc5d/csv2ifc.py b/src/ifc5d/ifc5d/csv2ifc.py index 8e8b704d6e..0ecb5f5d54 100644 --- a/src/ifc5d/ifc5d/csv2ifc.py +++ b/src/ifc5d/ifc5d/csv2ifc.py @@ -106,11 +106,11 @@ class Csv2Ifc: elif self.has_categories: for category, value in cost_item["CostValues"].items(): cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) - cost_value.AppliedValue = self.file.createIfcReal(value) + cost_value.AppliedValue = self.file.createIfcMonetaryMeasure(value) cost_value.Category = category else: cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) - cost_value.AppliedValue = self.file.createIfcReal(cost_item["CostValues"]) + cost_value.AppliedValue = self.file.createIfcMonetaryMeasure(cost_item["CostValues"]) if cost_item["CostQuantities"]: quantity_class = ifcopenshell.util.unit.get_symbol_quantity_class(cost_item["CostQuantitiesUnit"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py index b80a9ff01e..219dc00e0b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py @@ -9,5 +9,5 @@ class Usecase: for name, value in self.settings["attributes"].items(): if name == "AppliedValue" and value is not None: # TODO: support all applied value select types - value = self.file.createIfcReal(value) + value = self.file.createIfcMonetaryMeasure(value) setattr(self.settings["cost_value"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/add_monetary_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/add_monetary_unit.py new file mode 100644 index 0000000000..6091496dd6 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/add_monetary_unit.py @@ -0,0 +1,9 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"currency": "DOLLARYDOO"} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + return self.file.create_entity("IfcMonetaryUnit", self.settings["currency"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/assign_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/assign_unit.py index bedebcb3eb..8ae42c7f53 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/unit/assign_unit.py +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/assign_unit.py @@ -1,44 +1,55 @@ +import ifcopenshell import ifcopenshell.util.unit -class Usecase(): +class Usecase: def __init__(self, file, **settings): self.file = file self.settings = { - "length": { - "is_metric": True, - "raw": "MILLIMETERS" - }, - "area": { - "is_metric": True, - "raw": "METERS" - }, - "volume": { - "is_metric": True, - "raw": "METERS" - }, + "units": None, + "length": {"is_metric": True, "raw": "MILLIMETERS"}, + "area": {"is_metric": True, "raw": "METERS"}, + "volume": {"is_metric": True, "raw": "METERS"}, } for key, value in settings.items(): self.settings[key] = value def execute(self): - for unit_type, data in self.settings.items(): - if data["is_metric"]: - data["ifc"] = self.create_metric_unit(unit_type, data) - else: - data["ifc"] = self.create_imperial_unit(unit_type, data) + # We're going to refactor this to split unit creation and assignment + if self.settings["units"]: + units = self.settings["units"] + else: + del self.settings["units"] # TODO refactor + units = [] + for unit_type, data in self.settings.items(): + if data["is_metric"]: + units.append(self.create_metric_unit(unit_type, data)) + else: + units.append(self.create_imperial_unit(unit_type, data)) + + unit_assignment = self.get_unit_assignment() + self.assign_units(unit_assignment, units) + return unit_assignment + + def get_unit_assignment(self): unit_assignment = self.file.by_type("IfcUnitAssignment") if unit_assignment: unit_assignment = unit_assignment[0] # TODO: handle unit rewriting, which is complicated else: - unit_assignment = self.file.createIfcUnitAssignment([u["ifc"] for u in self.settings.values()]) + unit_assignment = self.file.createIfcUnitAssignment() if self.file.schema == "IFC2X3": self.file.by_type("IfcProject")[0].UnitsInContext = unit_assignment else: self.file.by_type("IfcContext")[0].UnitsInContext = unit_assignment return unit_assignment + def assign_units(self, unit_assignment, new_units): + units = set(unit_assignment.Units or []) + for unit in new_units: + units.add(unit) + unit_assignment.Units = list(units) + def create_metric_unit(self, unit_type, data): type_prefix = "" if unit_type == "area": @@ -72,7 +83,9 @@ class Usecase(): name = "{}inch".format(name_prefix + " " if name_prefix else "") elif data["raw"] == "FEET": name = "{}foot".format(name_prefix + " " if name_prefix else "") - value_component = self.file.create_entity("IfcReal", **{"wrappedValue": ifcopenshell.util.unit.si_conversions[name]}) + value_component = self.file.create_entity( + "IfcReal", **{"wrappedValue": ifcopenshell.util.unit.si_conversions[name]} + ) conversion_factor = self.file.createIfcMeasureWithUnit(value_component, si_unit) return self.file.createIfcConversionBasedUnit( dimensional_exponents, "{}UNIT".format(unit_type.upper()), name, conversion_factor diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/edit_derived_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/edit_derived_unit.py new file mode 100644 index 0000000000..65f8cc871e --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/edit_derived_unit.py @@ -0,0 +1,10 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"unit": 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["unit"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/edit_monetary_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/edit_monetary_unit.py new file mode 100644 index 0000000000..65f8cc871e --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/edit_monetary_unit.py @@ -0,0 +1,10 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"unit": 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["unit"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/edit_named_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/edit_named_unit.py new file mode 100644 index 0000000000..65f8cc871e --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/edit_named_unit.py @@ -0,0 +1,10 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"unit": 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["unit"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema.py b/src/ifcopenshell-python/ifcopenshell/util/schema.py index 407e912ba1..3d8c86c64e 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/schema.py +++ b/src/ifcopenshell-python/ifcopenshell/util/schema.py @@ -18,6 +18,17 @@ def is_a(entity, ifc_class): return False +def get_subtypes(entity): + def get_classes(declaration): + results = [] + if not declaration.is_abstract(): + results.append(declaration) + for subtype in declaration.subtypes(): + results.extend(get_classes(subtype)) + return results + return get_classes(entity) + + def reassign_class(ifc_file, element, new_class): try: new_element = ifc_file.create_entity(new_class)