You can now export Cost Schedules as CSV, ODS, XSLX with BlenderBIM or using the cli app

This commit is contained in:
Sigma Dimensions
2023-02-15 22:50:39 +01:00
parent 476ab506d0
commit 9dc69f3aa7
10 changed files with 162 additions and 42 deletions
@@ -30,6 +30,7 @@ classes = (
operator.EnableEditingCostSchedule,
operator.EnableEditingCostItems,
operator.EnableEditingCostItem,
operator.ExportCostSchedules,
operator.ExpandCostItems,
operator.EnableEditingCostItemQuantities,
operator.EnableEditingCostItemQuantity,
@@ -21,6 +21,8 @@ import ifcopenshell
import ifcopenshell.util.cost
import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.bim.schema
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
def refresh():
@@ -36,6 +38,8 @@ class CostSchedulesData:
@classmethod
def load(cls):
cls.data = {
"predefined_types": cls.get_cost_schedule_types(),
"total_cost_schedules": cls.total_cost_schedules(),
"schedules": cls.schedules(),
"is_editing_rates": cls.is_editing_rates(),
"cost_items": cls.cost_items(),
@@ -45,6 +49,10 @@ class CostSchedulesData:
}
cls.is_loaded = True
@classmethod
def total_cost_schedules(cls):
return len(tool.Ifc.get().by_type("IfcCostSchedule"))
@classmethod
def schedules(cls):
results = []
@@ -212,6 +220,21 @@ class CostSchedulesData:
for t in tool.Ifc.schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()
]
@classmethod
def get_cost_schedule_types(cls):
results = []
declaration = tool.Ifc().schema().declaration_by_name("IfcCostSchedule")
version = tool.Ifc.get_schema()
for attribute in declaration.attributes():
if attribute.name() == "PredefinedType":
results.extend(
[
(e, e, get_predefined_type_doc(version, "IfcCostSchedule", e))
for e in attribute.type_of_attribute().declared_type().enumeration_items()
]
)
break
return results
class CostItemRatesData:
data = {}
@@ -36,7 +36,7 @@ class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a new cost schedule"
def _execute(self, context):
core.add_cost_schedule(tool.Ifc)
core.add_cost_schedule(tool.Ifc, predefined_type = context.scene.BIMCostProperties.cost_schedule_predefined_types)
class EditCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
@@ -328,7 +328,7 @@ class DisableEditingCostItemQuantity(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Disable editing cost item quantity"
def _execute(self, context):
def execute(self, context):
core.disable_editing_cost_item_quantity(tool.Cost)
return {"FINISHED"}
@@ -586,3 +586,22 @@ class CalculateCostItemResourceValue(bpy.types.Operator, tool.Ifc.Operator):
core.calculate_cost_item_resource_value(tool.Ifc, cost_item=tool.Ifc.get().by_id(self.cost_item))
return {"FINISHED"}
class ExportCostSchedules(bpy.types.Operator):
bl_idname = "bim.export_cost_schedules"
bl_label = "Export Cost Schedule"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file"
format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
def execute(self, context):
core.export_cost_schedules(tool.Cost, format=self.format)
return {"FINISHED"}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
self.layout.label(text="Choose a format")
self.layout.prop(self, "format")
@@ -109,6 +109,12 @@ def update_cost_item_name(self, context):
attribute.string_value = self.name
def get_schedule_predefined_types(self, context):
if not CostSchedulesData.is_loaded:
CostSchedulesData.load()
return CostSchedulesData.data["predefined_types"]
class CostItem(PropertyGroup):
name: StringProperty(name="Name", update=update_cost_item_name)
identification: StringProperty(name="Identification", update=update_cost_item_identification)
@@ -130,6 +136,9 @@ class CostItemType(PropertyGroup):
class BIMCostProperties(PropertyGroup):
cost_schedule_predefined_types: EnumProperty(
items=get_schedule_predefined_types, name="Predefined Type", default=None
)
is_cost_update_enabled: BoolProperty(name="Is Cost Update Enabled", default=True)
cost_schedule_attributes: CollectionProperty(name="Cost Schedule Attributes", type=Attribute)
is_editing: StringProperty(name="Is Editing")
@@ -42,9 +42,16 @@ class BIM_PT_cost_schedules(Panel):
CostSchedulesData.load()
self.props = context.scene.BIMCostProperties
row = self.layout.row()
if CostSchedulesData.data["total_cost_schedules"]:
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
else:
row.label(text="No Cost Schedules Found found.", icon="COMMUNITY")
row = self.layout.row()
row.operator("bim.add_cost_schedule", icon="ADD")
row.prop(self.props, "cost_schedule_predefined_types")
row.operator("bim.add_cost_schedule", icon="ADD", text="Add new")
for schedule in CostSchedulesData.data["schedules"]:
self.draw_cost_schedule_ui(schedule)
@@ -57,7 +64,6 @@ class BIM_PT_cost_schedules(Panel):
op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="")
op.cost_schedule = cost_schedule["id"]
row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY")
row.operator("bim.export_cost_schedule", text="", icon="EXPORT").cost_schedule = cost_schedule["id"]
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
elif self.props.is_editing == "COST_ITEMS":
+6 -3
View File
@@ -1,5 +1,5 @@
def add_cost_schedule(ifc):
ifc.run("cost.add_cost_schedule")
def add_cost_schedule(ifc, predefined_type):
ifc.run("cost.add_cost_schedule", predefined_type=predefined_type)
def edit_cost_schedule(ifc, cost, cost_schedule):
@@ -209,4 +209,7 @@ def contract_cost_item_rate(cost, cost_item):
cost.contract_cost_item_rate(cost_item)
def calculate_cost_item_resource_value(ifc, cost_item):
ifc.run("cost.calculate_cost_item_resource_value", cost_item=cost_item)
ifc.run("cost.calculate_cost_item_resource_value", cost_item=cost_item)
def export_cost_schedules(cost, format):
cost.export_cost_schedules(format)
+1 -1
View File
@@ -176,8 +176,8 @@ class Cost:
def load_cost_schedule_tree(cls): pass
def load_schedule_of_rates_tree(cls, schedule_of_rates): pass
def play_chaching_sound(cls): pass
def print_all_cost_items(cls, cost_schedule): pass
def remove_cost_column(cls, name): pass
def export_cost_schedules(cls, format): pass
@interface
class Debug:
+25 -31
View File
@@ -279,9 +279,7 @@ class Cost(blenderbim.core.tool.Cost):
prop.float_value = (
0.0
if prop.is_null
else ifcopenshell.util.cost.calculate_applied_value(
tool.Ifc.get().by_id(props.active_cost_item_id), cost_value
)
else cls.calculate_applied_value(tool.Ifc.get().by_id(props.active_cost_item_id), cost_value)
)
return True
if name == "UnitBasis" and is_rates:
@@ -299,33 +297,11 @@ class Cost(blenderbim.core.tool.Cost):
prop.name = "UnitBasisUnit"
prop.data_type = "enum"
prop.is_null = prop.is_optional = False
units = {}
def format_unit(unit):
if unit.is_a("IfcContextDependentUnit"):
return f"{unit.UnitType} / {unit.Name}"
else:
name = unit.Name
if unit.get_info().get("Prefix", None):
name = f"{unit.Prefix} {name}"
return f"{unit.UnitType} / {name}"
for unit in tool.Ifc.get().by_type("IfcNamedUnit"):
if unit.get_info().get("UnitType", None) in [
"AREAUNIT",
"LENGTHUNIT",
"TIMEUNIT",
"VOLUMEUNIT",
"MASSUNIT",
"USERDEFINED",
]:
units[unit.id()] = format_unit(unit)
units = cls.get_units()
prop.enum_items = json.dumps(units)
if data["UnitBasis"] and data["UnitBasis"].UnitComponent:
# prop.enum_value = prop.enum_items[data["UnitBasis"].UnitComponent.id()]
print(prop.enum_items)
for key, value in json.loads(prop.enum_items).items():
if value == format_unit(data["UnitBasis"].UnitComponent):
if value == cls.format_unit(data["UnitBasis"].UnitComponent):
prop.enum_value = key
break
return True
@@ -384,7 +360,6 @@ class Cost(blenderbim.core.tool.Cost):
"ValueComponent": prop.float_value or 1,
"UnitComponent": cls.get_cost_value_unit_component(),
}
print(attributes)
return True
if prop.name == "UnitBasisUnit":
return True
@@ -424,9 +399,6 @@ class Cost(blenderbim.core.tool.Cost):
def get_nested_cost_items(cls, cost_item):
return [obj for rel in cost_item.IsNestedBy for obj in rel.RelatedObjects]
@classmethod
def print_all_cost_items(cls, cost_schedule):
print(cls.get_schedule_cost_items(cost_schedule))
@classmethod
def get_schedule_cost_items(cls, cost_schedule):
@@ -526,3 +498,25 @@ class Cost(blenderbim.core.tool.Cost):
for cost_item in rel.RelatedObjects
]
props.is_cost_update_enabled = True
@classmethod
def export_cost_schedules(cls, format=None):
path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", "cost_schedules")
if format == "CSV":
from ifc5d.ifc5Dspreadsheet import Ifc5DCsvWriter
if not os.path.exists(path):
os.makedirs(path)
writer = Ifc5DCsvWriter(file=tool.Ifc.get(), output=path)
writer.write()
elif format == "ODS":
from ifc5d.ifc5Dspreadsheet import Ifc5DOdsWriter
writer = Ifc5DOdsWriter(file=tool.Ifc.get(), output=path)
writer.write()
elif format == "XLSX":
from ifc5d.ifc5Dspreadsheet import Ifc5DXlsxWriter
writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path + ".xlsx")
writer.write()
@@ -379,3 +379,29 @@ Scenario: Select cost item products
And I press "bim.assign_cost_item_quantity(cost_item={cost_item}, related_object_type='PRODUCT', prop_name='')"
When I press "bim.select_cost_item_products(cost_item={cost_item})"
Then nothing happens
Scenario: Select Cost Schedule Products
Given an empty IFC project
And I press "bim.add_cost_schedule"
And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()"
And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})"
And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})"
And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I press "bim.assign_cost_item_quantity(cost_item={cost_item}, related_object_type='PRODUCT', prop_name='')"
When I press "bim.select_cost_schedule_products(cost_schedule={cost_schedule})"
Then nothing happens
Scenario: Load Cost Item Types
Given an empty IFC project
And I press "bim.add_cost_schedule"
And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()"
And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})"
And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})"
And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()"
When I press "bim.add_cost_item(cost_item={cost_item})"
And I press "bim.load_cost_item_types"
Then nothing happens
+42 -3
View File
@@ -1,18 +1,57 @@
# ifc5d
## Description
Ifc5D is a collection of utilities of manipulating cost-related data to and from
formats, reports, and optimisation engines.
Currently supported:
- CSV to IFC
- IFC to XLSX
- IFC to CSV
- IFC to ODS
Planned (would you like to contribute? Please reach out!):
- IFC to CSV
- IFC to PDF
- IFC to ODS
- IFC to XLSX
- ODS to CSV
- XLSX to CSV
- IFC to Graph
## Usage CSV to IFC
## Usage IFC to CSV, ODS, XSLS
### CLI app for converting IFC files to CSV, ODS or XLSX format.
Usage:
python ifc5Dspreadsheet.py input_file output_file [-l log_file] [-f format_type]
Arguments:
input_file (str): The path to the input IFC file to process.
output_file (str): The output directory for CSV or filename for other formats.
Options:
-l, --log log_file (str): The path to the file where errors should be logged. Default is process.log.
-f, --format format_type (str): The output format to export in (csv/ods/xlsx). Default is csv.
Examples:
python ifc5Dspreadsheet.py "C:\Users\Username\Desktop\test_cost.ifc" rev_01_schedule -l error.log -f ods
python ifc5Dspreadsheet.py "C:\Users\Dev-Machine\Desktop\test_cost.ifc" "C:\Users\Dev-Machine\Desktop" -l error.log -f csv
### Scripting:
Example for ODS exports:
"""
import ifcopenshell
from ifc5d.ifc5Dspreadsheet import Ifc5DOdsWriter
file = "path_to_file/file.ifc"
path = "directory/cost_schedule"
writer = Ifc5DOdsWriter(file=file, output=path)
writer.write()
"""