mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Cost values are now serialised and shown as formulas
This commit is contained in:
@@ -592,12 +592,15 @@ class AddCostValue(bpy.types.Operator):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.cost_type == "FIXED":
|
||||
category = None
|
||||
attributes = {"AppliedValue": 0.0}
|
||||
elif self.cost_type == "SUM":
|
||||
category = "*"
|
||||
attributes = {"Category": category}
|
||||
elif self.cost_type == "CATEGORY":
|
||||
category = self.cost_category
|
||||
attributes = {"Category": 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=attributes)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -646,8 +649,11 @@ class EnableEditingCostItemValue(bpy.types.Operator):
|
||||
|
||||
def import_attributes(self, name, prop, data, context):
|
||||
if name == "AppliedValue":
|
||||
# TODO: for now, only support simple values
|
||||
# TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure)
|
||||
prop = self.props.cost_value_attributes.add()
|
||||
prop.data_type = "float"
|
||||
prop.name = "AppliedValue"
|
||||
prop.is_optional = True
|
||||
prop.float_value = 0.0 if prop.is_null else data[name]
|
||||
return True
|
||||
if (
|
||||
|
||||
@@ -190,6 +190,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
|
||||
for cost_value_id in Data.cost_items[self.props.active_cost_item_id]["CostValues"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=Data.cost_values[cost_value_id]["Formula"])
|
||||
self.draw_readonly_cost_value_ui(row, cost_value_id)
|
||||
|
||||
if self.props.active_cost_item_value_id:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.cost
|
||||
|
||||
|
||||
class Data:
|
||||
@@ -148,6 +149,8 @@ class Data:
|
||||
cost_item_data["CategoryValues"].setdefault(cost_value.Category, 0)
|
||||
cost_item_data["CategoryValues"][cost_value.Category] += value_data["AppliedValue"]
|
||||
|
||||
value_data["Formula"] = ifcopenshell.util.cost.serialise_cost_value(cost_value)
|
||||
|
||||
cls.cost_values[cost_value.id()] = value_data
|
||||
for component in cost_value.Components or []:
|
||||
cls.load_cost_item_value(cost_item, cost_item_data, component)
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import lark
|
||||
|
||||
|
||||
arithmetic_operator_symbols = {"ADD": "+", "DIVIDE": "/", "MULTIPLY": "*", "SUBTRACT": "-"}
|
||||
symbol_arithmetic_operators = {"+": "ADD", "/": "DIVIDE", "*": "MULTIPLY", "-": "SUBTRACT"}
|
||||
|
||||
|
||||
def serialise_cost_value(cost_value):
|
||||
result = _serialise_cost_value(cost_value)
|
||||
if result and result[0] == "(" and result[-1] == ")":
|
||||
return result[1:-1]
|
||||
return result
|
||||
|
||||
|
||||
def _serialise_cost_value(cost_value):
|
||||
value = ""
|
||||
if cost_value.ArithmeticOperator and cost_value.Components:
|
||||
operator = arithmetic_operator_symbols[cost_value.ArithmeticOperator]
|
||||
serialised_components = []
|
||||
for component in cost_value.Components:
|
||||
serialised_components.append(_serialise_cost_value(component))
|
||||
value = operator.join(serialised_components)
|
||||
elif cost_value.AppliedValue is not None:
|
||||
value = serialise_applied_value(cost_value.AppliedValue)
|
||||
|
||||
category = ""
|
||||
if cost_value.Category == "*":
|
||||
category = "SUM"
|
||||
elif cost_value.Category:
|
||||
category = cost_value.Category
|
||||
|
||||
if not category and not value:
|
||||
value = "0"
|
||||
|
||||
if category:
|
||||
return f"{category}({value})"
|
||||
elif cost_value.Components:
|
||||
return f"({value})"
|
||||
return value
|
||||
|
||||
|
||||
def serialise_applied_value(applied_value):
|
||||
if applied_value.is_a("IfcMonetaryMeasure"):
|
||||
return str(applied_value.wrappedValue)
|
||||
return "?"
|
||||
|
||||
|
||||
def unserialise_cost_value(formula, cost_value):
|
||||
unserialiser = CostValueUnserialiser()
|
||||
result = unserialiser.parse(formula)
|
||||
def map_element_to_result(element, result):
|
||||
result["ifc"] = element
|
||||
for i, component in enumerate(result.get("Components", [])):
|
||||
if element.Components and i < len(element.Components):
|
||||
map_element_to_formula(element.Components[i], result["Components"][i])
|
||||
map_element_to_result(cost_value, result)
|
||||
return result
|
||||
|
||||
|
||||
class CostValueUnserialiser:
|
||||
def parse(self, formula):
|
||||
l = lark.Lark(
|
||||
"""start: formula
|
||||
formula: operand (operator operand)*
|
||||
operand: value | category "(" formula ")"
|
||||
value: NUMBER?
|
||||
category: WORD?
|
||||
operator: add | divide | multiply | subtract
|
||||
add: "+"
|
||||
divide: "/"
|
||||
multiply: "*"
|
||||
subtract: "-"
|
||||
|
||||
// Embed common.lark for packaging
|
||||
DIGIT: "0".."9"
|
||||
HEXDIGIT: "a".."f"|"A".."F"|DIGIT
|
||||
INT: DIGIT+
|
||||
SIGNED_INT: ["+"|"-"] INT
|
||||
DECIMAL: INT "." INT? | "." INT
|
||||
_EXP: ("e"|"E") SIGNED_INT
|
||||
FLOAT: INT _EXP | DECIMAL _EXP?
|
||||
SIGNED_FLOAT: ["+"|"-"] FLOAT
|
||||
NUMBER: FLOAT | INT
|
||||
SIGNED_NUMBER: ["+"|"-"] NUMBER
|
||||
_STRING_INNER: /.*?/
|
||||
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
|
||||
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
|
||||
LCASE_LETTER: "a".."z"
|
||||
UCASE_LETTER: "A".."Z"
|
||||
LETTER: UCASE_LETTER | LCASE_LETTER
|
||||
WORD: LETTER+
|
||||
CNAME: ("_"|LETTER) ("_"|LETTER|DIGIT)*
|
||||
WS_INLINE: (" "|/\\t/)+
|
||||
WS: /[ \\t\\f\\r\\n]/+
|
||||
CR : /\\r/
|
||||
LF : /\\n/
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
"""
|
||||
)
|
||||
start = l.parse(formula)
|
||||
return self.get_formula(start.children[0])
|
||||
|
||||
def get_formula(self, formula):
|
||||
if len(formula.children) == 1:
|
||||
return self.get_operand(formula.children[0])
|
||||
results = {"Components": []}
|
||||
for child in formula.children:
|
||||
if child.data == "operand":
|
||||
results["Components"].append(self.get_operand(child))
|
||||
elif child.data == "operator":
|
||||
results["ArithmeticOperator"] = self.get_operator(child)
|
||||
return results
|
||||
|
||||
def get_operand(self, operand):
|
||||
child = operand.children[0]
|
||||
if child.data == "value":
|
||||
return {"AppliedValue": self.get_value(child)}
|
||||
elif child.data == "category":
|
||||
data = {}
|
||||
category = self.get_category(child)
|
||||
if category:
|
||||
data["Category"] = category
|
||||
formula = self.get_formula(operand.children[1])
|
||||
if formula.get("Components"):
|
||||
data["Components"] = formula["Components"]
|
||||
data["ArithmeticOperator"] = formula["ArithmeticOperator"]
|
||||
else:
|
||||
data["AppliedValue"] = formula["AppliedValue"]
|
||||
return data
|
||||
|
||||
def get_value(self, value):
|
||||
if value.children:
|
||||
return value.children[0].value
|
||||
|
||||
def get_category(self, category):
|
||||
if category.children:
|
||||
return category.children[0].value
|
||||
|
||||
def get_operator(self, operator):
|
||||
return operator.children[0].data.upper()
|
||||
Reference in New Issue
Block a user