Resource base costs are now supported with all the features coming from the cost module.

This commit is contained in:
Dion Moult
2021-08-27 17:12:21 +10:00
parent b99c5036e6
commit b272210a97
12 changed files with 468 additions and 161 deletions
@@ -26,8 +26,8 @@ classes = (
operator.EditCostSchedule, operator.EditCostSchedule,
operator.EditCostItem, operator.EditCostItem,
operator.EditCostItemQuantity, operator.EditCostItemQuantity,
operator.EditCostValue, operator.EditCostItemValue,
operator.EditCostValueFormula, operator.EditCostItemValueFormula,
operator.EnableEditingCostSchedule, operator.EnableEditingCostSchedule,
operator.EnableEditingCostItems, operator.EnableEditingCostItems,
operator.EnableEditingCostItem, operator.EnableEditingCostItem,
@@ -27,6 +27,7 @@ from blenderbim.bim.ifc import IfcStore
from bpy_extras.io_utils import ImportHelper from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.cost.data import Data from ifcopenshell.api.cost.data import Data
from ifcopenshell.api.unit.data import Data as UnitData from ifcopenshell.api.unit.data import Data as UnitData
from ifcopenshell.api.resource.data import Data as ResourceData
class AddCostSchedule(bpy.types.Operator): class AddCostSchedule(bpy.types.Operator):
@@ -590,6 +591,7 @@ class AddCostValue(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
parent = self.file.by_id(self.parent)
if self.cost_type == "FIXED": if self.cost_type == "FIXED":
category = None category = None
attributes = {"AppliedValue": 0.0} attributes = {"AppliedValue": 0.0}
@@ -599,14 +601,17 @@ class AddCostValue(bpy.types.Operator):
elif self.cost_type == "CATEGORY": elif self.cost_type == "CATEGORY":
category = self.cost_category category = self.cost_category
attributes = {"Category": 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) 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"} return {"FINISHED"}
class RemoveCostItemValue(bpy.types.Operator): 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_label = "Add Cost Item Value"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.IntProperty() parent: bpy.props.IntProperty()
@@ -617,13 +622,17 @@ class RemoveCostItemValue(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
parent = self.file.by_id(self.parent)
ifcopenshell.api.run( ifcopenshell.api.run(
"cost.remove_cost_item_value", "cost.remove_cost_value",
self.file, self.file,
parent=self.file.by_id(self.parent), parent=parent,
cost_value=self.file.by_id(self.cost_value), 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"} return {"FINISHED"}
@@ -636,7 +645,7 @@ class EnableEditingCostItemValue(bpy.types.Operator):
def execute(self, context): def execute(self, context):
self.props = context.scene.BIMCostProperties self.props = context.scene.BIMCostProperties
self.props.cost_value_attributes.clear() 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" self.props.cost_value_editing_type = "ATTRIBUTES"
data = Data.cost_values[self.cost_value] data = Data.cost_values[self.cost_value]
@@ -706,7 +715,7 @@ class DisableEditingCostItemValue(bpy.types.Operator):
def execute(self, context): def execute(self, context):
props = context.scene.BIMCostProperties props = context.scene.BIMCostProperties
props.active_cost_item_value_id = 0 props.active_cost_value_id = 0
props.cost_value_editing_type = "" props.cost_value_editing_type = ""
return {"FINISHED"} return {"FINISHED"}
@@ -720,13 +729,13 @@ class EnableEditingCostItemValueFormula(bpy.types.Operator):
def execute(self, context): def execute(self, context):
self.props = context.scene.BIMCostProperties self.props = context.scene.BIMCostProperties
self.props.cost_value_attributes.clear() 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_editing_type = "FORMULA"
self.props.cost_value_formula = Data.cost_values[self.cost_value]["Formula"] self.props.cost_value_formula = Data.cost_values[self.cost_value]["Formula"]
return {"FINISHED"} return {"FINISHED"}
class EditCostValueFormula(bpy.types.Operator): class EditCostItemValueFormula(bpy.types.Operator):
bl_idname = "bim.edit_cost_value_formula" bl_idname = "bim.edit_cost_value_formula"
bl_label = "Edit Cost Value Formula" bl_label = "Edit Cost Value Formula"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -748,7 +757,7 @@ class EditCostValueFormula(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class EditCostValue(bpy.types.Operator): class EditCostItemValue(bpy.types.Operator):
bl_idname = "bim.edit_cost_value" bl_idname = "bim.edit_cost_value"
bl_label = "Edit Cost Value" bl_label = "Edit Cost Value"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -244,7 +244,7 @@ class BIMCostProperties(PropertyGroup):
name="Cost Types", name="Cost Types",
) )
cost_category: StringProperty(name="Cost Category") 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_editing_type: StringProperty(name="Cost Value Editing Type")
cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute) cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute)
cost_value_formula: StringProperty(name="Cost Value Formula") cost_value_formula: StringProperty(name="Cost Value Formula")
+13 -13
View File
@@ -16,10 +16,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
import blenderbim.bim.module.cost.prop as CostProp import blenderbim.bim.module.cost.prop as CostProp
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes
from ifcopenshell.api.cost.data import Data from ifcopenshell.api.cost.data import Data
from ifcopenshell.api.unit.data import Data as UnitData 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") self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index")
def draw_editable_cost_schedule_ui(self): 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): def draw_editable_cost_item_ui(self, cost_schedule_id):
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -140,7 +140,7 @@ class BIM_PT_cost_schedules(Panel):
self.draw_editable_cost_item_values_ui() self.draw_editable_cost_item_values_ui()
def draw_editable_cost_item_attributes_ui(self): 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): def draw_editable_cost_item_quantities_ui(self):
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -175,7 +175,7 @@ class BIM_PT_cost_schedules(Panel):
self.draw_editable_cost_item_quantity_ui(box) self.draw_editable_cost_item_quantity_ui(box)
def draw_editable_cost_item_quantity_ui(self, layout): 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): def draw_editable_cost_item_values_ui(self):
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -194,12 +194,12 @@ class BIM_PT_cost_schedules(Panel):
if self.props.cost_value_editing_type == "ATTRIBUTES": if self.props.cost_value_editing_type == "ATTRIBUTES":
box = self.layout.box() 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): def draw_readonly_cost_value_ui(self, layout, cost_value_id):
cost_value = Data.cost_values[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="") layout.prop(self.props, "cost_value_formula", text="")
else: else:
cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"]) 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) 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): 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": 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 op.cost_value = cost_value_id
elif self.props.cost_value_editing_type == "FORMULA": 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 op.cost_value = cost_value_id
layout.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL") layout.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL")
elif self.props.active_cost_item_value_id: elif self.props.active_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.parent = parent_id
op.cost_value = cost_value_id op.cost_value = cost_value_id
else: else:
@@ -226,12 +226,12 @@ class BIM_PT_cost_schedules(Panel):
op.cost_value = cost_value_id op.cost_value = cost_value_id
op = layout.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL") op = layout.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL")
op.cost_value = cost_value_id 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.parent = parent_id
op.cost_value = cost_value_id op.cost_value = cost_value_id
def draw_editable_cost_value_ui(self, layout, cost_value): 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): class BIM_PT_cost_item_types(Panel):
@@ -34,8 +34,14 @@ classes = (
operator.AssignResource, operator.AssignResource,
operator.UnassignResource, operator.UnassignResource,
operator.EnableEditingResourceTime, operator.EnableEditingResourceTime,
operator.EnableEditingResourceCosts,
operator.EnableEditingResourceCostValueFormula,
operator.EnableEditingResourceCostValue,
operator.EditResourceTime, operator.EditResourceTime,
operator.EditResourceCostValue,
operator.EditResourceCostValueFormula,
operator.DisableEditingResourceTime, operator.DisableEditingResourceTime,
operator.DisableEditingResourceCostValue,
operator.CalculateResourceWork, operator.CalculateResourceWork,
prop.Resource, prop.Resource,
prop.BIMResourceProperties, prop.BIMResourceProperties,
@@ -1,4 +1,3 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on # BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -19,14 +18,15 @@
import bpy import bpy
import json import json
import time
import isodate
import ifcopenshell.api import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.resource.data import Data
import blenderbim.bim.helper import blenderbim.bim.helper
import blenderbim.bim.module.sequence.helper as helper import blenderbim.bim.module.sequence.helper as helper
import time
from datetime import datetime 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): class LoadResources(bpy.types.Operator):
@@ -95,7 +95,6 @@ class EnableEditingResource(bpy.types.Operator):
new.enum_value = data[attribute.name()] new.enum_value = data[attribute.name()]
class LoadResourceProperties(bpy.types.Operator): class LoadResourceProperties(bpy.types.Operator):
bl_idname = "bim.load_resource_properties" bl_idname = "bim.load_resource_properties"
bl_label = "Load Resource Properties" bl_label = "Load Resource Properties"
@@ -301,7 +300,9 @@ class EnableEditingResourceTime(bpy.types.Operator):
data = Data.resource_times[resource_time_id] 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_time_id = resource_time_id
props.active_resource_id = self.resource props.active_resource_id = self.resource
props.editing_resource_type = "USAGE" props.editing_resource_type = "USAGE"
@@ -319,7 +320,9 @@ class EnableEditingResourceTime(bpy.types.Operator):
return True return True
def add_resource_time(self): 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) Data.load(self.file)
return resource_time return resource_time
@@ -345,7 +348,9 @@ class EditResourceTime(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
self.props = context.scene.BIMResourceProperties 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() self.file = IfcStore.get_file()
ifcopenshell.api.run( ifcopenshell.api.run(
@@ -365,7 +370,7 @@ class EditResourceTime(bpy.types.Operator):
return True return True
attributes[prop.name] = helper.parse_datetime(prop.string_value) attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True return True
elif prop.name =="LevelingDelay" or "Work" in prop.name: elif prop.name == "LevelingDelay" or "Work" in prop.name:
if prop.is_null: if prop.is_null:
attributes[prop.name] = None attributes[prop.name] = None
return True return True
@@ -385,3 +390,186 @@ class CalculateResourceWork(bpy.types.Operator):
Data.load(self.file) Data.load(self.file)
bpy.ops.bim.load_resources() bpy.ops.bim.load_resources()
return {"FINISHED"} 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
@@ -74,3 +74,16 @@ class BIMResourceProperties(PropertyGroup):
active_resource_time_id: IntProperty(name="Active Resource Usage Id") active_resource_time_id: IntProperty(name="Active Resource Usage Id")
resource_time_attributes: CollectionProperty(name="Resource Usage Attributes", type=Attribute) resource_time_attributes: CollectionProperty(name="Resource Usage Attributes", type=Attribute)
editing_resource_type: StringProperty(name="Editing Resource Type") 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")
@@ -16,10 +16,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.resource.data import Data from ifcopenshell.api.resource.data import Data
import blenderbim.bim.helper
class BIM_PT_resources(Panel): class BIM_PT_resources(Panel):
@@ -61,9 +61,11 @@ class BIM_PT_resources(Panel):
self.props, self.props,
"active_resource_index", "active_resource_index",
) )
if self.props.active_resource_id and self.props.editing_resource_type == "ATTRIBUTES": if self.props.active_resource_id and self.props.editing_resource_type == "ATTRIBUTES":
self.draw_editable_resource_attributes_ui() 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": elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE":
self.draw_editable_resource_time_attributes_ui() 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": 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.edit_resource_time", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_resource_time", text="", icon="CANCEL") 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: elif self.props.active_resource_id:
row.operator("bim.add_resource", text="", icon="ADD").resource = ifc_definition_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 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 = row.operator("bim.calculate_resource_work", text="", icon="TEMP")
op.resource = ifc_definition_id op.resource = ifc_definition_id
row.operator("bim.enable_editing_resource_time", text="", icon="TIME").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.enable_editing_resource", text="", icon="GREASEPENCIL").resource = ifc_definition_id
row.operator("bim.remove_resource", text="", icon="X").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): def draw_editable_resource_time_attributes_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.resource_time_attributes, self.layout) 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): class BIM_UL_resources(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
@@ -11,6 +11,10 @@ class Usecase:
values = list(self.settings["parent"].CostValues or []) values = list(self.settings["parent"].CostValues or [])
values.append(value) values.append(value)
self.settings["parent"].CostValues = values 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"): elif self.settings["parent"].is_a("IfcCostValue"):
values = list(self.settings["parent"].Components or []) values = list(self.settings["parent"].Components or [])
values.append(value) values.append(value)
@@ -3,7 +3,137 @@ import ifcopenshell.util.unit
import ifcopenshell.util.cost 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 is_loaded = False
cost_schedules = {} cost_schedules = {}
cost_items = {} cost_items = {}
@@ -63,7 +193,7 @@ class Data:
parametric_quantities.extend(quantities) parametric_quantities.extend(quantities)
cls.cost_items[cost_item.id()] = data cls.cost_items[cost_item.id()] = data
cls.load_cost_item_quantities(cost_item, data, parametric_quantities) 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 cls.is_loaded = True
@classmethod @classmethod
@@ -104,122 +234,3 @@ class Data:
else: else:
data["Unit"] = None data["Unit"] = None
data["UnitSymbol"] = 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"
@@ -13,6 +13,10 @@ class Usecase:
values = list(self.settings["parent"].CostValues) values = list(self.settings["parent"].CostValues)
values.remove(self.settings["cost_value"]) values.remove(self.settings["cost_value"])
self.settings["parent"].CostValues = values if values else None 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"): elif self.settings["parent"].is_a("IfcCostValue"):
components = list(self.settings["parent"].Components) components = list(self.settings["parent"].Components)
components.remove(self.settings["cost_value"]) components.remove(self.settings["cost_value"])
@@ -1,17 +1,20 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.date import ifcopenshell.util.date
from ifcopenshell.api.cost.data import CostValueTrait
class Data: class Data(CostValueTrait):
is_loaded = False is_loaded = False
resources = {} resources = {}
resource_times = {} resource_times = {}
cost_values = {}
@classmethod @classmethod
def purge(cls): def purge(cls):
cls.is_loaded = False cls.is_loaded = False
cls.resources = {} cls.resources = {}
cls.resource_times = {} cls.resource_times = {}
cls.cost_values = {}
@classmethod @classmethod
def load(cls, file): def load(cls, file):
@@ -25,6 +28,7 @@ class Data:
@classmethod @classmethod
def load_resources(cls): def load_resources(cls):
cls.resources = {} cls.resources = {}
cls.cost_values = {}
for resource in cls._file.by_type("IfcResource"): for resource in cls._file.by_type("IfcResource"):
data = resource.get_info() data = resource.get_info()
del data["OwnerHistory"] del data["OwnerHistory"]
@@ -40,6 +44,12 @@ class Data:
data["HasContext"] = resource.HasContext[0].RelatingContext.id() if resource.HasContext else None data["HasContext"] = resource.HasContext[0].RelatingContext.id() if resource.HasContext else None
if resource.Usage: if resource.Usage:
data["Usage"] = data["Usage"].id() 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 cls.resources[resource.id()] = data
@classmethod @classmethod