diff --git a/src/blenderbim/blenderbim/bim/module/pset/__init__.py b/src/blenderbim/blenderbim/bim/module/pset/__init__.py index 168d453d7a..ce6343975f 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/pset/__init__.py @@ -28,7 +28,6 @@ classes = ( operator.EnablePsetEditing, operator.GuessQuantity, operator.CalculateQuantity, - operator.CalculateAllQuantities, operator.RemovePset, operator.TogglePsetExpansion, operator.BIM_OT_add_property_to_edit, diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 9f96f61177..a8e2715012 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -438,20 +438,6 @@ class GuessQuantity(bpy.types.Operator): return unit_settings.length_unit[0 : -len("METERS")], "METRE" -class CalculateAllQuantities(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.calculate_all_quantities" - bl_label = "Calculate All Quantities" - bl_options = {"REGISTER", "UNDO"} - bl_description = "Calculate all quantities for all selected objects" - - @classmethod - def poll(cls, context): - return context.active_object - - def _execute(self, context): - QtoCore.calculate_all_qtos(tool.Qto, selected_objects = [context.active_object]) - - class CopyPropertyToSelection(bpy.types.Operator, Operator): bl_idname = "bim.copy_property_to_selection" bl_label = "Copy Property To Selection" diff --git a/src/blenderbim/blenderbim/bim/module/qto/__init__.py b/src/blenderbim/blenderbim/bim/module/qto/__init__.py index 98ff7c2589..87256a846b 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/qto/__init__.py @@ -26,8 +26,8 @@ classes = ( operator.CalculateObjectVolumes, operator.ExecuteQtoMethod, operator.QuantifyObjects, - operator.AssignPsetQto, - operator.CalculateAllQtos, + operator.AssignBaseQto, + operator.CalculateAllQuantities, prop.BIMQtoProperties, ui.BIM_PT_qto_utilities, ) diff --git a/src/blenderbim/blenderbim/bim/module/qto/operator.py b/src/blenderbim/blenderbim/bim/module/qto/operator.py index 0d067c45de..9841aac858 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/operator.py +++ b/src/blenderbim/blenderbim/bim/module/qto/operator.py @@ -172,30 +172,30 @@ class QuantifyObjects(bpy.types.Operator): PsetData.load(self.file, obj.BIMObjectProperties.ifc_definition_id) return {"FINISHED"} -class AssignPsetQto(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.assign_pset_qto" +class AssignBaseQto(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.assign_objects_base_qto" bl_label = "Assign IFC Object Quantity Set" bl_options = {"REGISTER", "UNDO"} bl_description = "Assign IFC quantity set to selected object" @classmethod def poll(cls, context): - return context.active_object and context.selected_objects + return tool.Ifc.get() and context.selected_objects def _execute(self, context): - core.assign_pset_qto(tool.Qto, selected_objects = context.selected_objects) + core.assign_objects_base_qto(tool.Ifc, tool.Qto, selected_objects = context.selected_objects) return {"FINISHED"} -class CalculateAllQtos(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.calculate_all_qtos" +class CalculateAllQuantities(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.calculate_all_quantities" bl_label = "Calculate all quantities" bl_options = {"REGISTER", "UNDO"} bl_description = "Calculate all possible quantities and assign them to selected object" @classmethod def poll(cls, context): - return context.active_object and context.selected_objects + return tool.Ifc.get() and context.selected_objects def _execute(self, context): - core.calculate_all_qtos(tool.Qto, selected_objects = context.selected_objects) - return {"FINISHED"} + core.calculate_objects_base_quantities(tool.Ifc, tool.Qto, QtoCalculator(), selected_objects = context.selected_objects) + return {"FINISHED"} \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/module/qto/ui.py b/src/blenderbim/blenderbim/bim/module/qto/ui.py index f3629ae5b7..6386859d88 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/ui.py +++ b/src/blenderbim/blenderbim/bim/module/qto/ui.py @@ -53,7 +53,7 @@ class BIM_PT_qto_utilities(Panel): row.operator("bim.quantify_objects", icon="COPYDOWN", text="") row = layout.row(align=True) - row.operator("bim.assign_pset_qto") + row.operator("bim.assign_objects_base_qto") row = layout.row(align=True) - row.operator("bim.calculate_all_qtos") + row.operator("bim.calculate_all_quantities", icon="MOD_EDGESPLIT") diff --git a/src/blenderbim/blenderbim/core/qto.py b/src/blenderbim/blenderbim/core/qto.py index d363b9afa5..32dd20054a 100644 --- a/src/blenderbim/blenderbim/core/qto.py +++ b/src/blenderbim/blenderbim/core/qto.py @@ -22,30 +22,46 @@ def calculate_circle_radius(qto, obj=None): qto.set_qto_result(result) return result -def assign_pset_qto(qto, selected_objects): + +def assign_objects_base_qto(ifc, qto, selected_objects): for obj in selected_objects: - qto.assign_base_qto_to_object(obj) + assign_object_base_qto(ifc, qto, obj) -def calculate_all_qtos(qto, selected_objects): - for obj in selected_objects: - if not qto.get_qto_entity(obj): - print(f"There is no pset qto instance associated to object {obj.name}") - continue - applicable_quantity_names = qto.get_applicable_quantity_names(obj) +def assign_object_base_qto(ifc, qto, obj): + product = ifc.get_entity(obj) + if not product: + return + base_quantity_name = qto.get_applicable_base_quantity_name(product) + if not base_quantity_name: + return + ifc.run( + "pset.add_qto", + product=product, + name=base_quantity_name, + ) - calculated_quantities = qto.get_calculated_quantities(obj, applicable_quantity_names) - qto.edit_qto(obj, calculated_quantities) +def calculate_objects_base_quantities(ifc, qto, calculator, selected_objects): + if selected_objects: + for obj in selected_objects: + calculate_object_base_quantities(ifc, qto, calculator, obj) -def guess_all_qtos(qto, selected_objects): - for obj in selected_objects: - if not qto.get_qto_entity(obj): - print(f"There is no pset qto instance associated to object {obj.name}") - continue - applicable_quantity_names = qto.get_applicable_quantity_names(obj) - - calculated_quantities = qto.get_calculated_quantities(obj, applicable_quantity_names) - - qto.edit_qto(obj, calculated_quantities) +def calculate_object_base_quantities(ifc, qto, calculator, obj): + product = ifc.get_entity(obj) + if not product: + return + base_quantity_name = qto.get_applicable_base_quantity_name(product) + if not base_quantity_name: + print(f"There is no base quantity") + return + base_qto = qto.get_base_qto(product) + if not base_qto: + base_qto = ifc.run( + "pset.add_qto", + product=product, + name=base_quantity_name, + ) + calculated_quantities = qto.calculate_object_quantities(calculator, base_quantity_name, obj) + ifc.run("pset.edit_qto", qto=base_qto, properties=calculated_quantities) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 1dd5ef9391..9836b795fd 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -404,14 +404,16 @@ class Pset: class Qto: def get_radius_of_selected_vertices(cls, obj): pass def set_qto_result(cls, result): pass - def assign_base_qto_to_object(cls, object): pass - def get_applicable_quantity_names(cls, object): pass - def get_qto_applicable_name(cls, object): pass - def edit_qto(cls, object, calculated_quantities): pass - def get_qto_entity(cls, object): pass - def get_new_quantity(cls, object, quantity_name, alternative_prop_names): pass + def get_applicable_quantity_names(cls, qto_name): pass + def get_applicable_base_quantity_name(cls, object): pass def get_rounded_value(cls, new_quantity): pass - def get_calculated_quantities(cls, object, pset_qto_properties): pass + def calculate_object_quantities(cls, calculator, baste_qto, object): pass + def add_object_base_qto(cls, object): pass + def add_product_base_qto(cls, product): pass + def get_applicable_base_quantity_names(cls, product): pass + def get_new_calculated_quantity(cls, qto_name, quantity_name, object): pass + def get_new_guessed_quantity(cls, object, qto_name, quantity_name, ): pass + @interface class Resource: diff --git a/src/blenderbim/blenderbim/tool/qto.py b/src/blenderbim/blenderbim/tool/qto.py index 6044269fed..8d0242a920 100644 --- a/src/blenderbim/blenderbim/tool/qto.py +++ b/src/blenderbim/blenderbim/tool/qto.py @@ -25,6 +25,7 @@ from mathutils import Vector from ifcopenshell import util from blenderbim.bim.module.pset.qto_calculator import QtoCalculator from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper +import blenderbim.bim.schema class Qto(blenderbim.core.tool.Qto): @@ -42,87 +43,55 @@ class Qto(blenderbim.core.tool.Qto): bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3)) @classmethod - def assign_base_qto_to_object(cls, obj): - file = tool.Ifc.get() - entity = tool.Ifc.get_entity(obj) - pset_qto_name = cls.get_qto_applicable_name(obj) - ifcopenshell.api.run( - "pset.add_qto", - file, - **{ - "product": entity, - "name": pset_qto_name, - }, - ) + def add_object_base_qto(cls, obj): + product = tool.Ifc.get_entity(obj) + cls.add_product_base_qto(product) @classmethod - def get_applicable_quantity_names(cls, obj): - file = tool.Ifc.get() - schema = file.schema - pset_qto = util.pset.PsetQto(schema) - qto_name = cls.get_qto_applicable_name(obj) - properties_templates = pset_qto.get_by_name(qto_name).get_info()['HasPropertyTemplates'] - applicable_quantity_names = [a.get_info()['Name'] for a in properties_templates] - return applicable_quantity_names - - @classmethod - def get_qto_applicable_name(cls, obj): - entity = tool.Ifc.get_entity(obj) - ifc_class = entity.get_info()['type'] - qto_applicable_names = blenderbim.bim.schema.ifc.psetqto.get_applicable_names(ifc_class, qto_only=True) - qto_applicable_name = [q for q in qto_applicable_names if "Qto_" in q and "Base" in q][0] - return qto_applicable_name - - @classmethod - def edit_qto(cls, obj, calculated_quantities): - file = tool.Ifc.get() - pset_qto_applicable_name = cls.get_qto_applicable_name(obj) - qto_entity = cls.get_qto_entity(obj) - - ifcopenshell.api.run("pset.edit_qto", - file, - qto = qto_entity, properties = calculated_quantities, + def add_product_base_qto(cls, product): + base_quantity_name = cls.get_applicable_base_quantity_name(product) + if base_quantity_name: + return tool.Ifc.run( + "pset.add_qto", + product=product, + name=base_quantity_name, ) @classmethod - def get_qto_entity(cls, obj): - file = tool.Ifc.get() - entity = tool.Ifc.get_entity(obj) - qto_applicable_name = cls.get_qto_applicable_name(obj) - qto_entity = tool.Pset.get_element_pset(entity, qto_applicable_name) - return qto_entity + def get_applicable_quantity_names(cls, qto_name): + pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(qto_name) + return ( + [property.Name for property in pset_template.HasPropertyTemplates] + if hasattr(pset_template, "HasPropertyTemplates") + else [] + ) + + @classmethod + def get_applicable_base_quantity_name(cls, product=None): + if not product: + return + applicable_qto_names = blenderbim.bim.schema.ifc.psetqto.get_applicable_names(product.is_a(), qto_only=True) + return next((qto_name for qto_name in applicable_qto_names if "Qto_" in qto_name and "Base" in qto_name), None) @classmethod def get_new_calculated_quantity(cls, qto_name, quantity_name, obj): - calculator = QtoCalculator() - new_quantity = calculator.calculate_quantity(qto_name, quantity_name, obj) - return new_quantity + return QtoCalculator().calculate_quantity(qto_name, quantity_name, obj) @classmethod def get_new_guessed_quantity(cls, obj, quantity_name, alternative_prop_names): - calculator = QtoCalculator() - new_quantity = calculator.guess_quantity(quantity_name, alternative_prop_names, obj) + return QtoCalculator().guess_quantity(quantity_name, alternative_prop_names, obj) @classmethod def get_rounded_value(cls, new_quantity): return round(new_quantity, 3) @classmethod - def get_calculated_quantities(cls, obj, applicable_quantity_names): - calculated_quantities = {} - qto_name = cls.get_qto_applicable_name(obj) - calculator = QtoCalculator() - - for quantity_name in applicable_quantity_names: - if not cls.has_calculator(qto_name, quantity_name): - continue - else: - new_quantity = calculator.calculate_quantity(qto_name, quantity_name, obj) - new_quantity = cls.get_rounded_value(new_quantity) - - calculated_quantities[quantity_name] = new_quantity - - return calculated_quantities + def calculate_object_quantities(cls, calculator, qto_name, obj): + return { + quantity_name: cls.get_rounded_value(calculator.calculate_quantity(qto_name, quantity_name, obj)) + for quantity_name in cls.get_applicable_quantity_names(qto_name) or [] + if cls.has_calculator(qto_name, quantity_name) + } @classmethod def has_calculator(cls, qto_name, quantity_name): @@ -132,8 +101,8 @@ class Qto(blenderbim.core.tool.Qto): def get_guessed_quantities(cls, obj, pset_qto_properties): calculated_quantities = {} for pset_qto_property in pset_qto_properties: - quantity_name = pset_qto_property.get_info()['Name'] - alternative_prop_names = [p.get_info()['Name'] for p in pset_qto_properties] + quantity_name = pset_qto_property.get_info()["Name"] + alternative_prop_names = [p.get_info()["Name"] for p in pset_qto_properties] new_quantity = cls.get_new_guessed_quantity(obj, quantity_name, alternative_prop_names) @@ -146,3 +115,15 @@ class Qto(blenderbim.core.tool.Qto): return calculated_quantities + @classmethod + def get_base_qto(cls, product): + if not hasattr(product, "IsDefinedBy"): + return + for rel in product.IsDefinedBy or []: + if not ( + rel.is_a("IfcRelDefinesByProperties") + and "Base" in rel.RelatingPropertyDefinition.Name + and "Qto_" in rel.RelatingPropertyDefinition.Name + ): + continue + return rel.RelatingPropertyDefinition