diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index e445aa67fb..3b255f76e7 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -18,7 +18,7 @@ def import_attributes(ifc_class, props, data, callback=None): new.is_null = data[attribute.name()] is None new.is_optional = attribute.optional() new.data_type = data_type if isinstance(data_type, str) else "" - is_handled_by_callback = callback(attribute.name(), new, data) if callback else False + is_handled_by_callback = callback(attribute.name(), new, data) if callback else None if is_handled_by_callback: pass # Our job is done elif is_handled_by_callback is False: diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index 242c41809b..2368b1b783 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -24,8 +24,8 @@ classes = ( operator.ExpandCostItem, operator.ContractCostItem, operator.RemoveCostItem, - operator.AssignControl, - operator.UnassignControl, + operator.AssignCostItemProduct, + operator.UnassignCostItemProduct, operator.AddCostItemQuantity, operator.RemoveCostItemQuantity, operator.AddCostValue, diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 134fdb757a..9348d4d90e 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -3,6 +3,7 @@ import bpy import json import ifcopenshell.api import blenderbim.bim.helper +from blenderbim.bim.module.cost.prop import purge from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.cost.data import Data @@ -259,8 +260,8 @@ class EditCostItem(bpy.types.Operator): return {"FINISHED"} -class AssignControl(bpy.types.Operator): - bl_idname = "bim.assign_control" +class AssignCostItemProduct(bpy.types.Operator): + bl_idname = "bim.assign_cost_item_product" bl_label = "Assign Control" cost_item: bpy.props.IntProperty() related_object: bpy.props.StringProperty() @@ -269,20 +270,23 @@ class AssignControl(bpy.types.Operator): related_objects = ( [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects ) - for related_object in related_objects: - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "control.assign_control", - self.file, - related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id), - relating_control=self.file.by_id(self.cost_item), - ) + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "cost.assign_cost_item_product", + self.file, + cost_item=self.file.by_id(self.cost_item), + products=[ + self.file.by_id(o.BIMObjectProperties.ifc_definition_id) + for o in related_objects + if o.BIMObjectProperties.ifc_definition_id + ], + ) Data.load(self.file) return {"FINISHED"} -class UnassignControl(bpy.types.Operator): - bl_idname = "bim.unassign_control" +class UnassignCostItemProduct(bpy.types.Operator): + bl_idname = "bim.unassign_cost_item_product" bl_label = "Unassign Control" cost_item: bpy.props.IntProperty() related_object: bpy.props.StringProperty() @@ -291,14 +295,17 @@ class UnassignControl(bpy.types.Operator): related_objects = ( [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects ) - for related_object in related_objects: - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "control.unassign_control", - self.file, - related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id), - relating_control=self.file.by_id(self.cost_item), - ) + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "cost.unassign_cost_item_product", + self.file, + cost_item=self.file.by_id(self.cost_item), + products=[ + self.file.by_id(o.BIMObjectProperties.ifc_definition_id) + for o in related_objects + if o.BIMObjectProperties.ifc_definition_id + ], + ) Data.load(self.file) return {"FINISHED"} @@ -312,6 +319,7 @@ class EnableEditingCostItemQuantities(bpy.types.Operator): props = context.scene.BIMCostProperties props.active_cost_item_id = self.cost_item props.cost_item_editing_type = "QUANTITIES" + purge() return {"FINISHED"} @@ -348,8 +356,7 @@ class AddCostItemQuantity(bpy.types.Operator): "cost.assign_cost_item_product_quantities", self.file, cost_item=self.file.by_id(self.cost_item), - qto_name=self.props.qto_name, - prop_name=self.props.prop_name + prop_name=self.props.quantity_names, ) def add_manual_quantity(self): @@ -439,9 +446,7 @@ class AddCostValue(bpy.types.Operator): elif self.cost_type == "CATEGORY": category = self.cost_category value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=self.file.by_id(self.parent)) - ifcopenshell.api.run( - "cost.edit_cost_value", self.file, cost_value=value, attributes={"Category": category} - ) + ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes={"Category": category}) Data.load(self.file) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index 3ee77dd403..16c48c736c 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -2,6 +2,7 @@ import bpy import ifcopenshell.api from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.cost.data import Data +from ifcopenshell.api.pset.data import Data as PsetData from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup from bpy.props import ( @@ -17,17 +18,19 @@ from bpy.props import ( quantitytypes_enum = [] +quantitynames_enum = [] def purge(): global quantitytypes_enum + global quantitynames_enum quantitytypes_enum = [] + quantitynames_enum = [] def getQuantityTypes(self, context): global quantitytypes_enum if len(quantitytypes_enum) == 0 and IfcStore.get_schema(): - quantitytypes_enum.clear() quantitytypes_enum = [("QTO", "Qto", "Derive quantities from IFC quantity sets")] quantitytypes_enum.extend( [ @@ -38,6 +41,21 @@ def getQuantityTypes(self, context): return quantitytypes_enum +def getQuantityNames(self, context): + global quantitynames_enum + ifc_file = IfcStore.get_file() + if len(quantitynames_enum) == 0 and ifc_file: + names = set() + for element_id in Data.cost_items[self.active_cost_item_id]["Controls"]: + if element_id not in PsetData.products: + PsetData.load(IfcStore.get_file(), element_id) + for qto_id in PsetData.products[element_id]["qtos"]: + qto = PsetData.qtos[qto_id] + [names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]] + quantitynames_enum.extend([(n, n, "") for n in names]) + return quantitynames_enum + + def updateCostItemName(self, context): if self.name == "Unnamed": return @@ -73,8 +91,7 @@ class BIMCostProperties(PropertyGroup): cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute) contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]") quantity_types: EnumProperty(items=getQuantityTypes, name="Quantity Types") - qto_name: StringProperty(name="Qto Name") - prop_name: StringProperty(name="Prop Name") + quantity_names: EnumProperty(items=getQuantityNames, name="Quantity Names") active_cost_item_quantity_id: IntProperty(name="Active Cost Item Quantity Id") quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute) cost_types: EnumProperty( diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 30ecd8f07f..e02226edfa 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -97,8 +97,7 @@ class BIM_PT_cost_schedules(Panel): row = self.layout.row(align=True) row.prop(self.props, "quantity_types", text="") if self.props.quantity_types == "QTO": - row.prop(self.props, "qto_name", text="") - row.prop(self.props, "prop_name", text="") + row.prop(self.props, "quantity_names", text="") op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD") op.cost_item = self.props.active_cost_item_id op.ifc_class = self.props.quantity_types @@ -254,10 +253,10 @@ class BIM_UL_cost_items(UIList): oprops = context.active_object.BIMObjectProperties row = layout.row(align=True) if oprops.ifc_definition_id in cost_item["Controls"]: - op = row.operator("bim.unassign_control", text="", icon="KEYFRAME_HLT", emboss=False) + op = row.operator("bim.unassign_cost_item_product", text="", icon="KEYFRAME_HLT", emboss=False) op.cost_item = item.ifc_definition_id else: - op = row.operator("bim.assign_control", text="", icon="KEYFRAME", emboss=False) + op = row.operator("bim.assign_cost_item_product", text="", icon="KEYFRAME", emboss=False) op.cost_item = item.ifc_definition_id if props.active_cost_item_id == item.ifc_definition_id: diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_product.py b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_product.py new file mode 100644 index 0000000000..df0944a876 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_product.py @@ -0,0 +1,31 @@ +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"cost_item": None, "products": []} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + quantity_names = set() + for quantity in self.settings["cost_item"].CostQuantities or []: + if quantity.Name: + quantity_names.add(quantity.Name) + + for product in self.settings["products"]: + ifcopenshell.api.run( + "control.assign_control", + self.file, + related_object=product, + relating_control=self.settings["cost_item"], + ) + + for name in quantity_names: + ifcopenshell.api.run( + "cost.assign_cost_item_product_quantities", + self.file, + cost_item=self.settings["cost_item"], + prop_name=name + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_product_quantities.py b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_product_quantities.py index c20a2fa861..eea39bf633 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_product_quantities.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_product_quantities.py @@ -4,7 +4,7 @@ import ifcopenshell.api class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"cost_item": None, "qto_name": "", "prop_name": ""} + self.settings = {"cost_item": None, "prop_name": ""} for key, value in settings.items(): self.settings[key] = value @@ -25,7 +25,7 @@ class Usecase: self.add_quantity_from_qto(relationship.RelatingPropertyDefinition) def add_quantity_from_qto(self, qto): - if not qto.is_a("IfcElementQuantity") or qto.Name.lower() != self.settings["qto_name"].lower(): + if not qto.is_a("IfcElementQuantity"): return for prop in qto.Quantities: if prop.is_a("IfcPhysicalSimpleQuantity") and prop.Name.lower() == self.settings["prop_name"].lower(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_product.py b/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_product.py new file mode 100644 index 0000000000..ec652a0e10 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_product.py @@ -0,0 +1,29 @@ +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"cost_item": None, "products": []} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.quantities = set(self.settings["cost_item"].CostQuantities or []) + for quantity in self.settings["cost_item"].CostQuantities or []: + for inverse in self.file.get_inverse(quantity): + if not inverse.is_a("IfcElementQuantity"): + continue + for rel in inverse.DefinesOccurrence or []: + for related_object in rel.RelatedObjects: + if related_object in self.settings["products"]: + self.quantities.remove(quantity) + self.settings["cost_item"].CostQuantities = list(self.quantities) + + for product in self.settings["products"]: + ifcopenshell.api.run( + "control.unassign_control", + self.file, + related_object=product, + relating_control=self.settings["cost_item"], + )