New qto tool is now unit aware

This commit is contained in:
Dion Moult
2024-05-29 21:46:23 +10:00
parent f92a2b3c8e
commit 435ce8d58a
3 changed files with 126 additions and 83 deletions
@@ -298,7 +298,6 @@ class WorkSchedulePsetsData(Data):
@classmethod @classmethod
def load(cls): def load(cls):
props = bpy.context.scene.WorkSchedulePsetProperties
ifc_definition_id = bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id ifc_definition_id = bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id
cls.data = {"psets": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), psets_only=True)} cls.data = {"psets": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), psets_only=True)}
cls.is_loaded = True cls.is_loaded = True
@@ -49,8 +49,13 @@ def get_calculator(self, context):
def get_calculator_function(self, context): def get_calculator_function(self, context):
calculator = ifc5d.qto.calculators[self.calculator] calculator = ifc5d.qto.calculators[self.calculator]
results = [] results = []
for function in calculator.get_functions(): previous_measure = None
results.append((function.id, function.name, function.description)) for function_id, function in calculator.functions.items():
measure = function.measure.split("Measure")[0][3:]
if previous_measure is not None and measure != previous_measure:
results.append(None)
results.append((function_id, f"{measure}: {function.name}", function.description))
previous_measure = measure
return results return results
+119 -80
View File
@@ -21,13 +21,13 @@ import json
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.pset import ifcopenshell.api.pset
import ifcopenshell.util.unit
import ifcopenshell.util.selector import ifcopenshell.util.selector
import multiprocessing import multiprocessing
from collections import namedtuple from collections import namedtuple
from typing import Iterable
Function = namedtuple("Function", ["id", "name", "description"]) Function = namedtuple("Function", ["measure", "name", "description"])
rules = {} rules = {}
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
@@ -58,10 +58,77 @@ def edit_qtos(ifc_file, results) -> None:
ifcopenshell.api.pset.edit_qto(ifc_file, qto=qto, properties=quantities) ifcopenshell.api.pset.edit_qto(ifc_file, qto=qto, properties=quantities)
class SI2ProjectUnitConverter:
def __init__(self, ifc_file):
self.project_units = {
"IfcAreaMeasure": ifcopenshell.util.unit.get_project_unit(ifc_file, "AREAUNIT"),
"IfcLengthMeasure": ifcopenshell.util.unit.get_project_unit(ifc_file, "LENGTHUNIT"),
"IfcMassMeasure": ifcopenshell.util.unit.get_project_unit(ifc_file, "MASSUNIT"),
"IfcTimeMeasure": ifcopenshell.util.unit.get_project_unit(ifc_file, "TIMEUNIT"),
"IfcVolumeMeasure": ifcopenshell.util.unit.get_project_unit(ifc_file, "VOLUMEUNIT"),
}
for key, value in self.project_units.items():
if value:
self.project_units[key] = (getattr(value, "Prefix", "None"), value.Name)
self.si_names = {
"IfcAreaMeasure": "SQUARE_METRE",
"IfcLengthMeasure": "METRE",
"IfcMassMeasure": "GRAM",
"IfcTimeMeasure": "SECOND",
"IfcVolumeMeasure": "CUBIE_METRE",
}
def convert(self, value, measure):
if measure_unit := self.project_units.get(measure, None):
return ifcopenshell.util.unit.convert(value, None, self.si_names[measure], *measure_unit)
return value
class IfcOpenShell: class IfcOpenShell:
"""Calculates Model body context geometry using the default IfcOpenShell """Calculates Model body context geometry using the default IfcOpenShell
iterator on triangulation elements.""" iterator on triangulation elements."""
functions = {
# IfcLengthMeasure
"get_x": Function("IfcLengthMeasure", "X", "Calculates the length along the local X axis"),
"get_y": Function("IfcLengthMeasure", "Y", "Calculates the length along the local Y axis"),
"get_z": Function("IfcLengthMeasure", "Z", "Calculates the length along the local Z axis"),
"get_max_xyz": Function("IfcLengthMeasure", "Max XYZ", "The maximum X, Y, or Z local dimension"),
"get_min_xyz": Function("IfcLengthMeasure", "Min XYZ", "The minimum X, Y, or Z local dimension"),
"get_top_elevation": Function("IfcLengthMeasure", "Top elevation", "The local maximum Z ordinate"),
"get_bottom_elevation": Function("IfcLengthMeasure", "Bottom Elevation", "The local minimum Z ordinate"),
"get_footprint_perimeter": Function(
"IfcLengthMeasure",
"Footprint Perimeter",
"The perimeter if the object's faces were projected along the Z-axis and seen top down",
),
# IfcAreaMeasure
"get_area": Function("IfcAreaMeasure", "Area", "The total surface area of the element"),
"get_footprint_area": Function(
"IfcAreaMeasure",
"Footprint Area",
"The area if the object's faces were projected along the Z-axis and seen top down",
),
"get_max_side_area": Function(
"IfcAreaMeasure",
"Max Side Area",
"The maximum side area when seen from either X, Y, or Z directions",
),
"get_outer_surface_area": Function(
"IfcAreaMeasure",
"Outer Surface Area",
"The total surface area except for the top or bottom, such as the ends of columns or beams",
),
"get_side_area": Function(
"IfcAreaMeasure",
"Side area",
"The side (non-projected) are of the shape as seen from the local Y-axis",
),
# IfcVolumeMeasure
"get_volume": Function("IfcVolumeMeasure", "Volume", "Calculates the volume of a manifold shape"),
}
@staticmethod @staticmethod
def calculate( def calculate(
ifc_file: ifcopenshell.file, ifc_file: ifcopenshell.file,
@@ -103,6 +170,8 @@ class IfcOpenShell:
if net_qtos: if net_qtos:
tasks.append((IfcOpenShell.create_iterator(ifc_file, net_settings, list(elements)), net_qtos)) tasks.append((IfcOpenShell.create_iterator(ifc_file, net_settings, list(elements)), net_qtos))
unit_converter = SI2ProjectUnitConverter(ifc_file)
for iterator, qtos in tasks: for iterator, qtos in tasks:
if iterator.initialize(): if iterator.initialize():
while True: while True:
@@ -112,7 +181,9 @@ class IfcOpenShell:
for name, quantities in qtos.items(): for name, quantities in qtos.items():
results[element].setdefault(name, {}) results[element].setdefault(name, {})
for quantity, formula in quantities.items(): for quantity, formula in quantities.items():
results[element][name][quantity] = formula_functions[formula](shape.geometry) results[element][name][quantity] = unit_converter.convert(
formula_functions[formula](shape.geometry), IfcOpenShell.functions[formula].measure
)
if not iterator.next(): if not iterator.next():
break break
@@ -122,47 +193,51 @@ class IfcOpenShell:
) -> ifcopenshell.geom.iterator: ) -> ifcopenshell.geom.iterator:
return ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements) return ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements)
@staticmethod
def get_functions() -> list[Function]:
return [
Function("get_area", "Area", "The total surface area of the element"),
Function("get_bottom_elevation", "Bottom Elevation", "The local minimum Z ordinate"),
Function(
"get_footprint_area",
"Footprint Area",
"The area if the object's faces were projected along the Z-axis and seen top down",
),
Function(
"get_footprint_perimeter",
"Footprint Perimeter",
"The perimeter if the object's faces were projected along the Z-axis and seen top down",
),
Function(
"get_max_side_area",
"Max Side Area",
"The maximum side area when seen from either X, Y, or Z directions",
),
Function("get_max_xyz", "Max XYZ", "The maximum X, Y, or Z local dimension"),
Function("get_min_xyz", "Min XYZ", "The minimum X, Y, or Z local dimension"),
Function(
"get_outer_surface_area",
"Outer Surface Area",
"The total surface area except for the top or bottom, such as the ends of columns or beams",
),
Function(
"get_side_area", "Side area", "The side (non-projected) are of the shape as seen from the local Y-axis"
),
Function("get_top_elevation", "Top elevation", "The local maximum Z ordinate"),
Function("get_volume", "Volume", "Calculates the volume of a manifold shape"),
Function("get_x", "X", "Calculates the length along the local X axis"),
Function("get_y", "Y", "Calculates the length along the local Y axis"),
Function("get_z", "Z", "Calculates the length along the local Z axis"),
]
class Blender: class Blender:
"""Calculates geometry based on currently loaded Blender objects.""" """Calculates geometry based on currently loaded Blender objects."""
functions = {
# IfcLengthMeasure
"get_covering_width": Function("IfcLengthMeasure", "Covering Width", ""),
"get_finish_ceiling_height": Function("IfcLengthMeasure", "Finish Ceiling Height", ""),
"get_finish_floor_height": Function("IfcLengthMeasure", "Finish Floor Height", ""),
"get_gross_perimeter": Function("IfcLengthMeasure", "Gross Perimeter", ""),
"get_height": Function("IfcLengthMeasure", "Height", ""),
"get_length": Function("IfcLengthMeasure", "Length", ""),
"get_opening_depth": Function("IfcLengthMeasure", "Opening Depth", ""),
"get_opening_height": Function("IfcLengthMeasure", "Opening Height", ""),
"get_rectangular_perimeter": Function("IfcLengthMeasure", "Rectangular Perimeter", ""),
"get_stair_length": Function("IfcLengthMeasure", "Stair Length", ""),
"get_width": Function("IfcLengthMeasure", "Width", ""),
# IfcAreaMeasure
"get_covering_gross_area": Function("IfcAreaMeasure", "Covering Gross Area", ""),
"get_covering_net_area": Function("IfcAreaMeasure", "Covering Net Area", ""),
"get_cross_section_area": Function("IfcAreaMeasure", "Cross Section Area", ""),
"get_gross_ceiling_area": Function("IfcAreaMeasure", "Gross Ceiling Area", ""),
"get_gross_footprint_area": Function("IfcAreaMeasure", "Gross Footprint Area", ""),
"get_gross_side_area": Function("IfcAreaMeasure", "Gross Side Area", ""),
"get_gross_stair_area": Function("IfcAreaMeasure", "Gross Stair Area", ""),
"get_gross_surface_area": Function("IfcAreaMeasure", "Gross Surface Area", ""),
"get_gross_top_area": Function("IfcAreaMeasure", "Gross Top Area", ""),
"get_net_ceiling_area": Function("IfcAreaMeasure", "Net Ceiling Area", ""),
"get_net_floor_area": Function("IfcAreaMeasure", "Net Floor Area", ""),
"get_net_footprint_area": Function("IfcAreaMeasure", "Net Footprint Area", ""),
"get_net_side_area": Function("IfcAreaMeasure", "Net Side Area", ""),
"get_net_stair_area": Function("IfcAreaMeasure", "Net Stair Area", ""),
"get_net_surface_area": Function("IfcAreaMeasure", "Net Surface Area", ""),
"get_net_top_area": Function("IfcAreaMeasure", "Net Top Area", ""),
"get_opening_mapping_area": Function("IfcAreaMeasure", "Opening Mapping Area", ""),
"get_outer_surface_area": Function("IfcAreaMeasure", "Outer Surface Area", ""),
# IfcVolumeMeasure
"get_gross_volume": Function("IfcVolumeMeasure", "Gross Volume", ""),
"get_net_volume": Function("IfcVolumeMeasure", "Net Volume", ""),
"get_space_net_volume": Function("IfcVolumeMeasure", "Space Net Volume", ""),
# IfcMassMeasure
"get_gross_weight": Function("IfcMassMeasure", "Gross Weight", ""),
"get_net_weight": Function("IfcMassMeasure", "Net Weight", ""),
}
@staticmethod @staticmethod
def calculate( def calculate(
ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_instance], qtos: dict, results: dict ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_instance], qtos: dict, results: dict
@@ -170,6 +245,7 @@ class Blender:
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.module.qto.calculator as calculator import blenderbim.bim.module.qto.calculator as calculator
unit_converter = SI2ProjectUnitConverter(ifc_file)
formula_functions = {} formula_functions = {}
for element in elements: for element in elements:
@@ -182,46 +258,9 @@ class Blender:
for quantity, formula in quantities.items(): for quantity, formula in quantities.items():
if not (formula_function := formula_functions.get(formula)): if not (formula_function := formula_functions.get(formula)):
formula_function = formula_functions[formula] = getattr(calculator, formula) formula_function = formula_functions[formula] = getattr(calculator, formula)
results[element][name][quantity] = formula_function(obj) results[element][name][quantity] = unit_converter.convert(
formula_function(obj), Blender.functions[formula].measure
@staticmethod )
def get_functions() -> list[Function]:
return [
Function("get_covering_gross_area", "Covering Gross Area", ""),
Function("get_covering_net_area", "Covering Net Area", ""),
Function("get_covering_width", "Covering Width", ""),
Function("get_cross_section_area", "Cross Section Area", ""),
Function("get_finish_ceiling_height", "Finish Ceiling Height", ""),
Function("get_finish_floor_height", "Finish Floor Height", ""),
Function("get_gross_ceiling_area", "Gross Ceiling Area", ""),
Function("get_gross_footprint_area", "Gross Footprint Area", ""),
Function("get_gross_perimeter", "Gross Perimeter", ""),
Function("get_gross_side_area", "Gross Side Area", ""),
Function("get_gross_stair_area", "Gross Stair Area", ""),
Function("get_gross_surface_area", "Gross Surface Area", ""),
Function("get_gross_top_area", "Gross Top Area", ""),
Function("get_gross_volume", "Gross Volume", ""),
Function("get_gross_weight", "Gross Weight", ""),
Function("get_height", "Height", ""),
Function("get_length", "Length", ""),
Function("get_net_ceiling_area", "Net Ceiling Area", ""),
Function("get_net_floor_area", "Net Floor Area", ""),
Function("get_net_footprint_area", "Net Footprint Area", ""),
Function("get_net_side_area", "Net Side Area", ""),
Function("get_net_stair_area", "Net Stair Area", ""),
Function("get_net_surface_area", "Net Surface Area", ""),
Function("get_net_top_area", "Net Top Area", ""),
Function("get_net_volume", "Net Volume", ""),
Function("get_net_weight", "Net Weight", ""),
Function("get_opening_depth", "Opening Depth", ""),
Function("get_opening_height", "Opening Height", ""),
Function("get_opening_mapping_area", "Opening Mapping Area", ""),
Function("get_outer_surface_area", "Outer Surface Area", ""),
Function("get_rectangular_perimeter", "Rectangular Perimeter", ""),
Function("get_space_net_volume", "Space Net Volume", ""),
Function("get_stair_length", "Stair Length", ""),
Function("get_width", "Width", ""),
]
calculators = {"Blender": Blender, "IfcOpenShell": IfcOpenShell} calculators = {"Blender": Blender, "IfcOpenShell": IfcOpenShell}