Quantification to consider Pset_ProfileMechanical.MassPerLength #6344

This commit is contained in:
Andrej730
2025-03-17 17:59:00 +05:00
parent 5109168533
commit 0abe6329d6
4 changed files with 103 additions and 13 deletions
+33 -3
View File
@@ -24,6 +24,7 @@ import bonsai.tool as tool
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.util.element
import ifc5d.qto
from mathutils import Vector, Matrix
from mathutils.bvhtree import BVHTree
from shapely.geometry import Polygon
@@ -565,7 +566,7 @@ def has_openings(obj: bpy.types.Object) -> list[ifcopenshell.entity_instance]:
element = tool.Ifc.get_entity(obj)
if not element:
return []
return [o for o in tool.Geometry.get_openings(element)]
return [o for o in ifcopenshell.util.element.get_openings(element)]
def get_obj_decompositions(obj: bpy.types.Object) -> set[ifcopenshell.entity_instance]:
@@ -576,20 +577,42 @@ def get_obj_decompositions(obj: bpy.types.Object) -> set[ifcopenshell.entity_ins
def get_gross_weight(obj: bpy.types.Object) -> Union[float, None]:
"""Get gross weight of the object (based on gross volume and Pset_MaterialCommon.MassDensity)"""
"""Get gross weight of the object.
Based on gross volume and Pset_MaterialCommon.MassDensity
or Pset_ProfileMechanical.MassPerLength and extrusion depth if it's profile based.
"""
weight = get_profile_obj_weight(obj)
if weight is not None:
return weight
obj_mass_density = get_obj_mass_density(obj)
if not obj_mass_density:
return
gross_volume = get_gross_volume(obj)
gross_weight = obj_mass_density * gross_volume
return gross_weight
def get_net_weight(obj: bpy.types.Object) -> Union[float, None]:
"""Get net weight of the object (based on net volume and Pset_MaterialCommon.MassDensity)"""
"""Get net weight of the object.
Based on Pset_ProfileMechanical.MassPerLength and extrusion depth
(for profile based objects, though objects with openings are not supported)
or object's net volume and Pset_MaterialCommon.MassDensity.
"""
if not has_openings(obj):
weight = get_profile_obj_weight(obj)
if weight is not None:
return weight
obj_mass_density = get_obj_mass_density(obj)
if not obj_mass_density:
return
net_volume = get_net_volume(obj)
net_weight = obj_mass_density * net_volume
return net_weight
@@ -602,6 +625,13 @@ def get_obj_mass_density(obj: bpy.types.Object) -> Union[float, None]:
return ifcopenshell.util.element.get_element_mass_density(entity)
def get_profile_obj_weight(obj: bpy.types.Object) -> Union[float, None]:
element = tool.Ifc.get_entity(obj)
assert element
weight = ifc5d.qto.IfcOpenShell.get_weight_profile_based(element)
return weight
def get_opening_type(opening: bpy.types.Object, obj: bpy.types.Object) -> Literal["OPENING", "RECESS"]:
"""_summary_: Returns the opening type - OPENING / RECESS
+4 -6
View File
@@ -1660,15 +1660,13 @@ class Geometry(bonsai.core.tool.Geometry):
Use `.RelatedOpeningElement` to get the opening element.
"""
for element_rel in getattr(element, "HasOpenings", ()):
yield element_rel
if aggregate := ifcopenshell.util.element.get_aggregate(element):
yield from cls.get_openings(aggregate)
# TODO: replace everywhere with util method.
return ifcopenshell.util.element.get_openings(element)
@classmethod
def has_openings(cls, element: ifcopenshell.entity_instance) -> bool:
return bool(next(cls.get_openings(element), False))
# TODO: replace everywhere with util method.
return ifcopenshell.util.element.has_openings(element)
@classmethod
def get_elements_by_representation(
+41 -3
View File
@@ -238,7 +238,9 @@ class IfcOpenShell(QtoCalculator):
"get_weight": Function(
"IfcMassMeasure",
"Weight",
"The weight of the object based on it's volume and material density (from Pset_MaterialCommon.MassDensity).",
"The weight of the object based on it's length and Pset_ProfileMechanical.MassPerLength "
"(for profile based objects, though objects with openings are not supported for net calculations)"
"or it's volume and material density (from Pset_MaterialCommon.MassDensity).",
),
}
@@ -308,7 +310,8 @@ class IfcOpenShell(QtoCalculator):
if value is None:
continue
elif formula == "get_weight":
value = cls.get_weight(element, geometry)
calculation_type = "GROSS" if iterator.settings is cls.gross_settings else "NET"
value = cls.get_weight(element, geometry, calculation_type)
if value is None:
continue
else:
@@ -367,7 +370,10 @@ class IfcOpenShell(QtoCalculator):
@classmethod
def get_weight(
cls, element: ifcopenshell.entity_instance, geometry: ifcopenshell.geom.ShapeType
cls,
element: ifcopenshell.entity_instance,
geometry: ifcopenshell.geom.ShapeType,
calculation_type: Literal["GROSS", "NET"],
) -> Union[float, None]:
"""Get element's weight.
@@ -376,12 +382,44 @@ class IfcOpenShell(QtoCalculator):
or ``None`` if mass density calculation for this element is not supported.
"""
if calculation_type == "gross" or not ifcopenshell.util.element.has_openings(element):
weight = cls.get_weight_profile_based(element)
if weight is not None:
return weight
density = ifcopenshell.util.element.get_element_mass_density(element)
if density is None:
return
volume = ifcopenshell.util.shape.get_volume(geometry)
return volume * density
@classmethod
def get_weight_profile_based(cls, element: ifcopenshell.entity_instance) -> Union[float, None]:
"""Get weight of the profile based element.
:return: A float weight value if calculation was successful
or ``None`` if it's either not profile based object
or it's not supported.
"""
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return None
items = representation.Items
if not all(item.is_a("IfcExtrudedAreaSolid") for item in items):
return None
mass = 0.0
for item in items:
profile = item.SweptArea
# TODO: there are also bunch of other similar props we will need to consider in the future.
# Examples:
# - Pset_CableSegmentTypeBusBarSegment.MassPerLength
# - Pset_CableCarrierSegmentTypeCatenaryWire.MassPerLength
mass_per_length = ifcopenshell.util.element.get_pset(profile, "Pset_ProfileMechanical", "MassPerLength")
if not isinstance(mass_per_length, float):
return None
mass += mass_per_length * item.Depth
return mass
class Blender(QtoCalculator):
"""Calculates geometry based on currently loaded Blender objects."""
@@ -20,7 +20,7 @@ import ifcopenshell
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.representation
from typing import Any, Callable, Optional, Union, Literal, overload, Sequence
from typing import Any, Callable, Optional, Union, Literal, overload, Sequence, Generator
from collections import namedtuple
@@ -1720,3 +1720,27 @@ def has_property(product: ifcopenshell.entity_instance, property_name: str) -> b
return True
qtos = get_psets(product, qtos_only=True)
return any(property_name in quantities.keys() for quantities in qtos.values())
def get_openings(element: ifcopenshell.entity_instance) -> Generator[ifcopenshell.entity_instance, None, None]:
"""Get element openings as IfcRelVoidsElements.
Use `.RelatedOpeningElement` to get the opening element.
:param element: IfcElement.
:return: Generator of IfcRelVoidsElements.
"""
for element_rel in getattr(element, "HasOpenings", ()):
yield element_rel
if aggregate := get_aggregate(element):
yield from get_openings(aggregate)
def has_openings(element: ifcopenshell.entity_instance) -> bool:
"""Check if the element has openings.
:param element: IfcElement.
:return: True if element has openings.
"""
return bool(next(get_openings(element), False))