mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 02:23:34 +00:00
Import of CSV-based schedule of rates is now possible
This commit is contained in:
@@ -592,11 +592,21 @@ class EnableEditingCostItemValue(bpy.types.Operator):
|
||||
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"]:
|
||||
name = unit["Name"]
|
||||
if unit.get("Prefix", None):
|
||||
name = f"(unit['Prefix']) {name}"
|
||||
units[unit_id] = f"{unit['UnitType']} / {name}"
|
||||
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"])
|
||||
@@ -719,6 +729,7 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||
is_schedule_of_rates: bpy.props.BoolProperty(name="Is Schedule Of Rates", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
from ifc5d.csv2ifc import Csv2Ifc
|
||||
@@ -728,8 +739,10 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper):
|
||||
csv2ifc = Csv2Ifc()
|
||||
csv2ifc.csv = self.filepath
|
||||
csv2ifc.file = self.file
|
||||
csv2ifc.is_schedule_of_rates = self.is_schedule_of_rates
|
||||
csv2ifc.execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
UnitData.load(IfcStore.get_file())
|
||||
print("Import finished in {:.2f} seconds".format(time.time() - start))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
op.cost_value = cost_value_id
|
||||
|
||||
def draw_editable_cost_value_ui(self, layout, cost_value):
|
||||
draw_attributes(self.props.cost_value_attributes, self.layout)
|
||||
draw_attributes(self.props.cost_value_attributes, layout)
|
||||
|
||||
class BIM_PT_cost_item_quantities(Panel):
|
||||
bl_label = "IFC Cost Item Quantities"
|
||||
|
||||
@@ -24,7 +24,7 @@ class BIM_PT_units(Panel):
|
||||
self.props = context.scene.BIMUnitProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Units Found".format(len(Data.unit_assignment)), icon="SNAP_GRID")
|
||||
row.label(text="{} Units Found".format(len(Data.units)), icon="SNAP_GRID")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.disable_unit_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
|
||||
+43
-12
@@ -28,6 +28,8 @@ class Csv2Ifc:
|
||||
self.file = None
|
||||
self.cost_items = []
|
||||
self.cost_schedule = None
|
||||
self.is_schedule_of_rates = False
|
||||
self.units = {}
|
||||
|
||||
def execute(self):
|
||||
self.parse_csv()
|
||||
@@ -61,8 +63,8 @@ class Csv2Ifc:
|
||||
def get_row_cost_data(self, row):
|
||||
name = row[self.headers["Name"]]
|
||||
identification = row[self.headers["Identification"]] if "Identification" in self.headers else None
|
||||
cost_quantities = row[self.headers["Quantity"]]
|
||||
cost_quantities_unit = row[self.headers["Unit"]]
|
||||
quantity = row[self.headers["Quantity"]]
|
||||
unit = row[self.headers["Unit"]]
|
||||
if self.has_categories:
|
||||
cost_values = {
|
||||
k: float(row[v])
|
||||
@@ -75,8 +77,8 @@ class Csv2Ifc:
|
||||
return {
|
||||
"Identification": str(identification) if identification else None,
|
||||
"Name": str(name) if name else None,
|
||||
"CostQuantities": float(cost_quantities) if cost_quantities else None,
|
||||
"CostQuantitiesUnit": str(cost_quantities_unit) if cost_quantities_unit else None,
|
||||
"Quantity": float(quantity) if quantity else None,
|
||||
"Unit": str(unit) if unit else None,
|
||||
"CostValues": cost_values,
|
||||
"children": [],
|
||||
}
|
||||
@@ -86,6 +88,8 @@ class Csv2Ifc:
|
||||
self.create_boilerplate_ifc()
|
||||
if not self.cost_schedule:
|
||||
self.cost_schedule = ifcopenshell.api.run("cost.add_cost_schedule", self.file, name="CSV Import")
|
||||
if self.is_schedule_of_rates:
|
||||
self.cost_schedule.PredefinedType = "SCHEDULEOFRATES"
|
||||
self.create_cost_items(self.cost_items)
|
||||
|
||||
def create_cost_items(self, cost_items, parent=None):
|
||||
@@ -94,9 +98,7 @@ class Csv2Ifc:
|
||||
|
||||
def create_cost_item(self, cost_item, parent):
|
||||
if parent is None:
|
||||
cost_item["ifc"] = ifcopenshell.api.run(
|
||||
"cost.add_cost_item", self.file, cost_schedule=self.cost_schedule
|
||||
)
|
||||
cost_item["ifc"] = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=self.cost_schedule)
|
||||
else:
|
||||
cost_item["ifc"] = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=parent)
|
||||
|
||||
@@ -104,8 +106,9 @@ class Csv2Ifc:
|
||||
cost_item["ifc"].Identification = cost_item["Identification"]
|
||||
|
||||
if not cost_item["CostValues"]:
|
||||
cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"])
|
||||
cost_value.Category = "*"
|
||||
if not self.is_schedule_of_rates:
|
||||
cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"])
|
||||
cost_value.Category = "*"
|
||||
elif self.has_categories:
|
||||
for category, value in cost_item["CostValues"].items():
|
||||
cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"])
|
||||
@@ -114,14 +117,42 @@ class Csv2Ifc:
|
||||
else:
|
||||
cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"])
|
||||
cost_value.AppliedValue = self.file.createIfcMonetaryMeasure(cost_item["CostValues"])
|
||||
if self.is_schedule_of_rates:
|
||||
measure_class = ifcopenshell.util.unit.get_symbol_measure_class(cost_item["Unit"])
|
||||
value_component = self.file.create_entity(measure_class, cost_item["Quantity"])
|
||||
unit_component = None
|
||||
|
||||
if cost_item["CostQuantities"]:
|
||||
quantity_class = ifcopenshell.util.unit.get_symbol_quantity_class(cost_item["CostQuantitiesUnit"])
|
||||
if measure_class == "IfcNumericMeasure":
|
||||
unit_component = self.create_unit(cost_item["Unit"])
|
||||
else:
|
||||
unit_type = ifcopenshell.util.unit.get_measure_unit_type(measure_class)
|
||||
unit_assignment = ifcopenshell.util.unit.get_unit_assignment(self.file)
|
||||
if unit_assignment:
|
||||
units = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == unit_type]
|
||||
if units:
|
||||
unit_component = units[0]
|
||||
if not unit_component:
|
||||
unit_component = self.create_unit(cost_item["Unit"])
|
||||
|
||||
cost_value.UnitBasis = self.file.createIfcMeasureWithUnit(value_component, unit_component)
|
||||
|
||||
if not self.is_schedule_of_rates and cost_item["Quantity"]:
|
||||
quantity_class = ifcopenshell.util.unit.get_symbol_quantity_class(cost_item["Unit"])
|
||||
quantity = ifcopenshell.api.run(
|
||||
"cost.add_cost_item_quantity", self.file, cost_item=cost_item["ifc"], ifc_class=quantity_class
|
||||
)
|
||||
quantity[3] = cost_item["CostQuantities"]
|
||||
quantity[3] = cost_item["Quantity"]
|
||||
self.create_cost_items(cost_item["children"], cost_item["ifc"])
|
||||
|
||||
def create_unit(self, symbol):
|
||||
unit = self.units.get(symbol, None)
|
||||
if unit:
|
||||
return unit
|
||||
unit = self.file.createIfcContextDependentUnit(
|
||||
self.file.createIfcDimensionalExponents(0, 0, 0, 0, 0, 0, 0), "USERDEFINED", symbol
|
||||
)
|
||||
self.units[symbol] = unit
|
||||
return unit
|
||||
|
||||
def create_boilerplate_ifc(self):
|
||||
self.file = ifcopenshell.file(schema="IFC4")
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
"Hierarchy","Identification","Name","Quantity","Unit","Value"
|
||||
1,1,"Rates",,,
|
||||
,,,,,
|
||||
2,1.1,"Category A",,,
|
||||
,,,,,
|
||||
3,"1.1.1","Cleaning",1,"m",5
|
||||
3,"1.1.2","Hiring",1,"m2",6
|
||||
3,"1.1.3","Building",1,"m3",7
|
||||
,,,,,
|
||||
2,1.2,"Category B",,,
|
||||
,,,,,
|
||||
3,"1.2.1","Cleaning",1,"hr",8
|
||||
3,"1.2.2","Hiring",2,"bob",9
|
||||
3,"1.2.3","Building",3,"kg",10
|
||||
|
@@ -16,18 +16,13 @@ class Usecase:
|
||||
# TODO: support all applied value select types
|
||||
value = self.file.createIfcMonetaryMeasure(value)
|
||||
elif name == "UnitBasis":
|
||||
self.remove_existing_unit_basis()
|
||||
old_unit_basis = self.settings["cost_value"].UnitBasis
|
||||
if value:
|
||||
value_component = self.file.create_entity(
|
||||
ifcopenshell.util.unit.get_unit_measure_type(value["UnitComponent"].UnitType),
|
||||
ifcopenshell.util.unit.get_unit_measure_class(value["UnitComponent"].UnitType),
|
||||
value["ValueComponent"],
|
||||
)
|
||||
value = self.file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"])
|
||||
if old_unit_basis and len(self.file.get_inverse(old_unit_basis)) == 0:
|
||||
ifcopenshell.util.element.remove_deep(self.file, old_unit_basis)
|
||||
setattr(self.settings["cost_value"], name, value)
|
||||
|
||||
def remove_existing_unit_basis(self):
|
||||
if (
|
||||
self.settings["cost_value"].UnitBasis
|
||||
and len(self.file.get_inverse(self.settings["cost_value"].UnitBasis)) == 1
|
||||
):
|
||||
ifcopenshell.util.element.remove_deep(self.file, self.settings["cost_value"].UnitBasis)
|
||||
|
||||
@@ -246,45 +246,79 @@ def get_named_dimensions(name):
|
||||
return named_dimensions.get(name, (0, 0, 0, 0, 0, 0, 0))
|
||||
|
||||
|
||||
def get_unit_assignment(ifc_file):
|
||||
unit_assignments = ifc_file.by_type("IfcUnitAssignment")
|
||||
if unit_assignments:
|
||||
return unit_assignments[0]
|
||||
|
||||
|
||||
def get_property_unit(prop, ifc_file):
|
||||
unit = getattr(prop, "Unit", None)
|
||||
if unit:
|
||||
return unit
|
||||
unit_assignment = ifc_file.by_type("IfcUnitAssignment")
|
||||
unit_assignment = get_unit_assignment(ifc_file)
|
||||
if not unit_assignment:
|
||||
return
|
||||
entity = prop.wrapped_data.declaration().as_entity()
|
||||
if prop.is_a("IfcPhysicalSimpleQuantity"):
|
||||
measure_type = entity.attribute_by_index(3).type_of_attribute().declared_type().name()
|
||||
measure_class = entity.attribute_by_index(3).type_of_attribute().declared_type().name()
|
||||
elif prop.is_a("IfcPropertySingleValue") and prop.NominalValue:
|
||||
measure_type = prop.NominalValue.is_a()
|
||||
for text in ("Ifc", "Measure", "Non", "Positive", "Negative"):
|
||||
measure_type = measure_type.replace(text, "")
|
||||
measure_type = measure_type.upper() + "UNIT"
|
||||
units = [u for u in unit_assignment[0].Units if getattr(u, "UnitType", None) == measure_type]
|
||||
measure_class = prop.NominalValue.is_a()
|
||||
unit_type = get_measure_unit_type(measure_class)
|
||||
units = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == unit_type]
|
||||
if units:
|
||||
return units[0]
|
||||
|
||||
|
||||
def get_unit_measure_type(unit_type):
|
||||
def get_unit_measure_class(unit_type):
|
||||
if unit_type == "USERDEFINED":
|
||||
# See https://github.com/buildingSMART/IFC4.3.x-development/issues/71
|
||||
return "IfcNumericMeasure"
|
||||
return "Ifc" + unit_type[0:-4].lower().capitalize() + "Measure"
|
||||
|
||||
|
||||
def get_measure_unit_type(measure_class):
|
||||
if measure_class == "IfcNumericMeasure":
|
||||
# See https://github.com/buildingSMART/IFC4.3.x-development/issues/71
|
||||
return "USERDEFINED"
|
||||
for text in ("Ifc", "Measure", "Non", "Positive", "Negative"):
|
||||
measure_class = measure_class.replace(text, "")
|
||||
return measure_class.upper() + "UNIT"
|
||||
|
||||
|
||||
def get_symbol_measure_class(symbol):
|
||||
# Dumb, but everybody gets it, unlike regex golf
|
||||
if not symbol:
|
||||
return "IfcNumericMeasure"
|
||||
symbol = symbol.lower()
|
||||
if symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]:
|
||||
return "IfcLengthMeasure"
|
||||
elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]:
|
||||
return "IfcAreaMeasure"
|
||||
elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]:
|
||||
return "IfcVolumeMeasure"
|
||||
elif symbol in ["kg", "g", "mt", "kt", "t"]:
|
||||
return "IfcMassMeasure"
|
||||
elif symbol in ["day", "d", "hour", "hr", "h", "minute", "min", "m", "second", "sec", "s"]:
|
||||
return "IfcTimeMeasure"
|
||||
return "IfcNumericMeasure"
|
||||
|
||||
|
||||
def get_symbol_quantity_class(symbol):
|
||||
# Dumb, but everybody gets it, unlike regex golf
|
||||
if not symbol:
|
||||
return "IfcQuantityCount"
|
||||
symbol = symbol.lower()
|
||||
if symbol in ["kg", "g", "mt", "kt", "t"]:
|
||||
if symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]:
|
||||
return "IfcQuantityLength"
|
||||
elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]:
|
||||
return "IfcQuantityArea"
|
||||
elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]:
|
||||
return "IfcQuantityVolume"
|
||||
elif symbol in ["kg", "g", "mt", "kt", "t"]:
|
||||
return "IfcQuantityWeight"
|
||||
elif symbol in ["day", "d", "hour", "hr", "h", "minute", "min", "m", "second", "sec", "s"]:
|
||||
return "IfcQuantityTime"
|
||||
elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]:
|
||||
return "IfcQuantityVolume"
|
||||
elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]:
|
||||
return "IfcQuantityArea"
|
||||
elif symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]:
|
||||
return "IfcQuantityLength"
|
||||
return "IfcQuantityCount"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user