mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
cost schedule - prohibit adding multiple quantity types on 1 cost item
All IfcCostItem.CostQuantities supposed to use only 1 type of quantity, so they can be added up. Changed UI to reflect that requirement - dropdown for selecting quantity type is now only visible if cost item has no quantities, otherwise it just reuses already used quantity type. See https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcCostItem.htm Before - https://i.imgur.com/gFlwHEk.png After - https://i.imgur.com/pk3wXfe.png
This commit is contained in:
@@ -29,7 +29,7 @@ from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from typing import Optional, Callable, Any
|
||||
from typing import Optional, Callable, Any, Union
|
||||
|
||||
|
||||
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
|
||||
@@ -91,7 +91,11 @@ def import_attributes(ifc_class, props, data, callback=None):
|
||||
|
||||
|
||||
# A more elegant attribute importer signature, intended to supersede import_attributes
|
||||
def import_attributes2(element, props, callback=None):
|
||||
def import_attributes2(
|
||||
element: Union[str, ifcopenshell.entity_instance],
|
||||
props: bpy.types.PropertyGroup,
|
||||
callback: Optional[Callable] = None,
|
||||
) -> None:
|
||||
if isinstance(element, str):
|
||||
attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes()
|
||||
info = {a.name(): None for a in attributes}
|
||||
|
||||
@@ -159,6 +159,7 @@ class CostSchedulesData:
|
||||
data["UnitSymbol"] = "-"
|
||||
if cost_item.CostQuantities:
|
||||
quantity = cost_item.CostQuantities[0]
|
||||
data["QuantityType"] = quantity.is_a()
|
||||
unit = ifcopenshell.util.unit.get_property_unit(quantity, tool.Ifc.get())
|
||||
if unit:
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
|
||||
@@ -21,6 +21,7 @@ import blenderbim.bim.module.cost.prop as CostProp
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.cost.data import CostSchedulesData
|
||||
from typing import Any
|
||||
|
||||
|
||||
class BIM_PT_cost_schedules(Panel):
|
||||
@@ -178,24 +179,33 @@ class BIM_PT_cost_schedules(Panel):
|
||||
"active_cost_item_index",
|
||||
)
|
||||
if self.props.active_cost_item_id:
|
||||
cost_item = CostSchedulesData.data["cost_items"][ifc_definition_id]
|
||||
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
||||
self.draw_editable_cost_item_attributes_ui()
|
||||
elif self.props.cost_item_editing_type == "QUANTITIES":
|
||||
self.draw_editable_cost_item_quantities_ui()
|
||||
self.draw_editable_cost_item_quantities_ui(cost_item)
|
||||
elif self.props.cost_item_editing_type == "VALUES":
|
||||
self.draw_editable_cost_item_values_ui()
|
||||
|
||||
def draw_editable_cost_item_attributes_ui(self):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.cost_item_attributes, self.layout)
|
||||
|
||||
def draw_editable_cost_item_quantities_ui(self):
|
||||
def draw_editable_cost_item_quantities_ui(self, cost_item: dict[str, Any]):
|
||||
quantities = CostSchedulesData.data["cost_quantities"]
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "quantity_types", text="")
|
||||
# In IFC, all quantities of IfcCostTime should have 1 type.
|
||||
if quantities:
|
||||
quantity_class = cost_item["QuantityType"]
|
||||
row.label(text=quantity_class)
|
||||
else:
|
||||
row.prop(self.props, "quantity_types", text="")
|
||||
quantity_class = self.props.quantity_types
|
||||
|
||||
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
|
||||
op.ifc_class = quantity_class
|
||||
|
||||
for quantity in CostSchedulesData.data["cost_quantities"]:
|
||||
for quantity in quantities:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=quantity["name"])
|
||||
row.label(text=quantity["value"])
|
||||
|
||||
@@ -7,7 +7,7 @@ import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.bim.helper
|
||||
import json
|
||||
from typing import Optional
|
||||
from typing import Optional, Any, Generator
|
||||
|
||||
|
||||
class Cost(blenderbim.core.tool.Cost):
|
||||
@@ -261,7 +261,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
blenderbim.bim.helper.import_attributes2(physical_quantity, props.quantity_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_item_values(cls, cost_item=None):
|
||||
def enable_editing_cost_item_values(cls, cost_item: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.active_cost_item_id = cost_item.id()
|
||||
props.cost_item_editing_type = "VALUES"
|
||||
@@ -289,7 +289,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
return attributes
|
||||
|
||||
@classmethod
|
||||
def load_cost_item_value_attributes(cls, cost_value=None):
|
||||
def load_cost_item_value_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
def import_attributes(name, prop, data, cost_value, is_rates, props_collection):
|
||||
if name == "AppliedValue":
|
||||
# TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure)
|
||||
@@ -336,36 +336,38 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
blenderbim.bim.helper.import_attributes2(cost_value, props.cost_value_attributes, callback=callback)
|
||||
|
||||
@classmethod
|
||||
def calculate_applied_value(cls, cost_item, cost_value):
|
||||
def calculate_applied_value(
|
||||
cls, cost_item: ifcopenshell.entity_instance, cost_value: ifcopenshell.entity_instance
|
||||
) -> float:
|
||||
return ifcopenshell.util.cost.calculate_applied_value(cost_item, cost_value)
|
||||
|
||||
@classmethod
|
||||
def is_active_schedule_of_rates(cls):
|
||||
def is_active_schedule_of_rates(cls) -> bool:
|
||||
return (
|
||||
tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_schedule_id).PredefinedType
|
||||
== "SCHEDULEOFRATES"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_item_value(cls, cost_value=None):
|
||||
def enable_editing_cost_item_value(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.active_cost_value_id = cost_value.id()
|
||||
props.cost_value_editing_type = "ATTRIBUTES"
|
||||
|
||||
@classmethod
|
||||
def disable_editing_cost_item_value(cls):
|
||||
def disable_editing_cost_item_value(cls) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.active_cost_value_id = 0
|
||||
props.cost_value_editing_type = ""
|
||||
|
||||
@classmethod
|
||||
def load_cost_item_value_formula_attributes(cls, cost_value=None):
|
||||
def load_cost_item_value_formula_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.cost_value_attributes.clear()
|
||||
bpy.context.scene.BIMCostProperties.cost_value_formula = ifcopenshell.util.cost.serialise_cost_value(cost_value)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_item_value_formula(cls, cost_value=None):
|
||||
def enable_editing_cost_item_value_formula(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.active_cost_value_id = cost_value.id()
|
||||
props.cost_value_editing_type = "FORMULA"
|
||||
@@ -375,7 +377,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
return bpy.context.scene.BIMCostProperties.cost_value_formula
|
||||
|
||||
@classmethod
|
||||
def get_cost_value_attributes(cls):
|
||||
def get_cost_value_attributes(cls) -> dict[str, Any]:
|
||||
def export_attributes(attributes, prop):
|
||||
if prop.name == "UnitBasisValue":
|
||||
if prop.is_null:
|
||||
@@ -394,13 +396,18 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
return blenderbim.bim.helper.export_attributes(props.cost_value_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def get_cost_value_unit_component(cls):
|
||||
def get_cost_value_unit_component(cls) -> ifcopenshell.entity_instance:
|
||||
return tool.Ifc.get().by_id(
|
||||
int(bpy.context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_cost_item_assignments(cls, cost_item, filter_by_type=None, is_deep=False):
|
||||
def get_cost_item_assignments(
|
||||
cls,
|
||||
cost_item: ifcopenshell.entity_instance,
|
||||
filter_by_type: Optional[ifcopenshell.util.cost.FILTER_BY_TYPE] = None,
|
||||
is_deep: bool = False,
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
return ifcopenshell.util.cost.get_cost_item_assignments(
|
||||
cost_item, filter_by_type=filter_by_type, is_deep=is_deep
|
||||
)
|
||||
@@ -410,30 +417,40 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
return bpy.context.scene.BIMCostProperties.show_nested_elements
|
||||
|
||||
@classmethod
|
||||
def get_cost_item_products(cls, cost_item, is_deep=False):
|
||||
def get_cost_item_products(
|
||||
cls, cost_item: ifcopenshell.entity_instance, is_deep: bool = False
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
return cls.get_cost_item_assignments(cost_item, filter_by_type="PRODUCT", is_deep=is_deep)
|
||||
|
||||
@classmethod
|
||||
def get_cost_item_resources(cls, cost_item, is_deep=False):
|
||||
def get_cost_item_resources(
|
||||
cls, cost_item: ifcopenshell.entity_instance, is_deep: bool = False
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
return cls.get_cost_item_assignments(cost_item, filter_by_type="RESOURCE", is_deep=is_deep)
|
||||
|
||||
@classmethod
|
||||
def get_cost_item_processes(cls, cost_item, is_deep=False):
|
||||
def get_cost_item_processes(
|
||||
cls, cost_item: ifcopenshell.entity_instance, is_deep: bool = False
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
return cls.get_cost_item_assignments(cost_item, filter_by_type="PROCESS", is_deep=is_deep)
|
||||
|
||||
@classmethod
|
||||
def get_schedule_cost_items(cls, cost_schedule):
|
||||
def get_schedule_cost_items(
|
||||
cls, cost_schedule: ifcopenshell.entity_instance
|
||||
) -> Generator[ifcopenshell.entity_instance, None, None]:
|
||||
return ifcopenshell.util.cost.get_schedule_cost_items(cost_schedule)
|
||||
|
||||
@classmethod
|
||||
def get_cost_schedule_products(cls, cost_schedule):
|
||||
def get_cost_schedule_products(
|
||||
cls, cost_schedule: ifcopenshell.entity_instance
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
products = []
|
||||
for cost_item in ifcopenshell.util.cost.get_schedule_cost_items(cost_schedule):
|
||||
products.extend(cls.get_cost_item_products(cost_item))
|
||||
return products
|
||||
|
||||
@classmethod
|
||||
def import_cost_schedule_csv(cls, file_path=None, is_schedule_of_rates=False):
|
||||
def import_cost_schedule_csv(cls, file_path: Optional[str] = None, is_schedule_of_rates: bool = False) -> None:
|
||||
if not file_path:
|
||||
return
|
||||
from ifc5d.csv2ifc import Csv2Ifc
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
|
||||
import lark
|
||||
import ifcopenshell
|
||||
from typing import Optional, Union
|
||||
from typing import Optional, Union, Literal, Generator, Any
|
||||
|
||||
|
||||
arithmetic_operator_symbols = {"ADD": "+", "DIVIDE": "/", "MULTIPLY": "*", "SUBTRACT": "-"}
|
||||
symbol_arithmetic_operators = {"+": "ADD", "/": "DIVIDE", "*": "MULTIPLY", "-": "SUBTRACT"}
|
||||
FILTER_BY_TYPE = Literal["PRODUCT", "RESOURCE", "PROCESS"]
|
||||
|
||||
|
||||
def get_primitive_applied_value(applied_value: Union[ifcopenshell.entity_instance, float, None]) -> float:
|
||||
@@ -150,11 +151,11 @@ def serialise_applied_value(applied_value: ifcopenshell.entity_instance) -> str:
|
||||
return "?"
|
||||
|
||||
|
||||
def unserialise_cost_value(formula, cost_value):
|
||||
def unserialise_cost_value(formula: str, cost_value: ifcopenshell.entity_instance) -> dict[str, Any]:
|
||||
unserialiser = CostValueUnserialiser()
|
||||
result = unserialiser.parse(formula)
|
||||
|
||||
def map_element_to_result(element, result):
|
||||
def map_element_to_result(element: ifcopenshell.entity_instance, result: dict):
|
||||
result["ifc"] = element
|
||||
for i, component in enumerate(result.get("Components", [])):
|
||||
if element.Components and i < len(element.Components):
|
||||
@@ -164,7 +165,7 @@ def unserialise_cost_value(formula, cost_value):
|
||||
return result
|
||||
|
||||
|
||||
def get_cost_items_for_product(product):
|
||||
def get_cost_items_for_product(product: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Returns a list of cost items related to the given product.
|
||||
|
||||
@@ -181,7 +182,7 @@ def get_cost_items_for_product(product):
|
||||
return cost_items
|
||||
|
||||
|
||||
def get_root_cost_items(cost_schedule):
|
||||
def get_root_cost_items(cost_schedule: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
return [
|
||||
related_object
|
||||
for rel in cost_schedule.Controls or []
|
||||
@@ -190,26 +191,32 @@ def get_root_cost_items(cost_schedule):
|
||||
]
|
||||
|
||||
|
||||
def get_all_nested_cost_items(cost_item):
|
||||
def get_all_nested_cost_items(
|
||||
cost_item: ifcopenshell.entity_instance,
|
||||
) -> Generator[ifcopenshell.entity_instance, None, None]:
|
||||
for cost_item in get_nested_cost_items(cost_item):
|
||||
yield cost_item
|
||||
yield from get_all_nested_cost_items(cost_item)
|
||||
|
||||
|
||||
def get_nested_cost_items(cost_item, is_deep=False):
|
||||
def get_nested_cost_items(cost_item: ifcopenshell.entity_instance, is_deep=False) -> list[ifcopenshell.entity_instance]:
|
||||
if is_deep:
|
||||
return list(get_all_nested_cost_items(cost_item))
|
||||
else:
|
||||
return [obj for rel in cost_item.IsNestedBy for obj in rel.RelatedObjects]
|
||||
|
||||
|
||||
def get_schedule_cost_items(cost_schedule):
|
||||
def get_schedule_cost_items(
|
||||
cost_schedule: ifcopenshell.entity_instance,
|
||||
) -> Generator[ifcopenshell.entity_instance, None, None]:
|
||||
for cost_item in get_root_cost_items(cost_schedule):
|
||||
yield cost_item
|
||||
yield from get_all_nested_cost_items(cost_item)
|
||||
|
||||
|
||||
def get_cost_assignments_by_type(cost_item, filter_by_type=None):
|
||||
def get_cost_assignments_by_type(
|
||||
cost_item: ifcopenshell.entity_instance, filter_by_type: Optional[FILTER_BY_TYPE] = None
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
if filter_by_type is not None:
|
||||
if filter_by_type == "PRODUCT":
|
||||
filter_by_type = "IfcElement"
|
||||
@@ -225,7 +232,9 @@ def get_cost_assignments_by_type(cost_item, filter_by_type=None):
|
||||
]
|
||||
|
||||
|
||||
def get_cost_item_assignments(cost_item, filter_by_type=None, is_deep=False):
|
||||
def get_cost_item_assignments(
|
||||
cost_item: ifcopenshell.entity_instance, filter_by_type: Optional[FILTER_BY_TYPE] = None, is_deep: bool = False
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
if not is_deep:
|
||||
return get_cost_assignments_by_type(cost_item, filter_by_type)
|
||||
else:
|
||||
@@ -237,7 +246,7 @@ def get_cost_item_assignments(cost_item, filter_by_type=None, is_deep=False):
|
||||
|
||||
|
||||
class CostValueUnserialiser:
|
||||
def parse(self, formula):
|
||||
def parse(self, formula: str):
|
||||
l = lark.Lark(
|
||||
"""start: formula
|
||||
formula: operand (operator operand)*
|
||||
|
||||
Reference in New Issue
Block a user