mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +00:00
Quantification to consider Pset_ProfileMechanical.MassPerLength #6344
This commit is contained in:
@@ -24,6 +24,7 @@ import bonsai.tool as tool
|
|||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
|
import ifc5d.qto
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from mathutils.bvhtree import BVHTree
|
from mathutils.bvhtree import BVHTree
|
||||||
from shapely.geometry import Polygon
|
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)
|
element = tool.Ifc.get_entity(obj)
|
||||||
if not element:
|
if not element:
|
||||||
return []
|
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]:
|
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]:
|
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)
|
obj_mass_density = get_obj_mass_density(obj)
|
||||||
if not obj_mass_density:
|
if not obj_mass_density:
|
||||||
return
|
return
|
||||||
|
|
||||||
gross_volume = get_gross_volume(obj)
|
gross_volume = get_gross_volume(obj)
|
||||||
gross_weight = obj_mass_density * gross_volume
|
gross_weight = obj_mass_density * gross_volume
|
||||||
return gross_weight
|
return gross_weight
|
||||||
|
|
||||||
|
|
||||||
def get_net_weight(obj: bpy.types.Object) -> Union[float, None]:
|
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)
|
obj_mass_density = get_obj_mass_density(obj)
|
||||||
if not obj_mass_density:
|
if not obj_mass_density:
|
||||||
return
|
return
|
||||||
|
|
||||||
net_volume = get_net_volume(obj)
|
net_volume = get_net_volume(obj)
|
||||||
net_weight = obj_mass_density * net_volume
|
net_weight = obj_mass_density * net_volume
|
||||||
return net_weight
|
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)
|
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"]:
|
def get_opening_type(opening: bpy.types.Object, obj: bpy.types.Object) -> Literal["OPENING", "RECESS"]:
|
||||||
"""_summary_: Returns the opening type - OPENING / RECESS
|
"""_summary_: Returns the opening type - OPENING / RECESS
|
||||||
|
|
||||||
|
|||||||
@@ -1660,15 +1660,13 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
|
|
||||||
Use `.RelatedOpeningElement` to get the opening element.
|
Use `.RelatedOpeningElement` to get the opening element.
|
||||||
"""
|
"""
|
||||||
for element_rel in getattr(element, "HasOpenings", ()):
|
# TODO: replace everywhere with util method.
|
||||||
yield element_rel
|
return ifcopenshell.util.element.get_openings(element)
|
||||||
|
|
||||||
if aggregate := ifcopenshell.util.element.get_aggregate(element):
|
|
||||||
yield from cls.get_openings(aggregate)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_openings(cls, element: ifcopenshell.entity_instance) -> bool:
|
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
|
@classmethod
|
||||||
def get_elements_by_representation(
|
def get_elements_by_representation(
|
||||||
|
|||||||
+41
-3
@@ -238,7 +238,9 @@ class IfcOpenShell(QtoCalculator):
|
|||||||
"get_weight": Function(
|
"get_weight": Function(
|
||||||
"IfcMassMeasure",
|
"IfcMassMeasure",
|
||||||
"Weight",
|
"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:
|
if value is None:
|
||||||
continue
|
continue
|
||||||
elif formula == "get_weight":
|
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:
|
if value is None:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -367,7 +370,10 @@ class IfcOpenShell(QtoCalculator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_weight(
|
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]:
|
) -> Union[float, None]:
|
||||||
"""Get element's weight.
|
"""Get element's weight.
|
||||||
|
|
||||||
@@ -376,12 +382,44 @@ class IfcOpenShell(QtoCalculator):
|
|||||||
or ``None`` if mass density calculation for this element is not supported.
|
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)
|
density = ifcopenshell.util.element.get_element_mass_density(element)
|
||||||
if density is None:
|
if density is None:
|
||||||
return
|
return
|
||||||
volume = ifcopenshell.util.shape.get_volume(geometry)
|
volume = ifcopenshell.util.shape.get_volume(geometry)
|
||||||
return volume * density
|
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):
|
class Blender(QtoCalculator):
|
||||||
"""Calculates geometry based on currently loaded Blender objects."""
|
"""Calculates geometry based on currently loaded Blender objects."""
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import ifcopenshell
|
|||||||
import ifcopenshell.guid
|
import ifcopenshell.guid
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.representation
|
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
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
@@ -1720,3 +1720,27 @@ def has_property(product: ifcopenshell.entity_instance, property_name: str) -> b
|
|||||||
return True
|
return True
|
||||||
qtos = get_psets(product, qtos_only=True)
|
qtos = get_psets(product, qtos_only=True)
|
||||||
return any(property_name in quantities.keys() for quantities in qtos.values())
|
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))
|
||||||
|
|||||||
Reference in New Issue
Block a user