mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 14:33:28 +00:00
Added relative CSV file paths
This commit is contained in:
@@ -541,6 +541,7 @@ class SelectCostScheduleProducts(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.import_cost_schedule_csv"
|
bl_idname = "bim.import_cost_schedule_csv"
|
||||||
bl_label = "Import Cost Schedule CSV"
|
bl_label = "Import Cost Schedule CSV"
|
||||||
@@ -549,6 +550,11 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator)
|
|||||||
filename_ext = ".csv"
|
filename_ext = ".csv"
|
||||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||||
is_schedule_of_rates: bpy.props.BoolProperty(name="Is Schedule Of Rates", default=False)
|
is_schedule_of_rates: bpy.props.BoolProperty(name="Is Schedule Of Rates", default=False)
|
||||||
|
use_relative_path: bpy.props.BoolProperty(
|
||||||
|
name="Use Relative Path",
|
||||||
|
description="Store the CSV filepath relative to the currently opened IFC file",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@@ -559,9 +565,28 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator)
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
cost_schedule = core.import_cost_schedule_csv(tool.Cost, self.filepath, self.is_schedule_of_rates)
|
from pathlib import Path
|
||||||
core.add_csv_filepath(tool.Cost, self.filepath, self.is_schedule_of_rates, cost_schedule)
|
|
||||||
return {"FINISHED"}
|
store_path = self.filepath
|
||||||
|
if self.use_relative_path:
|
||||||
|
store_path = tool.Ifc.get_uri(self.filepath, use_relative_path=True)
|
||||||
|
|
||||||
|
resolved_path = Path(tool.Ifc.resolve_uri(self.filepath))
|
||||||
|
if not resolved_path.exists():
|
||||||
|
self.report({"ERROR"}, f"File does not exist: '{store_path}' (resolved to '{resolved_path}')")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
cost_schedule = core.import_cost_schedule_csv(tool.Cost, str(resolved_path), self.is_schedule_of_rates)
|
||||||
|
if cost_schedule:
|
||||||
|
core.add_csv_filepath(tool.Cost, store_path, self.is_schedule_of_rates, cost_schedule)
|
||||||
|
return {"FINISHED"}
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
row = self.layout.row()
|
||||||
|
row.prop(self, "is_schedule_of_rates")
|
||||||
|
row = self.layout.row()
|
||||||
|
row.prop(self, "use_relative_path")
|
||||||
|
|
||||||
|
|
||||||
class RefreshCostScheduleCsv(bpy.types.Operator, tool.Ifc.Operator):
|
class RefreshCostScheduleCsv(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
@@ -576,19 +601,40 @@ class RefreshCostScheduleCsv(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if not props.active_cost_schedule_id:
|
if not props.active_cost_schedule_id:
|
||||||
cls.poll_message_set("No active cost schedule")
|
cls.poll_message_set("No active cost schedule")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
filepath = tool.Cost.get_cost_schedule_csv_filepath(props.active_cost_schedule_id)
|
|
||||||
if not filepath:
|
|
||||||
cls.poll_message_set("No CSV file associated with this cost schedule")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
props = tool.Cost.get_cost_props()
|
||||||
|
cost_schedule_id = props.active_cost_schedule_id
|
||||||
|
|
||||||
|
file_path = tool.Cost.get_cost_schedule_csv_filepath(cost_schedule_id)
|
||||||
|
resolved_path = Path(tool.Ifc.resolve_uri(file_path))
|
||||||
|
if not resolved_path.exists():
|
||||||
|
self.report({"ERROR"}, f"File does not exist: '{file_path}' (resolved to '{resolved_path}')")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
tool.Cost.delete_all_cost_items()
|
tool.Cost.delete_all_cost_items()
|
||||||
tool.Cost.refresh_cost_schedule_csv()
|
|
||||||
tool.Cost.load_cost_schedule_tree()
|
cost_schedule = tool.Ifc.get_entity_by_id(cost_schedule_id)
|
||||||
return {"FINISHED"}
|
is_schedule_of_rates = tool.Cost.is_schedule_of_rates_csv(cost_schedule_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ifc5d.csv2ifc import Csv2Ifc
|
||||||
|
|
||||||
|
csv2ifc = Csv2Ifc()
|
||||||
|
csv2ifc.csv = str(resolved_path)
|
||||||
|
csv2ifc.file = tool.Ifc.get()
|
||||||
|
csv2ifc.cost_schedule = cost_schedule
|
||||||
|
csv2ifc.is_schedule_of_rates = is_schedule_of_rates
|
||||||
|
csv2ifc.refresh()
|
||||||
|
|
||||||
|
tool.Cost.load_cost_schedule_tree()
|
||||||
|
return {"FINISHED"}
|
||||||
|
except Exception as e:
|
||||||
|
self.report({"ERROR"}, f"Error refreshing CSV: {str(e)}")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
|
||||||
class AddCostColumn(bpy.types.Operator):
|
class AddCostColumn(bpy.types.Operator):
|
||||||
|
|||||||
@@ -77,8 +77,26 @@ class BIM_PT_cost_schedules(Panel):
|
|||||||
col.label(text="Linked CSV:")
|
col.label(text="Linked CSV:")
|
||||||
row_1 = col.row(align=True)
|
row_1 = col.row(align=True)
|
||||||
|
|
||||||
# Get filepath from document reference instead of props
|
ifc_file = tool.Ifc.get()
|
||||||
file_path = tool.Cost.get_cost_schedule_csv_filepath(self.props.active_cost_schedule_id)
|
cost_docs_document = next(
|
||||||
|
(
|
||||||
|
document
|
||||||
|
for document in ifc_file.by_type("IfcDocumentInformation")
|
||||||
|
if document.Name == "BBIM_Cost_Documents"
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
file_path = None
|
||||||
|
if cost_docs_document:
|
||||||
|
references = tool.Document.get_document_references(cost_docs_document)
|
||||||
|
for reference in references:
|
||||||
|
if (
|
||||||
|
reference.Description
|
||||||
|
and f"Cost Schedule ID: {self.props.active_cost_schedule_id}" in reference.Description
|
||||||
|
):
|
||||||
|
file_path = reference.Location
|
||||||
|
break
|
||||||
|
|
||||||
if file_path:
|
if file_path:
|
||||||
row_1.label(text=file_path)
|
row_1.label(text=file_path)
|
||||||
|
|||||||
@@ -349,6 +349,7 @@ def import_cost_schedule_csv(
|
|||||||
def add_csv_filepath(cost: type[tool.Cost], file_path: str, is_schedule_of_rates: bool, cost_schedule) -> None:
|
def add_csv_filepath(cost: type[tool.Cost], file_path: str, is_schedule_of_rates: bool, cost_schedule) -> None:
|
||||||
cost.add_csv_filepath(file_path, is_schedule_of_rates, cost_schedule)
|
cost.add_csv_filepath(file_path, is_schedule_of_rates, cost_schedule)
|
||||||
|
|
||||||
|
|
||||||
def remove_csv_filepath(cost: type[tool.Cost], cost_schedule) -> None:
|
def remove_csv_filepath(cost: type[tool.Cost], cost_schedule) -> None:
|
||||||
cost.remove_csv_filepath(cost_schedule)
|
cost.remove_csv_filepath(cost_schedule)
|
||||||
|
|
||||||
|
|||||||
@@ -584,14 +584,16 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
csv2ifc = Csv2Ifc(file_path, tool.Ifc.get(), is_schedule_of_rates=is_schedule_of_rates)
|
|
||||||
|
resolved_path = tool.Ifc.resolve_uri(file_path)
|
||||||
|
|
||||||
|
csv2ifc = Csv2Ifc(resolved_path, tool.Ifc.get(), is_schedule_of_rates=is_schedule_of_rates)
|
||||||
csv2ifc.execute()
|
csv2ifc.execute()
|
||||||
print("Import finished in {:.2f} seconds".format(time.time() - start))
|
print("Import finished in {:.2f} seconds".format(time.time() - start))
|
||||||
return csv2ifc.cost_schedule
|
return csv2ifc.cost_schedule
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_csv_filepath(cls, file_path: str, is_schedule_of_rates: bool, cost_schedule) -> None:
|
def add_csv_filepath(cls, file_path: str, is_schedule_of_rates: bool, cost_schedule) -> None:
|
||||||
"""Store CSV filepath as a document reference in the IFC file."""
|
|
||||||
if not file_path or not cost_schedule:
|
if not file_path or not cost_schedule:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -610,14 +612,11 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
cost_docs_document.Name = "BBIM_Cost_Documents"
|
cost_docs_document.Name = "BBIM_Cost_Documents"
|
||||||
cost_docs_document.Description = "Bonsai internal document containing references to cost CSV files"
|
cost_docs_document.Description = "Bonsai internal document containing references to cost CSV files"
|
||||||
|
|
||||||
# Create a reference with the filepath
|
|
||||||
reference = ifcopenshell.api.document.add_reference(ifc_file, cost_docs_document)
|
reference = ifcopenshell.api.document.add_reference(ifc_file, cost_docs_document)
|
||||||
reference.Location = file_path
|
reference.Location = file_path
|
||||||
|
|
||||||
# Store the cost schedule ID in the reference description
|
|
||||||
reference.Description = f"Cost Schedule ID: {cost_schedule.id()}"
|
reference.Description = f"Cost Schedule ID: {cost_schedule.id()}"
|
||||||
|
|
||||||
# If it's a schedule of rates, store that info too
|
|
||||||
if is_schedule_of_rates:
|
if is_schedule_of_rates:
|
||||||
reference.Identification = "SCHEDULE_OF_RATES"
|
reference.Identification = "SCHEDULE_OF_RATES"
|
||||||
else:
|
else:
|
||||||
@@ -625,7 +624,6 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove_csv_filepath(cls, cost_schedule: ifcopenshell.entity_instance = None) -> None:
|
def remove_csv_filepath(cls, cost_schedule: ifcopenshell.entity_instance = None) -> None:
|
||||||
"""Remove CSV filepath reference from IFC file."""
|
|
||||||
if not cost_schedule:
|
if not cost_schedule:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -646,7 +644,6 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
references = tool.Document.get_document_references(cost_docs_document)
|
references = tool.Document.get_document_references(cost_docs_document)
|
||||||
|
|
||||||
for reference in references:
|
for reference in references:
|
||||||
# Check if this reference is for the given cost schedule
|
|
||||||
if reference.Description and f"Cost Schedule ID: {cost_schedule_id}" in reference.Description:
|
if reference.Description and f"Cost Schedule ID: {cost_schedule_id}" in reference.Description:
|
||||||
ifcopenshell.api.document.remove_reference(ifc_file, reference)
|
ifcopenshell.api.document.remove_reference(ifc_file, reference)
|
||||||
print(f"Cost schedule id={cost_schedule_id} csv filepath correctly removed")
|
print(f"Cost schedule id={cost_schedule_id} csv filepath correctly removed")
|
||||||
@@ -687,7 +684,6 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_cost_schedule_csv_filepath(cls, cost_schedule_id: int) -> Optional[str]:
|
def get_cost_schedule_csv_filepath(cls, cost_schedule_id: int) -> Optional[str]:
|
||||||
"""Get CSV filepath for a cost schedule from document references."""
|
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
cost_docs_document = next(
|
cost_docs_document = next(
|
||||||
(
|
(
|
||||||
@@ -709,11 +705,11 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def refresh_cost_schedule_csv(cls):
|
def refresh_cost_schedule_csv(cls):
|
||||||
"""Refresh cost schedule from CSV file stored in document references."""
|
"""Refresh cost schedule from CSV file stored in document references."""
|
||||||
from ifc5d.csv2ifc import Csv2Ifc
|
from ifc5d.csv2ifc import Csv2Ifc
|
||||||
|
import os
|
||||||
|
|
||||||
props = cls.get_cost_props()
|
props = cls.get_cost_props()
|
||||||
cost_schedule_id = props.active_cost_schedule_id
|
cost_schedule_id = props.active_cost_schedule_id
|
||||||
@@ -722,11 +718,13 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
if not file_path:
|
if not file_path:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
resolved_path = tool.Ifc.resolve_uri(file_path)
|
||||||
|
|
||||||
cost_schedule = tool.Ifc.get_entity_by_id(cost_schedule_id)
|
cost_schedule = tool.Ifc.get_entity_by_id(cost_schedule_id)
|
||||||
is_schedule_of_rates = cls.is_schedule_of_rates_csv(cost_schedule_id)
|
is_schedule_of_rates = cls.is_schedule_of_rates_csv(cost_schedule_id)
|
||||||
|
|
||||||
csv2ifc = Csv2Ifc()
|
csv2ifc = Csv2Ifc()
|
||||||
csv2ifc.csv = file_path
|
csv2ifc.csv = resolved_path
|
||||||
csv2ifc.file = tool.Ifc.get()
|
csv2ifc.file = tool.Ifc.get()
|
||||||
csv2ifc.cost_schedule = cost_schedule
|
csv2ifc.cost_schedule = cost_schedule
|
||||||
csv2ifc.is_schedule_of_rates = is_schedule_of_rates
|
csv2ifc.is_schedule_of_rates = is_schedule_of_rates
|
||||||
@@ -1120,42 +1118,4 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
return document
|
return document
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_csv_filepath(
|
|
||||||
cls,
|
|
||||||
file_path: Optional[str] = None,
|
|
||||||
is_schedule_of_rates: bool = False,
|
|
||||||
cost_schedule: ifcopenshell.entity_instance = None,
|
|
||||||
) -> None:
|
|
||||||
"""Store CSV filepath as a document reference in the IFC file."""
|
|
||||||
if not file_path or not cost_schedule:
|
|
||||||
return
|
|
||||||
|
|
||||||
ifc_file = tool.Ifc.get()
|
|
||||||
cost_docs_document = next(
|
|
||||||
(
|
|
||||||
document
|
|
||||||
for document in ifc_file.by_type("IfcDocumentInformation")
|
|
||||||
if document.Name == "BBIM_Cost_Documents"
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not cost_docs_document:
|
|
||||||
cost_docs_document = ifcopenshell.api.document.add_information(ifc_file)
|
|
||||||
cost_docs_document.Name = "BBIM_Cost_Documents"
|
|
||||||
cost_docs_document.Description = "Bonsai internal document containing references to cost CSV files"
|
|
||||||
|
|
||||||
# Create a reference with the filepath
|
|
||||||
reference = ifcopenshell.api.document.add_reference(ifc_file, cost_docs_document)
|
|
||||||
reference.Location = file_path
|
|
||||||
|
|
||||||
# Store the cost schedule ID in the reference description
|
|
||||||
reference.Description = f"Cost Schedule ID: {cost_schedule.id()}"
|
|
||||||
|
|
||||||
# If it's a schedule of rates, store that info too
|
|
||||||
if is_schedule_of_rates:
|
|
||||||
reference.Identification = "SCHEDULE_OF_RATES"
|
|
||||||
else:
|
|
||||||
reference.Identification = "COST_SCHEDULE"
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user