mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +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": {
|
"Qto_CableCarrierSegmentBaseQuantities": {
|
||||||
"CrossSectionArea": null,
|
"CrossSectionArea": null,
|
||||||
"GrossWeight": null,
|
"GrossWeight": null,
|
||||||
"Length": "net_get_max_xyz",
|
"Length": "net_get_segment_length",
|
||||||
"OuterSurfaceArea": null
|
"OuterSurfaceArea": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"Qto_CableSegmentBaseQuantities": {
|
"Qto_CableSegmentBaseQuantities": {
|
||||||
"CrossSectionArea": null,
|
"CrossSectionArea": null,
|
||||||
"GrossWeight": null,
|
"GrossWeight": null,
|
||||||
"Length": "net_get_max_xyz",
|
"Length": "net_get_segment_length",
|
||||||
"OuterSurfaceArea": null
|
"OuterSurfaceArea": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"Qto_DuctSegmentBaseQuantities": {
|
"Qto_DuctSegmentBaseQuantities": {
|
||||||
"GrossCrossSectionArea": null,
|
"GrossCrossSectionArea": null,
|
||||||
"GrossWeight": null,
|
"GrossWeight": null,
|
||||||
"Length": "net_get_max_xyz",
|
"Length": "net_get_segment_length",
|
||||||
"NetCrossSectionArea": null,
|
"NetCrossSectionArea": null,
|
||||||
"OuterSurfaceArea": null
|
"OuterSurfaceArea": null
|
||||||
}
|
}
|
||||||
@@ -421,7 +421,7 @@
|
|||||||
"Qto_PipeSegmentBaseQuantities": {
|
"Qto_PipeSegmentBaseQuantities": {
|
||||||
"GrossCrossSectionArea": null,
|
"GrossCrossSectionArea": null,
|
||||||
"GrossWeight": null,
|
"GrossWeight": null,
|
||||||
"Length": "net_get_max_xyz",
|
"Length": "net_get_segment_length",
|
||||||
"NetCrossSectionArea": null,
|
"NetCrossSectionArea": null,
|
||||||
"NetWeight": null,
|
"NetWeight": null,
|
||||||
"OuterSurfaceArea": null
|
"OuterSurfaceArea": null
|
||||||
|
|||||||
+52
-16
@@ -21,9 +21,10 @@ import json
|
|||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
import ifcopenshell.api.pset
|
import ifcopenshell.api.pset
|
||||||
import ifcopenshell.util.element
|
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.selector
|
import ifcopenshell.util.selector
|
||||||
|
import ifcopenshell.util.representation
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -106,6 +107,7 @@ class IfcOpenShell:
|
|||||||
"Footprint Perimeter",
|
"Footprint Perimeter",
|
||||||
"The perimeter if the object's faces were projected along the Z-axis and seen top down",
|
"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
|
# IfcAreaMeasure
|
||||||
"get_area": Function("IfcAreaMeasure", "Area", "The total surface area of the element"),
|
"get_area": Function("IfcAreaMeasure", "Area", "The total surface area of the element"),
|
||||||
"get_footprint_area": Function(
|
"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"gross_{k}"] = Function(v.measure, f"Gross {v.name}", v.description)
|
||||||
functions[f"net_{k}"] = Function(v.measure, f"Net {v.name}", v.description)
|
functions[f"net_{k}"] = Function(v.measure, f"Net {v.name}", v.description)
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def calculate(
|
def calculate(
|
||||||
|
cls,
|
||||||
ifc_file: ifcopenshell.file,
|
ifc_file: ifcopenshell.file,
|
||||||
elements: set[ifcopenshell.entity_instance],
|
elements: set[ifcopenshell.entity_instance],
|
||||||
qtos: dict,
|
qtos: dict,
|
||||||
@@ -150,9 +153,10 @@ class IfcOpenShell:
|
|||||||
|
|
||||||
formula_functions = {}
|
formula_functions = {}
|
||||||
|
|
||||||
gross_settings = ifcopenshell.geom.settings()
|
cls.gross_settings = ifcopenshell.geom.settings()
|
||||||
gross_settings.set("disable-opening-subtractions", True)
|
cls.gross_settings.set("disable-opening-subtractions", True)
|
||||||
net_settings = ifcopenshell.geom.settings()
|
cls.net_settings = ifcopenshell.geom.settings()
|
||||||
|
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||||
|
|
||||||
gross_qtos = {}
|
gross_qtos = {}
|
||||||
net_qtos = {}
|
net_qtos = {}
|
||||||
@@ -161,24 +165,27 @@ class IfcOpenShell:
|
|||||||
for quantity, formula in quantities.items():
|
for quantity, formula in quantities.items():
|
||||||
if not formula:
|
if not formula:
|
||||||
continue
|
continue
|
||||||
if formula.startswith("gross_"):
|
gross_or_net_qtos = gross_qtos if formula.startswith("gross_") else net_qtos
|
||||||
formula = formula[6:]
|
if formula.endswith("get_segment_length"):
|
||||||
gross_qtos.setdefault(name, {})[quantity] = formula
|
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)
|
formula_functions[formula] = getattr(ifcopenshell.util.shape, formula)
|
||||||
elif formula.startswith("net_"):
|
elif formula.startswith("net_"):
|
||||||
formula = formula[4:]
|
formula = formula.partition("_")[2]
|
||||||
net_qtos.setdefault(name, {})[quantity] = formula
|
gross_or_net_qtos.setdefault(name, {})[quantity] = formula
|
||||||
formula_functions[formula] = getattr(ifcopenshell.util.shape, formula)
|
formula_functions[formula] = getattr(ifcopenshell.util.shape, formula)
|
||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
|
|
||||||
if gross_qtos:
|
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:
|
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:
|
for iterator, qtos in tasks:
|
||||||
if iterator.initialize():
|
if iterator.initialize():
|
||||||
@@ -189,9 +196,13 @@ 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] = unit_converter.convert(
|
if formula == "get_segment_length":
|
||||||
formula_functions[formula](shape.geometry), IfcOpenShell.raw_functions[formula].measure
|
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():
|
if not iterator.next():
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -201,6 +212,31 @@ 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)
|
||||||
|
|
||||||
|
@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:
|
class Blender:
|
||||||
"""Calculates geometry based on currently loaded Blender objects."""
|
"""Calculates geometry based on currently loaded Blender objects."""
|
||||||
|
|||||||
Reference in New Issue
Block a user