mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
You can now export Cost Schedules as CSV, ODS, XSLX with BlenderBIM or using the cli app
This commit is contained in:
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
"""
|
||||
Reference in New Issue
Block a user