mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +00:00
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:
@@ -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
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user