mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 18:16:40 +00:00
bim.export_resources #5760
Simple export for resources to csv file, supports just the same fields that import currently does. Location - https://i.imgur.com/Ssz7BwZ.png
This commit is contained in:
@@ -43,6 +43,7 @@ classes = (
|
||||
operator.EnableEditingResourceBaseQuantity,
|
||||
operator.EnableEditingResourceCosts,
|
||||
operator.EnableEditingResourceCostValue,
|
||||
operator.ExportResources,
|
||||
operator.CalculateResourceUsage,
|
||||
operator.EnableEditingResourceCostValueFormula,
|
||||
operator.EnableEditingResourceQuantity,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
from bonsai.bim.module.resource.ui import draw_productivity_ui
|
||||
import bonsai.core.resource as core
|
||||
import bonsai.tool as tool
|
||||
@@ -334,6 +334,7 @@ class EditResourceQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "bim.import_resources"
|
||||
bl_label = "Import Resources"
|
||||
bl_description = "Import resources from a .csv file"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||
@@ -351,6 +352,24 @@ class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
self.report({"INFO"}, f"{resources_after - resources_before} resources are imported.")
|
||||
|
||||
|
||||
class ExportResources(bpy.types.Operator, tool.Ifc.Operator, ExportHelper):
|
||||
bl_idname = "bim.export_resources"
|
||||
bl_label = "Export Resources"
|
||||
bl_description = "Export resources to a .csv file"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
return ifc_file is not None
|
||||
|
||||
def _execute(self, context):
|
||||
core.export_resources(tool.Resource, file_path=self.filepath)
|
||||
self.report({"INFO"}, f"Resources are exported to {self.filepath}")
|
||||
|
||||
|
||||
class AddProductivityData(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_productivity_data"
|
||||
bl_label = "Add Productivity"
|
||||
|
||||
@@ -54,6 +54,7 @@ class BIM_PT_resources(Panel):
|
||||
else:
|
||||
row.operator("bim.load_resources", text="", icon="GREASEPENCIL")
|
||||
row.operator("bim.import_resources", text="", icon="IMPORT")
|
||||
row.operator("bim.export_resources", text="", icon="EXPORT")
|
||||
if not self.props.is_editing:
|
||||
return
|
||||
|
||||
|
||||
@@ -171,6 +171,10 @@ def import_resources(resource_tool: tool.Resource, file_path: str) -> None:
|
||||
resource_tool.load_resources()
|
||||
|
||||
|
||||
def export_resources(resource_tool: tool.Resource, file_path: str) -> None:
|
||||
resource_tool.export_resources(file_path)
|
||||
|
||||
|
||||
def expand_resource(resource_tool: tool.Resource, resource: ifcopenshell.entity_instance) -> None:
|
||||
resource_tool.expand_resource(resource)
|
||||
resource_tool.load_resources()
|
||||
|
||||
@@ -304,8 +304,17 @@ class Resource(bonsai.core.tool.Resource):
|
||||
from ifc4d.csv2ifc import Csv2Ifc
|
||||
|
||||
start = time.time()
|
||||
p62ifc = Csv2Ifc(file_path, tool.Ifc.get())
|
||||
p62ifc.execute()
|
||||
csv2ifc = Csv2Ifc(file_path, tool.Ifc.get())
|
||||
csv2ifc.execute()
|
||||
print("Importing Resources CSV finished in {:.2f} seconds".format(time.time() - start))
|
||||
|
||||
@classmethod
|
||||
def export_resources(cls, file_path: str) -> None:
|
||||
from ifc4d.csv2ifc import Ifc2Csv
|
||||
|
||||
start = time.time()
|
||||
csv2ifc = Ifc2Csv(file_path, tool.Ifc.get())
|
||||
csv2ifc.execute()
|
||||
print("Importing Resources CSV finished in {:.2f} seconds".format(time.time() - start))
|
||||
|
||||
@classmethod
|
||||
|
||||
+100
-10
@@ -16,13 +16,18 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcResources. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import csv
|
||||
import _csv
|
||||
import isodate
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.resource
|
||||
import ifcopenshell.util.unit
|
||||
import datetime
|
||||
from typing import Any, Optional, Union, Literal, get_args
|
||||
from typing import Any, Optional, Union, Literal, get_args, NamedTuple, cast
|
||||
|
||||
|
||||
SUPPORTED_COLUMN = Literal[
|
||||
@@ -36,6 +41,15 @@ SUPPORTED_COLUMN = Literal[
|
||||
"EQUIPMENT OUTPUT",
|
||||
]
|
||||
|
||||
RESOURCE_MAP = {
|
||||
"CREW": "IfcCrewResource",
|
||||
"LABOR": "IfcLaborResource",
|
||||
"EQUIPMENT": "IfcConstructionEquipmentResource",
|
||||
"SUBCONTRACTOR": "IfcSubContractResource",
|
||||
"MATERIAL": "IfcConstructionMaterialResource",
|
||||
"PRODUCT": "IfcConstructionProductResource",
|
||||
}
|
||||
|
||||
|
||||
class Csv2Ifc:
|
||||
"""Class to import resources from a CSV file into an IFC file.
|
||||
@@ -64,14 +78,6 @@ class Csv2Ifc:
|
||||
self.file = ifc_file
|
||||
self.resources: list[dict[str, Any]] = []
|
||||
self.units = {} # TODO: never used
|
||||
self.resource_map = {
|
||||
"CREW": "IfcCrewResource",
|
||||
"LABOR": "IfcLaborResource",
|
||||
"EQUIPMENT": "IfcConstructionEquipmentResource",
|
||||
"SUBCONTRACTOR": "IfcSubContractResource",
|
||||
"MATERIAL": "IfcConstructionMaterialResource",
|
||||
"PRODUCT": "IfcConstructionProductResource",
|
||||
}
|
||||
|
||||
def execute(self) -> None:
|
||||
self.parse_csv()
|
||||
@@ -114,7 +120,7 @@ class Csv2Ifc:
|
||||
|
||||
def get_row_resource_data(self, row: list[str]) -> dict[str, Any]:
|
||||
name = row[self.headers["ACTIVITY/RESOURCE NAME"]]
|
||||
resource_class = self.resource_map[row[self.headers["TYPE"]]]
|
||||
resource_class = RESOURCE_MAP[row[self.headers["TYPE"]]]
|
||||
base_cost_value = row[self.headers["COST"]]
|
||||
productivity = {}
|
||||
|
||||
@@ -194,3 +200,87 @@ class Csv2Ifc:
|
||||
self.file = ifcopenshell.file(schema="IFC4")
|
||||
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||
ifcopenshell.api.run("unit.assign_unit", self.file)
|
||||
|
||||
|
||||
class Ifc2CsvRow(NamedTuple):
|
||||
hierarchy: int
|
||||
resource_type: str
|
||||
name: str
|
||||
description: str
|
||||
cost: Union[float, None]
|
||||
quantity_name: Union[str, None]
|
||||
labor_output: Union[float, None]
|
||||
equipment_output: Union[float, None]
|
||||
|
||||
|
||||
class Ifc2Csv:
|
||||
"""Class to export resources from an IFC file to csv file."""
|
||||
|
||||
HEADER = (
|
||||
"HIERARCHY",
|
||||
"TYPE",
|
||||
"ACTIVITY/RESOURCE NAME",
|
||||
"DESCRIPTION",
|
||||
"COST",
|
||||
"QUANTITY NAME",
|
||||
"LABOR OUTPUT",
|
||||
"EQUIPMENT OUTPUT",
|
||||
"GUID",
|
||||
)
|
||||
assert len(HEADER) == len(Ifc2CsvRow._fields)
|
||||
|
||||
def __init__(self, filepath: str, ifc_file: ifcopenshell.file):
|
||||
self.filepath = filepath
|
||||
self.file = ifc_file
|
||||
self.inverse_resource_map = {value: key for key, value in RESOURCE_MAP.items()}
|
||||
|
||||
def execute(self) -> None:
|
||||
# TODO: unused columns: USAGE, UNIT, PRODUCTIVITY UNIT.
|
||||
with open(self.filepath, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(self.HEADER)
|
||||
for resource in self.file.by_type("IfcConstructionResource"):
|
||||
if resource.Nests:
|
||||
continue
|
||||
self.process_recourse(writer, resource, 1)
|
||||
|
||||
def process_recourse(
|
||||
self, writer: _csv._writer, resource: ifcopenshell.entity_instance, hierarchy_level: int
|
||||
) -> None:
|
||||
row = self.get_row(resource, hierarchy_level)
|
||||
writer.writerow(row)
|
||||
resources = ifcopenshell.util.resource.get_nested_resources(resource)
|
||||
for resource in resources:
|
||||
self.process_recourse(writer, resource, hierarchy_level + 1)
|
||||
if resources:
|
||||
writer.writerow((None,) * len(self.HEADER))
|
||||
|
||||
def get_row(self, resource: ifcopenshell.entity_instance, hierarchy_level: int) -> Ifc2CsvRow:
|
||||
resource_class = resource.is_a()
|
||||
cost, unit = ifcopenshell.util.resource.get_cost(resource)
|
||||
|
||||
quantity_name = None
|
||||
labor_output, equipment_output = None, None
|
||||
if resource_class in ("IfcConstructionEquipmentResource", "IfcLaborResource"):
|
||||
pset: dict[str, Any] = ifcopenshell.util.element.get_pset(resource, "EPset_Productivity")
|
||||
if pset:
|
||||
quantity_name = cast(str, pset["BaseQuantityProducedName"])
|
||||
time_consumed: float = isodate.parse_duration(pset["BaseQuantityConsumed"]).total_seconds() / 3600
|
||||
produced: float = pset["BaseQuantityProducedValue"]
|
||||
output = time_consumed / produced
|
||||
if resource_class == "IfcLaborResource":
|
||||
labor_output = output
|
||||
else:
|
||||
equipment_output, labor_output = None, output
|
||||
|
||||
return Ifc2CsvRow(
|
||||
hierarchy=hierarchy_level,
|
||||
resource_type=self.inverse_resource_map[resource.is_a()],
|
||||
name=resource.Name,
|
||||
description=resource.Description,
|
||||
cost=cost,
|
||||
quantity_name=quantity_name,
|
||||
labor_output=labor_output,
|
||||
equipment_output=equipment_output,
|
||||
guid=resource.GlobalId,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user