diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py
index 068fe6d598..b2188023b7 100644
--- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py
@@ -26,8 +26,8 @@ classes = (
operator.EditCostSchedule,
operator.EditCostItem,
operator.EditCostItemQuantity,
- operator.EditCostValue,
- operator.EditCostValueFormula,
+ operator.EditCostItemValue,
+ operator.EditCostItemValueFormula,
operator.EnableEditingCostSchedule,
operator.EnableEditingCostItems,
operator.EnableEditingCostItem,
diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py
index efab5ff5f1..c9807fb922 100644
--- a/src/blenderbim/blenderbim/bim/module/cost/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py
@@ -27,6 +27,7 @@ from blenderbim.bim.ifc import IfcStore
from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.cost.data import Data
from ifcopenshell.api.unit.data import Data as UnitData
+from ifcopenshell.api.resource.data import Data as ResourceData
class AddCostSchedule(bpy.types.Operator):
@@ -590,6 +591,7 @@ class AddCostValue(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
+ parent = self.file.by_id(self.parent)
if self.cost_type == "FIXED":
category = None
attributes = {"AppliedValue": 0.0}
@@ -599,14 +601,17 @@ class AddCostValue(bpy.types.Operator):
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))
+ value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=parent)
ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes=attributes)
- Data.load(self.file)
+ if parent.is_a("IfcConstructionResource"):
+ ResourceData.load(self.file)
+ else:
+ Data.load(self.file)
return {"FINISHED"}
class RemoveCostItemValue(bpy.types.Operator):
- bl_idname = "bim.remove_cost_item_value"
+ bl_idname = "bim.remove_cost_value"
bl_label = "Add Cost Item Value"
bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.IntProperty()
@@ -617,13 +622,17 @@ class RemoveCostItemValue(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
+ parent = self.file.by_id(self.parent)
ifcopenshell.api.run(
- "cost.remove_cost_item_value",
+ "cost.remove_cost_value",
self.file,
- parent=self.file.by_id(self.parent),
+ parent=parent,
cost_value=self.file.by_id(self.cost_value),
)
- Data.load(self.file)
+ if parent.is_a("IfcConstructionResource"):
+ ResourceData.load(self.file)
+ else:
+ Data.load(self.file)
return {"FINISHED"}
@@ -636,7 +645,7 @@ class EnableEditingCostItemValue(bpy.types.Operator):
def execute(self, context):
self.props = context.scene.BIMCostProperties
self.props.cost_value_attributes.clear()
- self.props.active_cost_item_value_id = self.cost_value
+ self.props.active_cost_value_id = self.cost_value
self.props.cost_value_editing_type = "ATTRIBUTES"
data = Data.cost_values[self.cost_value]
@@ -706,7 +715,7 @@ class DisableEditingCostItemValue(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMCostProperties
- props.active_cost_item_value_id = 0
+ props.active_cost_value_id = 0
props.cost_value_editing_type = ""
return {"FINISHED"}
@@ -720,13 +729,13 @@ class EnableEditingCostItemValueFormula(bpy.types.Operator):
def execute(self, context):
self.props = context.scene.BIMCostProperties
self.props.cost_value_attributes.clear()
- self.props.active_cost_item_value_id = self.cost_value
+ self.props.active_cost_value_id = self.cost_value
self.props.cost_value_editing_type = "FORMULA"
self.props.cost_value_formula = Data.cost_values[self.cost_value]["Formula"]
return {"FINISHED"}
-class EditCostValueFormula(bpy.types.Operator):
+class EditCostItemValueFormula(bpy.types.Operator):
bl_idname = "bim.edit_cost_value_formula"
bl_label = "Edit Cost Value Formula"
bl_options = {"REGISTER", "UNDO"}
@@ -748,7 +757,7 @@ class EditCostValueFormula(bpy.types.Operator):
return {"FINISHED"}
-class EditCostValue(bpy.types.Operator):
+class EditCostItemValue(bpy.types.Operator):
bl_idname = "bim.edit_cost_value"
bl_label = "Edit Cost Value"
bl_options = {"REGISTER", "UNDO"}
diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py
index 3d11601970..69ac4f825f 100644
--- a/src/blenderbim/blenderbim/bim/module/cost/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py
@@ -244,7 +244,7 @@ class BIMCostProperties(PropertyGroup):
name="Cost Types",
)
cost_category: StringProperty(name="Cost Category")
- active_cost_item_value_id: IntProperty(name="Active Cost Item Value Id")
+ active_cost_value_id: IntProperty(name="Active Cost Item Value Id")
cost_value_editing_type: StringProperty(name="Cost Value Editing Type")
cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute)
cost_value_formula: StringProperty(name="Cost Value Formula")
diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py
index c0542b40c0..d2062d98ad 100644
--- a/src/blenderbim/blenderbim/bim/module/cost/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py
@@ -16,10 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
+import blenderbim.bim.helper
import blenderbim.bim.module.cost.prop as CostProp
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
-from blenderbim.bim.helper import draw_attributes
from ifcopenshell.api.cost.data import Data
from ifcopenshell.api.unit.data import Data as UnitData
@@ -94,7 +94,7 @@ class BIM_PT_cost_schedules(Panel):
self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index")
def draw_editable_cost_schedule_ui(self):
- draw_attributes(self.props.cost_schedule_attributes, self.layout)
+ blenderbim.bim.helper.draw_attributes(self.props.cost_schedule_attributes, self.layout)
def draw_editable_cost_item_ui(self, cost_schedule_id):
row = self.layout.row(align=True)
@@ -140,7 +140,7 @@ class BIM_PT_cost_schedules(Panel):
self.draw_editable_cost_item_values_ui()
def draw_editable_cost_item_attributes_ui(self):
- draw_attributes(self.props.cost_item_attributes, self.layout)
+ blenderbim.bim.helper.draw_attributes(self.props.cost_item_attributes, self.layout)
def draw_editable_cost_item_quantities_ui(self):
row = self.layout.row(align=True)
@@ -175,7 +175,7 @@ class BIM_PT_cost_schedules(Panel):
self.draw_editable_cost_item_quantity_ui(box)
def draw_editable_cost_item_quantity_ui(self, layout):
- draw_attributes(self.props.quantity_attributes, self.layout)
+ blenderbim.bim.helper.draw_attributes(self.props.quantity_attributes, self.layout)
def draw_editable_cost_item_values_ui(self):
row = self.layout.row(align=True)
@@ -194,12 +194,12 @@ class BIM_PT_cost_schedules(Panel):
if self.props.cost_value_editing_type == "ATTRIBUTES":
box = self.layout.box()
- self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_item_value_id])
+ self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_value_id])
def draw_readonly_cost_value_ui(self, layout, cost_value_id):
cost_value = Data.cost_values[cost_value_id]
- if self.props.active_cost_item_value_id == cost_value_id and self.props.cost_value_editing_type == "FORMULA":
+ if self.props.active_cost_value_id == cost_value_id and self.props.cost_value_editing_type == "FORMULA":
layout.prop(self.props, "cost_value_formula", text="")
else:
cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"])
@@ -209,16 +209,16 @@ class BIM_PT_cost_schedules(Panel):
self.draw_cost_value_operator_ui(layout, cost_value_id, self.props.active_cost_item_id)
def draw_cost_value_operator_ui(self, layout, cost_value_id, parent_id):
- if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id:
+ if self.props.active_cost_value_id and self.props.active_cost_value_id == cost_value_id:
if self.props.cost_value_editing_type == "ATTRIBUTES":
- op = layout.operator("bim.edit_cost_value", text="", icon="CHECKMARK")
+ op = layout.operator("bim.edit_cost_item_value", text="", icon="CHECKMARK")
op.cost_value = cost_value_id
elif self.props.cost_value_editing_type == "FORMULA":
- op = layout.operator("bim.edit_cost_value_formula", text="", icon="CHECKMARK")
+ op = layout.operator("bim.edit_cost_item_value_formula", text="", icon="CHECKMARK")
op.cost_value = cost_value_id
layout.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL")
- elif self.props.active_cost_item_value_id:
- op = layout.operator("bim.remove_cost_item_value", text="", icon="X")
+ elif self.props.active_cost_value_id:
+ op = layout.operator("bim.remove_cost_value", text="", icon="X")
op.parent = parent_id
op.cost_value = cost_value_id
else:
@@ -226,12 +226,12 @@ class BIM_PT_cost_schedules(Panel):
op.cost_value = cost_value_id
op = layout.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL")
op.cost_value = cost_value_id
- op = layout.operator("bim.remove_cost_item_value", text="", icon="X")
+ op = layout.operator("bim.remove_cost_value", text="", icon="X")
op.parent = parent_id
op.cost_value = cost_value_id
def draw_editable_cost_value_ui(self, layout, cost_value):
- draw_attributes(self.props.cost_value_attributes, layout)
+ blenderbim.bim.helper.draw_attributes(self.props.cost_value_attributes, layout)
class BIM_PT_cost_item_types(Panel):
diff --git a/src/blenderbim/blenderbim/bim/module/resource/__init__.py b/src/blenderbim/blenderbim/bim/module/resource/__init__.py
index cb2f056939..39a17db65c 100644
--- a/src/blenderbim/blenderbim/bim/module/resource/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/resource/__init__.py
@@ -34,8 +34,14 @@ classes = (
operator.AssignResource,
operator.UnassignResource,
operator.EnableEditingResourceTime,
+ operator.EnableEditingResourceCosts,
+ operator.EnableEditingResourceCostValueFormula,
+ operator.EnableEditingResourceCostValue,
operator.EditResourceTime,
+ operator.EditResourceCostValue,
+ operator.EditResourceCostValueFormula,
operator.DisableEditingResourceTime,
+ operator.DisableEditingResourceCostValue,
operator.CalculateResourceWork,
prop.Resource,
prop.BIMResourceProperties,
diff --git a/src/blenderbim/blenderbim/bim/module/resource/operator.py b/src/blenderbim/blenderbim/bim/module/resource/operator.py
index 34dbdd12be..547dd64396 100644
--- a/src/blenderbim/blenderbim/bim/module/resource/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/resource/operator.py
@@ -1,4 +1,3 @@
-
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult
#
@@ -19,14 +18,15 @@
import bpy
import json
+import time
+import isodate
import ifcopenshell.api
-from blenderbim.bim.ifc import IfcStore
-from ifcopenshell.api.resource.data import Data
import blenderbim.bim.helper
import blenderbim.bim.module.sequence.helper as helper
-import time
from datetime import datetime
-import isodate
+from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.resource.data import Data
+from ifcopenshell.api.unit.data import Data as UnitData
class LoadResources(bpy.types.Operator):
@@ -95,7 +95,6 @@ class EnableEditingResource(bpy.types.Operator):
new.enum_value = data[attribute.name()]
-
class LoadResourceProperties(bpy.types.Operator):
bl_idname = "bim.load_resource_properties"
bl_label = "Load Resource Properties"
@@ -301,7 +300,9 @@ class EnableEditingResourceTime(bpy.types.Operator):
data = Data.resource_times[resource_time_id]
- blenderbim.bim.helper.import_attributes("IfcResourceTime", props.resource_time_attributes, data, self.import_attributes)
+ blenderbim.bim.helper.import_attributes(
+ "IfcResourceTime", props.resource_time_attributes, data, self.import_attributes
+ )
props.active_resource_time_id = resource_time_id
props.active_resource_id = self.resource
props.editing_resource_type = "USAGE"
@@ -319,7 +320,9 @@ class EnableEditingResourceTime(bpy.types.Operator):
return True
def add_resource_time(self):
- resource_time = ifcopenshell.api.run("resource.add_resource_time", self.file, resource=self.file.by_id(self.resource))
+ resource_time = ifcopenshell.api.run(
+ "resource.add_resource_time", self.file, resource=self.file.by_id(self.resource)
+ )
Data.load(self.file)
return resource_time
@@ -345,7 +348,9 @@ class EditResourceTime(bpy.types.Operator):
def _execute(self, context):
self.props = context.scene.BIMResourceProperties
- attributes = blenderbim.bim.helper.export_attributes(self.props.resource_time_attributes, self.export_attributes)
+ attributes = blenderbim.bim.helper.export_attributes(
+ self.props.resource_time_attributes, self.export_attributes
+ )
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -365,7 +370,7 @@ class EditResourceTime(bpy.types.Operator):
return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
- elif prop.name =="LevelingDelay" or "Work" in prop.name:
+ elif prop.name == "LevelingDelay" or "Work" in prop.name:
if prop.is_null:
attributes[prop.name] = None
return True
@@ -385,3 +390,186 @@ class CalculateResourceWork(bpy.types.Operator):
Data.load(self.file)
bpy.ops.bim.load_resources()
return {"FINISHED"}
+
+
+class EnableEditingResourceCosts(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_resource_costs"
+ bl_label = "Enable Editing Resource Costs"
+ bl_options = {"REGISTER", "UNDO"}
+ resource: bpy.props.IntProperty()
+
+ def execute(self, context):
+ props = context.scene.BIMResourceProperties
+ props.active_resource_id = self.resource
+ props.editing_resource_type = "COSTS"
+ bpy.ops.bim.disable_editing_resource_cost_value()
+ return {"FINISHED"}
+
+
+class DisableEditingResourceCostValue(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_resource_cost_value"
+ bl_label = "Disable Editing Resource Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ props = context.scene.BIMResourceProperties
+ props.active_cost_value_id = 0
+ props.cost_value_editing_type = ""
+ return {"FINISHED"}
+
+
+class DisableEditingResourceCostValue(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_resource_cost_value"
+ bl_label = "Disable Editing Resource Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ props = context.scene.BIMResourceProperties
+ props.active_cost_value_id = 0
+ props.cost_value_editing_type = ""
+ return {"FINISHED"}
+
+
+class EnableEditingResourceCostValueFormula(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_resource_cost_value_formula"
+ bl_label = "Enable Editing Resource Cost Value Formula"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ self.props = context.scene.BIMResourceProperties
+ self.props.cost_value_attributes.clear()
+ self.props.active_cost_value_id = self.cost_value
+ self.props.cost_value_editing_type = "FORMULA"
+ self.props.cost_value_formula = Data.cost_values[self.cost_value]["Formula"]
+ return {"FINISHED"}
+
+
+class EnableEditingResourceCostValue(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_resource_cost_value"
+ bl_label = "Enable Editing Resource Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ self.props = context.scene.BIMResourceProperties
+ self.props.cost_value_attributes.clear()
+ self.props.active_cost_value_id = self.cost_value
+ self.props.cost_value_editing_type = "ATTRIBUTES"
+ data = Data.cost_values[self.cost_value]
+
+ blenderbim.bim.helper.import_attributes(
+ data["type"],
+ self.props.cost_value_attributes,
+ data,
+ lambda name, prop, data: self.import_attributes(name, prop, data, context),
+ )
+ return {"FINISHED"}
+
+ def import_attributes(self, name, prop, data, context):
+ if name == "AppliedValue":
+ # 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
+ elif name == "UnitBasis":
+ prop = self.props.cost_value_attributes.add()
+ prop.name = "UnitBasisValue"
+ prop.data_type = "float"
+ prop.is_null = data["UnitBasis"] is None
+ prop.is_optional = True
+ if data["UnitBasis"]:
+ prop.float_value = data["UnitBasis"]["ValueComponent"] or 0
+ else:
+ prop.float_value = 0
+
+ prop = self.props.cost_value_attributes.add()
+ prop.name = "UnitBasisUnit"
+ prop.data_type = "enum"
+ prop.is_null = prop.is_optional = False
+ units = {}
+ for unit_id, unit in UnitData.units.items():
+ if unit.get("UnitType", None) in [
+ "AREAUNIT",
+ "LENGTHUNIT",
+ "TIMEUNIT",
+ "VOLUMEUNIT",
+ "MASSUNIT",
+ "USERDEFINED",
+ ]:
+ if unit["type"] == "IfcContextDependentUnit":
+ units[unit_id] = f"{unit['UnitType']} / {unit['Name']}"
+ else:
+ name = unit["Name"]
+ if unit.get("Prefix", None):
+ name = f"(unit['Prefix']) {name}"
+ units[unit_id] = f"{unit['UnitType']} / {name}"
+ prop.enum_items = json.dumps(units)
+ if data["UnitBasis"] and data["UnitBasis"]["UnitComponent"]:
+ prop.enum_value = str(data["UnitBasis"]["UnitComponent"])
+ return True
+
+
+class EditResourceCostValueFormula(bpy.types.Operator):
+ bl_idname = "bim.edit_resource_cost_value_formula"
+ bl_label = "Edit Resource Cost Value Formula"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMResourceProperties
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "cost.edit_cost_value_formula",
+ self.file,
+ **{"cost_value": self.file.by_id(self.cost_value), "formula": props.cost_value_formula},
+ )
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.disable_editing_resource_cost_value()
+ return {"FINISHED"}
+
+
+class EditResourceCostValue(bpy.types.Operator):
+ bl_idname = "bim.edit_resource_cost_value"
+ bl_label = "Edit Resource Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMResourceProperties
+ attributes = blenderbim.bim.helper.export_attributes(
+ props.cost_value_attributes, lambda attributes, prop: self.export_attributes(attributes, prop, context)
+ )
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "cost.edit_cost_value",
+ self.file,
+ **{"cost_value": self.file.by_id(self.cost_value), "attributes": attributes},
+ )
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.disable_editing_resource_cost_value()
+ return {"FINISHED"}
+
+ def export_attributes(self, attributes, prop, context):
+ if prop.name == "UnitBasisValue":
+ if prop.is_null:
+ attributes["UnitBasis"] = None
+ return True
+ attributes["UnitBasis"] = {
+ "ValueComponent": prop.float_value or 1,
+ "UnitComponent": IfcStore.get_file().by_id(
+ int(context.scene.BIMResourceProperties.cost_value_attributes.get("UnitBasisUnit").enum_value)
+ ),
+ }
+ return True
+ if prop.name == "UnitBasisUnit":
+ return True
diff --git a/src/blenderbim/blenderbim/bim/module/resource/prop.py b/src/blenderbim/blenderbim/bim/module/resource/prop.py
index 5681deba24..f9f436f6f4 100644
--- a/src/blenderbim/blenderbim/bim/module/resource/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/resource/prop.py
@@ -74,3 +74,16 @@ class BIMResourceProperties(PropertyGroup):
active_resource_time_id: IntProperty(name="Active Resource Usage Id")
resource_time_attributes: CollectionProperty(name="Resource Usage Attributes", type=Attribute)
editing_resource_type: StringProperty(name="Editing Resource Type")
+ cost_types: EnumProperty(
+ items=[
+ ("FIXED", "Fixed", "The cost value is a fixed number"),
+ ("SUM", "Sum", "The cost value is automatically derived from the sum of all nested cost items"),
+ ("CATEGORY", "Category", "The cost value represents a single category"),
+ ],
+ name="Cost Types",
+ )
+ cost_category: StringProperty(name="Cost Category")
+ active_cost_value_id: IntProperty(name="Active Resource Cost Value Id")
+ cost_value_editing_type: StringProperty(name="Cost Value Editing Type")
+ cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute)
+ cost_value_formula: StringProperty(name="Cost Value Formula")
diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py
index c9e383d628..730133be72 100644
--- a/src/blenderbim/blenderbim/bim/module/resource/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py
@@ -16,10 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
+import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.resource.data import Data
-import blenderbim.bim.helper
class BIM_PT_resources(Panel):
@@ -61,9 +61,11 @@ class BIM_PT_resources(Panel):
self.props,
"active_resource_index",
)
+
if self.props.active_resource_id and self.props.editing_resource_type == "ATTRIBUTES":
self.draw_editable_resource_attributes_ui()
-
+ elif self.props.active_resource_id and self.props.editing_resource_type == "COSTS":
+ self.draw_editable_resource_costs_ui()
elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE":
self.draw_editable_resource_time_attributes_ui()
@@ -107,6 +109,8 @@ class BIM_PT_resources(Panel):
elif self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "USAGE":
row.operator("bim.edit_resource_time", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_resource_time", text="", icon="CANCEL")
+ elif self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "COSTS":
+ row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
elif self.props.active_resource_id:
row.operator("bim.add_resource", text="", icon="ADD").resource = ifc_definition_id
row.operator("bim.remove_resource", text="", icon="X").resource = ifc_definition_id
@@ -115,6 +119,8 @@ class BIM_PT_resources(Panel):
op = row.operator("bim.calculate_resource_work", text="", icon="TEMP")
op.resource = ifc_definition_id
row.operator("bim.enable_editing_resource_time", text="", icon="TIME").resource = ifc_definition_id
+ op = row.operator("bim.enable_editing_resource_costs", text="", icon="DISC")
+ op.resource = ifc_definition_id
row.operator("bim.enable_editing_resource", text="", icon="GREASEPENCIL").resource = ifc_definition_id
row.operator("bim.remove_resource", text="", icon="X").resource = ifc_definition_id
@@ -124,6 +130,62 @@ class BIM_PT_resources(Panel):
def draw_editable_resource_time_attributes_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.resource_time_attributes, self.layout)
+ def draw_editable_resource_costs_ui(self):
+ row = self.layout.row(align=True)
+ row.prop(self.props, "cost_types", text="")
+ if self.props.cost_types == "CATEGORY":
+ row.prop(self.props, "cost_category", text="")
+ op = row.operator("bim.add_cost_value", text="", icon="ADD")
+ op.parent = self.props.active_resource_id
+ op.cost_type = self.props.cost_types
+ if self.props.cost_types == "CATEGORY":
+ op.cost_category = self.props.cost_category
+
+ for cost_value_id in Data.resources[self.props.active_resource_id]["BaseCosts"] or []:
+ row = self.layout.row(align=True)
+ self.draw_readonly_cost_value_ui(row, cost_value_id)
+
+ if self.props.cost_value_editing_type == "ATTRIBUTES":
+ box = self.layout.box()
+ self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_value_id])
+
+ def draw_readonly_cost_value_ui(self, layout, cost_value_id):
+ cost_value = Data.cost_values[cost_value_id]
+
+ if self.props.active_cost_value_id == cost_value_id and self.props.cost_value_editing_type == "FORMULA":
+ layout.prop(self.props, "cost_value_formula", text="")
+ else:
+ cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"])
+ cost_value_label += " = " + cost_value["Formula"]
+ layout.label(text=cost_value_label, icon="DISC")
+
+ self.draw_cost_value_operator_ui(layout, cost_value_id, self.props.active_resource_id)
+
+ def draw_cost_value_operator_ui(self, layout, cost_value_id, parent_id):
+ if self.props.active_cost_value_id and self.props.active_cost_value_id == cost_value_id:
+ if self.props.cost_value_editing_type == "ATTRIBUTES":
+ op = layout.operator("bim.edit_resource_cost_value", text="", icon="CHECKMARK")
+ op.cost_value = cost_value_id
+ elif self.props.cost_value_editing_type == "FORMULA":
+ op = layout.operator("bim.edit_resource_cost_value_formula", text="", icon="CHECKMARK")
+ op.cost_value = cost_value_id
+ layout.operator("bim.disable_editing_resource_cost_value", text="", icon="CANCEL")
+ elif self.props.active_cost_value_id:
+ op = layout.operator("bim.remove_cost_value", text="", icon="X")
+ op.parent = parent_id
+ op.cost_value = cost_value_id
+ else:
+ op = layout.operator("bim.enable_editing_resource_cost_value_formula", text="", icon="CON_TRANSLIKE")
+ op.cost_value = cost_value_id
+ op = layout.operator("bim.enable_editing_resource_cost_value", text="", icon="GREASEPENCIL")
+ op.cost_value = cost_value_id
+ op = layout.operator("bim.remove_cost_value", text="", icon="X")
+ op.parent = parent_id
+ op.cost_value = cost_value_id
+
+ def draw_editable_cost_value_ui(self, layout, cost_value):
+ blenderbim.bim.helper.draw_attributes(self.props.cost_value_attributes, layout)
+
class BIM_UL_resources(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py
index eb3c44f2c7..5eaaa80384 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py
@@ -11,6 +11,10 @@ class Usecase:
values = list(self.settings["parent"].CostValues or [])
values.append(value)
self.settings["parent"].CostValues = values
+ elif self.settings["parent"].is_a("IfcConstructionResource"):
+ values = list(self.settings["parent"].BaseCosts or [])
+ values.append(value)
+ self.settings["parent"].BaseCosts = values
elif self.settings["parent"].is_a("IfcCostValue"):
values = list(self.settings["parent"].Components or [])
values.append(value)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py
index dfb3cc36d9..d402ed423e 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py
@@ -3,7 +3,137 @@ import ifcopenshell.util.unit
import ifcopenshell.util.cost
-class Data:
+class CostValueTrait:
+ @classmethod
+ def load_cost_values(cls, root_element, data):
+ data["CostValues"] = []
+ data["CategoryValues"] = {}
+ data["UnitBasisValueComponent"] = None
+ data["UnitBasisUnitSymbol"] = None
+ data["TotalAppliedValue"] = 0.0
+ data["TotalCost"] = 0.0
+ if root_element.is_a("IfcCostItem"):
+ values = root_element.CostValues
+ elif root_element.is_a("IfcConstructionResource"):
+ values = root_element.BaseCosts
+ for cost_value in values or []:
+ cls.load_cost_value(root_element, data, cost_value)
+ data["CostValues"].append(cost_value.id())
+ data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"]
+ if cost_value.UnitBasis:
+ cost_value_data = cls.cost_values[cost_value.id()]
+ data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
+ data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
+ if data["UnitBasisValueComponent"]:
+ data["TotalCost"] = data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"]
+ else:
+ data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"]
+
+ @classmethod
+ def load_cost_value(cls, root_element, root_element_data, cost_value):
+ value_data = cost_value.get_info()
+ del value_data["AppliedValue"]
+ if value_data["UnitBasis"]:
+ data = cost_value.UnitBasis.get_info()
+ data["ValueComponent"] = data["ValueComponent"].wrappedValue
+ data["UnitComponent"] = data["UnitComponent"].id()
+ data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(cost_value.UnitBasis.UnitComponent)
+ value_data["UnitBasis"] = data
+ if value_data["ApplicableDate"]:
+ value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"])
+ if value_data["FixedUntilDate"]:
+ value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"])
+ value_data["Components"] = [c.id() for c in value_data["Components"] or []]
+ value_data["AppliedValue"] = cls.calculate_applied_value(root_element, cost_value)
+
+ if cost_value.Category not in [None, "*"]:
+ root_element_data["CategoryValues"].setdefault(cost_value.Category, 0)
+ root_element_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_value(root_element, root_element_data, component)
+
+ @classmethod
+ def calculate_applied_value(cls, root_element, cost_value, category_filter=None):
+ if cost_value.ArithmeticOperator and cost_value.Components:
+ component_values = []
+ for component in cost_value.Components:
+ component_values.append(cls.calculate_applied_value(root_element, component, category_filter))
+ if cost_value.ArithmeticOperator == "ADD":
+ return sum(component_values)
+ result = component_values.pop(0)
+ if cost_value.ArithmeticOperator == "DIVIDE":
+ for value in component_values:
+ try:
+ result /= value
+ except ZeroDivisionError:
+ pass
+ elif cost_value.ArithmeticOperator == "MULTIPLY":
+ for value in component_values:
+ result *= value
+ elif cost_value.ArithmeticOperator == "SUBTRACT":
+ for value in component_values:
+ result -= value
+ return result
+ if cost_value.Category is None:
+ return cls.get_primitive_applied_value(cost_value.AppliedValue)
+ elif cost_value.Category == "*":
+ if root_element.IsNestedBy:
+ return cls.sum_child_root_elements(root_element)
+ else:
+ return cls.get_primitive_applied_value(cost_value.AppliedValue)
+ elif cost_value.Category:
+ if root_element.IsNestedBy:
+ return cls.sum_child_root_elements(root_element, category_filter=cost_value.Category)
+ else:
+ return cls.get_primitive_applied_value(cost_value.AppliedValue)
+ return 0
+
+ @classmethod
+ def sum_child_root_elements(cls, root_element, category_filter=None):
+ result = 0
+ for rel in root_element.IsNestedBy:
+ for child_root_element in rel.RelatedObjects:
+ if root_element.is_a("IfcCostItem"):
+ values = child_root_element.CostValues
+ elif root_element.is_a("IfcConstructionResource"):
+ values = child_root_element.BaseCosts
+ for child_cost_value in values or []:
+ if category_filter and child_cost_value.Category != category_filter:
+ continue
+ child_applied_value = cls.calculate_applied_value(child_root_element, child_cost_value)
+ child_quantity = cls.get_total_quantity(child_root_element)
+ if child_cost_value.UnitBasis:
+ value_component = child_cost_value.UnitBasis.ValueComponent.wrappedValue
+ result += child_quantity / value_component * child_applied_value
+ else:
+ result += child_quantity * child_applied_value
+ return result
+
+ @classmethod
+ def get_total_quantity(cls, root_element):
+ if root_element.is_a("IfcCostItem"):
+ return sum([q[3] for q in root_element.CostQuantities or []]) or 1.0
+ elif root_element.is_a("IfcConstructionResource"):
+ return root_element.BaseQuantity[3] if root_element.BaseQuantity else 1.0
+
+ @classmethod
+ def get_primitive_applied_value(cls, applied_value):
+ if not applied_value:
+ return 0.0
+ elif isinstance(applied_value, float):
+ return applied_value
+ elif hasattr(applied_value, "wrappedValue") and isinstance(applied_value.wrappedValue, float):
+ return applied_value.wrappedValue
+ elif applied_value.is_a("IfcMeasureWithUnit"):
+ return applied_value.ValueComponent
+ assert False, "Applied value {applied_value} not implemented"
+
+
+class Data(CostValueTrait):
is_loaded = False
cost_schedules = {}
cost_items = {}
@@ -63,7 +193,7 @@ class Data:
parametric_quantities.extend(quantities)
cls.cost_items[cost_item.id()] = data
cls.load_cost_item_quantities(cost_item, data, parametric_quantities)
- cls.load_cost_item_values(cost_item, data)
+ cls.load_cost_values(cost_item, data)
cls.is_loaded = True
@classmethod
@@ -104,122 +234,3 @@ class Data:
else:
data["Unit"] = None
data["UnitSymbol"] = None
-
- @classmethod
- def load_cost_item_values(cls, cost_item, data):
- data["CostValues"] = []
- data["CategoryValues"] = {}
- data["UnitBasisValueComponent"] = None
- data["UnitBasisUnitSymbol"] = None
- data["TotalAppliedValue"] = 0.0
- data["TotalCost"] = 0.0
- for cost_value in cost_item.CostValues or []:
- cls.load_cost_item_value(cost_item, data, cost_value)
- data["CostValues"].append(cost_value.id())
- data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"]
- if cost_value.UnitBasis:
- cost_value_data = cls.cost_values[cost_value.id()]
- data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
- data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
- if data["UnitBasisValueComponent"]:
- data["TotalCost"] = (
- data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"]
- )
- else:
- data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"]
-
- @classmethod
- def load_cost_item_value(cls, cost_item, cost_item_data, cost_value):
- value_data = cost_value.get_info()
- del value_data["AppliedValue"]
- if value_data["UnitBasis"]:
- data = cost_value.UnitBasis.get_info()
- data["ValueComponent"] = data["ValueComponent"].wrappedValue
- data["UnitComponent"] = data["UnitComponent"].id()
- data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(cost_value.UnitBasis.UnitComponent)
- value_data["UnitBasis"] = data
- if value_data["ApplicableDate"]:
- value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"])
- if value_data["FixedUntilDate"]:
- value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"])
- value_data["Components"] = [c.id() for c in value_data["Components"] or []]
- value_data["AppliedValue"] = cls.calculate_applied_value(cost_item, cost_value)
-
- if cost_value.Category not in [None, "*"]:
- 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)
-
- @classmethod
- def calculate_applied_value(cls, cost_item, cost_value, category_filter=None):
- if cost_value.ArithmeticOperator and cost_value.Components:
- component_values = []
- for component in cost_value.Components:
- component_values.append(cls.calculate_applied_value(cost_item, component, category_filter))
- if cost_value.ArithmeticOperator == "ADD":
- return sum(component_values)
- result = component_values.pop(0)
- if cost_value.ArithmeticOperator == "DIVIDE":
- for value in component_values:
- try:
- result /= value
- except ZeroDivisionError:
- pass
- elif cost_value.ArithmeticOperator == "MULTIPLY":
- for value in component_values:
- result *= value
- elif cost_value.ArithmeticOperator == "SUBTRACT":
- for value in component_values:
- result -= value
- return result
- if cost_value.Category is None:
- return cls.get_primitive_applied_value(cost_value.AppliedValue)
- elif cost_value.Category == "*":
- if cost_item.IsNestedBy:
- return cls.sum_child_cost_items(cost_item)
- else:
- return cls.get_primitive_applied_value(cost_value.AppliedValue)
- elif cost_value.Category:
- if cost_item.IsNestedBy:
- return cls.sum_child_cost_items(cost_item, category_filter=cost_value.Category)
- else:
- return cls.get_primitive_applied_value(cost_value.AppliedValue)
- return 0
-
- @classmethod
- def sum_child_cost_items(cls, cost_item, category_filter=None):
- result = 0
- for rel in cost_item.IsNestedBy:
- for child_cost_item in rel.RelatedObjects:
- for child_cost_value in child_cost_item.CostValues or []:
- if category_filter and child_cost_value.Category != category_filter:
- continue
- child_applied_value = cls.calculate_applied_value(child_cost_item, child_cost_value)
- child_quantity = cls.get_total_quantity(child_cost_item)
- if child_cost_value.UnitBasis:
- value_component = child_cost_value.UnitBasis.ValueComponent.wrappedValue
- result += child_quantity / value_component * child_applied_value
- else:
- result += child_quantity * child_applied_value
- return result
-
- @classmethod
- def get_total_quantity(cls, cost_item):
- return sum([q[3] for q in cost_item.CostQuantities or []]) or 1.0
-
- @classmethod
- def get_primitive_applied_value(cls, applied_value):
- if not applied_value:
- return 0.0
- elif isinstance(applied_value, float):
- return applied_value
- elif hasattr(applied_value, "wrappedValue") and isinstance(applied_value.wrappedValue, float):
- return applied_value.wrappedValue
- elif applied_value.is_a("IfcMeasureWithUnit"):
- return applied_value.ValueComponent
- assert False, "Applied value {applied_value} not implemented"
diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_value.py
similarity index 78%
rename from src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py
rename to src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_value.py
index 58eb0b9675..e3cab8dc5f 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_value.py
@@ -13,6 +13,10 @@ class Usecase:
values = list(self.settings["parent"].CostValues)
values.remove(self.settings["cost_value"])
self.settings["parent"].CostValues = values if values else None
+ elif self.settings["parent"].is_a("IfcConstructionResource"):
+ values = list(self.settings["parent"].BaseCosts)
+ values.remove(self.settings["cost_value"])
+ self.settings["parent"].BaseCosts = values if values else None
elif self.settings["parent"].is_a("IfcCostValue"):
components = list(self.settings["parent"].Components)
components.remove(self.settings["cost_value"])
diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/data.py b/src/ifcopenshell-python/ifcopenshell/api/resource/data.py
index b917e18920..fae5c37779 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/resource/data.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/resource/data.py
@@ -1,17 +1,20 @@
import ifcopenshell
import ifcopenshell.util.date
+from ifcopenshell.api.cost.data import CostValueTrait
-class Data:
+class Data(CostValueTrait):
is_loaded = False
resources = {}
resource_times = {}
+ cost_values = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.resources = {}
cls.resource_times = {}
+ cls.cost_values = {}
@classmethod
def load(cls, file):
@@ -25,6 +28,7 @@ class Data:
@classmethod
def load_resources(cls):
cls.resources = {}
+ cls.cost_values = {}
for resource in cls._file.by_type("IfcResource"):
data = resource.get_info()
del data["OwnerHistory"]
@@ -40,6 +44,12 @@ class Data:
data["HasContext"] = resource.HasContext[0].RelatingContext.id() if resource.HasContext else None
if resource.Usage:
data["Usage"] = data["Usage"].id()
+ data["TotalCostQuantity"] = cls.get_total_quantity(resource)
+ if resource.BaseQuantity:
+ data["BaseQuantity"] = resource.BaseQuantity.id()
+ if resource.BaseCosts:
+ data["BaseCosts"] = [e.id() for e in resource.BaseCosts]
+ cls.load_cost_values(resource, data)
cls.resources[resource.id()] = data
@classmethod