Improve QTO of segment lengths

Previous method uses bounding box which could be wrong if the app (Revit) doesn't extrude in +Z and has a slope (e.g. most pipes). Also Revit tends to use rectangle profiles not for the cross section, but instead for the footprint which is crazy.
This commit is contained in:
Dion Moult
2024-08-08 15:13:34 +10:00
parent b01e93161e
commit f9c4b6a98f
2 changed files with 56 additions and 20 deletions
+4 -4
View File
@@ -97,7 +97,7 @@
"Qto_CableCarrierSegmentBaseQuantities": {
"CrossSectionArea": null,
"GrossWeight": null,
"Length": "net_get_max_xyz",
"Length": "net_get_segment_length",
"OuterSurfaceArea": null
}
},
@@ -110,7 +110,7 @@
"Qto_CableSegmentBaseQuantities": {
"CrossSectionArea": null,
"GrossWeight": null,
"Length": "net_get_max_xyz",
"Length": "net_get_segment_length",
"OuterSurfaceArea": null
}
},
@@ -236,7 +236,7 @@
"Qto_DuctSegmentBaseQuantities": {
"GrossCrossSectionArea": null,
"GrossWeight": null,
"Length": "net_get_max_xyz",
"Length": "net_get_segment_length",
"NetCrossSectionArea": null,
"OuterSurfaceArea": null
}
@@ -421,7 +421,7 @@
"Qto_PipeSegmentBaseQuantities": {
"GrossCrossSectionArea": null,
"GrossWeight": null,
"Length": "net_get_max_xyz",
"Length": "net_get_segment_length",
"NetCrossSectionArea": null,
"NetWeight": null,
"OuterSurfaceArea": null
+52 -16
View File
@@ -21,9 +21,10 @@ import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.pset
import ifcopenshell.util.element
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.selector
import ifcopenshell.util.representation
import multiprocessing
from collections import namedtuple
from typing import Any
@@ -106,6 +107,7 @@ class IfcOpenShell:
"Footprint Perimeter",
"The perimeter if the object's faces were projected along the Z-axis and seen top down",
),
"get_segment_length": Function("IfcLengthMeasure", "Segment Length", "Intelligently guesses the length of flow segments"),
# IfcAreaMeasure
"get_area": Function("IfcAreaMeasure", "Area", "The total surface area of the element"),
"get_footprint_area": Function(
@@ -137,8 +139,9 @@ class IfcOpenShell:
functions[f"gross_{k}"] = Function(v.measure, f"Gross {v.name}", v.description)
functions[f"net_{k}"] = Function(v.measure, f"Net {v.name}", v.description)
@staticmethod
@classmethod
def calculate(
cls,
ifc_file: ifcopenshell.file,
elements: set[ifcopenshell.entity_instance],
qtos: dict,
@@ -150,9 +153,10 @@ class IfcOpenShell:
formula_functions = {}
gross_settings = ifcopenshell.geom.settings()
gross_settings.set("disable-opening-subtractions", True)
net_settings = ifcopenshell.geom.settings()
cls.gross_settings = ifcopenshell.geom.settings()
cls.gross_settings.set("disable-opening-subtractions", True)
cls.net_settings = ifcopenshell.geom.settings()
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
gross_qtos = {}
net_qtos = {}
@@ -161,24 +165,27 @@ class IfcOpenShell:
for quantity, formula in quantities.items():
if not formula:
continue
if formula.startswith("gross_"):
formula = formula[6:]
gross_qtos.setdefault(name, {})[quantity] = formula
gross_or_net_qtos = gross_qtos if formula.startswith("gross_") else net_qtos
if formula.endswith("get_segment_length"):
gross_or_net_qtos.setdefault(name, {})[quantity] = formula.partition("_")[2]
elif formula.startswith("gross_"):
formula = formula.partition("_")[2]
gross_or_net_qtos.setdefault(name, {})[quantity] = formula
formula_functions[formula] = getattr(ifcopenshell.util.shape, formula)
elif formula.startswith("net_"):
formula = formula[4:]
net_qtos.setdefault(name, {})[quantity] = formula
formula = formula.partition("_")[2]
gross_or_net_qtos.setdefault(name, {})[quantity] = formula
formula_functions[formula] = getattr(ifcopenshell.util.shape, formula)
tasks = []
if gross_qtos:
tasks.append((IfcOpenShell.create_iterator(ifc_file, gross_settings, list(elements)), gross_qtos))
tasks.append((IfcOpenShell.create_iterator(ifc_file, cls.gross_settings, list(elements)), gross_qtos))
if net_qtos:
tasks.append((IfcOpenShell.create_iterator(ifc_file, net_settings, list(elements)), net_qtos))
tasks.append((IfcOpenShell.create_iterator(ifc_file, cls.net_settings, list(elements)), net_qtos))
unit_converter = SI2ProjectUnitConverter(ifc_file)
cls.unit_converter = SI2ProjectUnitConverter(ifc_file)
for iterator, qtos in tasks:
if iterator.initialize():
@@ -189,9 +196,13 @@ class IfcOpenShell:
for name, quantities in qtos.items():
results[element].setdefault(name, {})
for quantity, formula in quantities.items():
results[element][name][quantity] = unit_converter.convert(
formula_functions[formula](shape.geometry), IfcOpenShell.raw_functions[formula].measure
)
if formula == "get_segment_length":
results[element][name][quantity] = cls.get_segment_length(ifc_file, shape)
else:
results[element][name][quantity] = cls.unit_converter.convert(
formula_functions[formula](shape.geometry),
IfcOpenShell.raw_functions[formula].measure,
)
if not iterator.next():
break
@@ -201,6 +212,31 @@ class IfcOpenShell:
) -> ifcopenshell.geom.iterator:
return ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements)
@classmethod
def get_segment_length(cls, ifc_file: ifcopenshell.file, shape) -> float:
element = ifc_file.by_id(shape.id)
rep = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if rep and len(rep.Items or []) == 1 and rep.Items[0].is_a("IfcExtrudedAreaSolid"):
item = rep.Items[0]
if item.SweptArea.is_a("IfcRectangleProfileDef"):
# Revit doesn't follow the +Z extrusion rule, so the rectangle isn't the cross section
x = item.SweptArea.XDim
y = item.SweptArea.YDim
z = item.Depth
return max([x, y, z])
elif item.SweptArea.is_a("IfcCircleProfileDef"):
return item.Depth
elif item.SweptArea.is_a("IfcParameterizedProfileDef"):
return item.Depth
try:
area_shape = ifcopenshell.geom.create_shape(settings, item.SweptArea)
except:
return
x = ifcopenshell.util.shape.get_x(area_shape.geometry) / cls.unit_scale
y = ifcopenshell.util.shape.get_y(area_shape.geometry) / cls.unit_scale
z = item.Depth
return max([x, y, z])
class Blender:
"""Calculates geometry based on currently loaded Blender objects."""