mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 10:11:46 +00:00
update qto core and qto tools:
- when calculating quantities of an object, assign base qto to object if none exists. - optimise core and tool functions so it's faster for many selected objects
This commit is contained in:
@@ -28,7 +28,6 @@ classes = (
|
|||||||
operator.EnablePsetEditing,
|
operator.EnablePsetEditing,
|
||||||
operator.GuessQuantity,
|
operator.GuessQuantity,
|
||||||
operator.CalculateQuantity,
|
operator.CalculateQuantity,
|
||||||
operator.CalculateAllQuantities,
|
|
||||||
operator.RemovePset,
|
operator.RemovePset,
|
||||||
operator.TogglePsetExpansion,
|
operator.TogglePsetExpansion,
|
||||||
operator.BIM_OT_add_property_to_edit,
|
operator.BIM_OT_add_property_to_edit,
|
||||||
|
|||||||
@@ -438,20 +438,6 @@ class GuessQuantity(bpy.types.Operator):
|
|||||||
return unit_settings.length_unit[0 : -len("METERS")], "METRE"
|
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):
|
class CopyPropertyToSelection(bpy.types.Operator, Operator):
|
||||||
bl_idname = "bim.copy_property_to_selection"
|
bl_idname = "bim.copy_property_to_selection"
|
||||||
bl_label = "Copy Property To Selection"
|
bl_label = "Copy Property To Selection"
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ classes = (
|
|||||||
operator.CalculateObjectVolumes,
|
operator.CalculateObjectVolumes,
|
||||||
operator.ExecuteQtoMethod,
|
operator.ExecuteQtoMethod,
|
||||||
operator.QuantifyObjects,
|
operator.QuantifyObjects,
|
||||||
operator.AssignPsetQto,
|
operator.AssignBaseQto,
|
||||||
operator.CalculateAllQtos,
|
operator.CalculateAllQuantities,
|
||||||
prop.BIMQtoProperties,
|
prop.BIMQtoProperties,
|
||||||
ui.BIM_PT_qto_utilities,
|
ui.BIM_PT_qto_utilities,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -172,30 +172,30 @@ class QuantifyObjects(bpy.types.Operator):
|
|||||||
PsetData.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
|
PsetData.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
class AssignPsetQto(bpy.types.Operator, tool.Ifc.Operator):
|
class AssignBaseQto(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.assign_pset_qto"
|
bl_idname = "bim.assign_objects_base_qto"
|
||||||
bl_label = "Assign IFC Object Quantity Set"
|
bl_label = "Assign IFC Object Quantity Set"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Assign IFC quantity set to selected object"
|
bl_description = "Assign IFC quantity set to selected object"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.active_object and context.selected_objects
|
return tool.Ifc.get() and context.selected_objects
|
||||||
|
|
||||||
def _execute(self, context):
|
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"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
class CalculateAllQtos(bpy.types.Operator, tool.Ifc.Operator):
|
class CalculateAllQuantities(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.calculate_all_qtos"
|
bl_idname = "bim.calculate_all_quantities"
|
||||||
bl_label = "Calculate all quantities"
|
bl_label = "Calculate all quantities"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Calculate all possible quantities and assign them to selected object"
|
bl_description = "Calculate all possible quantities and assign them to selected object"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.active_object and context.selected_objects
|
return tool.Ifc.get() and context.selected_objects
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
core.calculate_all_qtos(tool.Qto, selected_objects = context.selected_objects)
|
core.calculate_objects_base_quantities(tool.Ifc, tool.Qto, QtoCalculator(), selected_objects = context.selected_objects)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -53,7 +53,7 @@ class BIM_PT_qto_utilities(Panel):
|
|||||||
row.operator("bim.quantify_objects", icon="COPYDOWN", text="")
|
row.operator("bim.quantify_objects", icon="COPYDOWN", text="")
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator("bim.assign_pset_qto")
|
row.operator("bim.assign_objects_base_qto")
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator("bim.calculate_all_qtos")
|
row.operator("bim.calculate_all_quantities", icon="MOD_EDGESPLIT")
|
||||||
|
|||||||
@@ -22,30 +22,46 @@ def calculate_circle_radius(qto, obj=None):
|
|||||||
qto.set_qto_result(result)
|
qto.set_qto_result(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def assign_pset_qto(qto, selected_objects):
|
|
||||||
|
def assign_objects_base_qto(ifc, qto, selected_objects):
|
||||||
for obj in 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)
|
def calculate_object_base_quantities(ifc, qto, calculator, obj):
|
||||||
|
product = ifc.get_entity(obj)
|
||||||
calculated_quantities = qto.get_calculated_quantities(obj, applicable_quantity_names)
|
if not product:
|
||||||
|
return
|
||||||
qto.edit_qto(obj, calculated_quantities)
|
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)
|
||||||
|
|||||||
@@ -404,14 +404,16 @@ class Pset:
|
|||||||
class Qto:
|
class Qto:
|
||||||
def get_radius_of_selected_vertices(cls, obj): pass
|
def get_radius_of_selected_vertices(cls, obj): pass
|
||||||
def set_qto_result(cls, result): pass
|
def set_qto_result(cls, result): pass
|
||||||
def assign_base_qto_to_object(cls, object): pass
|
def get_applicable_quantity_names(cls, qto_name): pass
|
||||||
def get_applicable_quantity_names(cls, object): pass
|
def get_applicable_base_quantity_name(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_rounded_value(cls, new_quantity): 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
|
@interface
|
||||||
class Resource:
|
class Resource:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from mathutils import Vector
|
|||||||
from ifcopenshell import util
|
from ifcopenshell import util
|
||||||
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
||||||
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
|
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
|
||||||
|
import blenderbim.bim.schema
|
||||||
|
|
||||||
|
|
||||||
class Qto(blenderbim.core.tool.Qto):
|
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))
|
bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def assign_base_qto_to_object(cls, obj):
|
def add_object_base_qto(cls, obj):
|
||||||
file = tool.Ifc.get()
|
product = tool.Ifc.get_entity(obj)
|
||||||
entity = tool.Ifc.get_entity(obj)
|
cls.add_product_base_qto(product)
|
||||||
pset_qto_name = cls.get_qto_applicable_name(obj)
|
|
||||||
ifcopenshell.api.run(
|
|
||||||
"pset.add_qto",
|
|
||||||
file,
|
|
||||||
**{
|
|
||||||
"product": entity,
|
|
||||||
"name": pset_qto_name,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_applicable_quantity_names(cls, obj):
|
def add_product_base_qto(cls, product):
|
||||||
file = tool.Ifc.get()
|
base_quantity_name = cls.get_applicable_base_quantity_name(product)
|
||||||
schema = file.schema
|
if base_quantity_name:
|
||||||
pset_qto = util.pset.PsetQto(schema)
|
return tool.Ifc.run(
|
||||||
qto_name = cls.get_qto_applicable_name(obj)
|
"pset.add_qto",
|
||||||
properties_templates = pset_qto.get_by_name(qto_name).get_info()['HasPropertyTemplates']
|
product=product,
|
||||||
applicable_quantity_names = [a.get_info()['Name'] for a in properties_templates]
|
name=base_quantity_name,
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_qto_entity(cls, obj):
|
def get_applicable_quantity_names(cls, qto_name):
|
||||||
file = tool.Ifc.get()
|
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(qto_name)
|
||||||
entity = tool.Ifc.get_entity(obj)
|
return (
|
||||||
qto_applicable_name = cls.get_qto_applicable_name(obj)
|
[property.Name for property in pset_template.HasPropertyTemplates]
|
||||||
qto_entity = tool.Pset.get_element_pset(entity, qto_applicable_name)
|
if hasattr(pset_template, "HasPropertyTemplates")
|
||||||
return qto_entity
|
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
|
@classmethod
|
||||||
def get_new_calculated_quantity(cls, qto_name, quantity_name, obj):
|
def get_new_calculated_quantity(cls, qto_name, quantity_name, obj):
|
||||||
calculator = QtoCalculator()
|
return QtoCalculator().calculate_quantity(qto_name, quantity_name, obj)
|
||||||
new_quantity = calculator.calculate_quantity(qto_name, quantity_name, obj)
|
|
||||||
return new_quantity
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_new_guessed_quantity(cls, obj, quantity_name, alternative_prop_names):
|
def get_new_guessed_quantity(cls, obj, quantity_name, alternative_prop_names):
|
||||||
calculator = QtoCalculator()
|
return QtoCalculator().guess_quantity(quantity_name, alternative_prop_names, obj)
|
||||||
new_quantity = calculator.guess_quantity(quantity_name, alternative_prop_names, obj)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_rounded_value(cls, new_quantity):
|
def get_rounded_value(cls, new_quantity):
|
||||||
return round(new_quantity, 3)
|
return round(new_quantity, 3)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_calculated_quantities(cls, obj, applicable_quantity_names):
|
def calculate_object_quantities(cls, calculator, qto_name, obj):
|
||||||
calculated_quantities = {}
|
return {
|
||||||
qto_name = cls.get_qto_applicable_name(obj)
|
quantity_name: cls.get_rounded_value(calculator.calculate_quantity(qto_name, quantity_name, obj))
|
||||||
calculator = QtoCalculator()
|
for quantity_name in cls.get_applicable_quantity_names(qto_name) or []
|
||||||
|
if cls.has_calculator(qto_name, quantity_name)
|
||||||
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
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_calculator(cls, qto_name, quantity_name):
|
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):
|
def get_guessed_quantities(cls, obj, pset_qto_properties):
|
||||||
calculated_quantities = {}
|
calculated_quantities = {}
|
||||||
for pset_qto_property in pset_qto_properties:
|
for pset_qto_property in pset_qto_properties:
|
||||||
quantity_name = pset_qto_property.get_info()['Name']
|
quantity_name = pset_qto_property.get_info()["Name"]
|
||||||
alternative_prop_names = [p.get_info()['Name'] for p in pset_qto_properties]
|
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)
|
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
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user