Merge remote-tracking branch 'origin/v0.8.0' into tfk-rocksdb-storage

This commit is contained in:
Thomas Krijnen
2025-09-10 10:06:01 +02:00
181 changed files with 12075 additions and 2648 deletions
@@ -21,6 +21,7 @@ import numpy as np
import ifcopenshell
from typing import Any, Union
from dataclasses import dataclass
from ifcopenshell.util.shape_builder import ShapeBuilder
@dataclass
@@ -78,9 +79,7 @@ class Clipping:
if not ifc_file:
ifc_file = first_operand.file
location = ifc_file.createIfcCartesianPoint([i / unit_scale for i in self.location])
direction = ifc_file.createIfcDirection(self.normal)
builder = ShapeBuilder(ifc_file)
normal = np.array(self.normal)
if np.allclose(normal, np.array([0.0, 0.0, 1.0]), atol=1e-2) or np.allclose(
@@ -92,9 +91,9 @@ class Clipping:
x_axis = np.cross(normal, arbitrary_vector)
x_axis /= np.linalg.norm(x_axis)
x_axis = ifc_file.createIfcDirection(x_axis.tolist())
plane = ifc_file.createIfcPlane(ifc_file.createIfcAxis2Placement3D(location, direction, x_axis))
placement = builder.create_axis2_placement_3d([i / unit_scale for i in self.location], self.normal, x_axis)
plane = ifc_file.create_entity("IfcPlane", placement)
second_operand = ifc_file.createIfcHalfSpaceSolid(plane, False)
return ifc_file.createIfcBooleanClippingResult("DIFFERENCE", first_operand, second_operand)
@@ -1169,6 +1169,25 @@ def get_groups(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entit
return groups
def get_controls(element: ifcopenshell.entity_instance) -> Generator[ifcopenshell.entity_instance]:
"""
Retrieves the controls of an element.
:param element: The IFC element
:return: Generator of IfcControl elements assigned to the element.
Example:
.. code:: python
task = file.by_type("IfcTask")[0]
control = ifcopenshell.util.element.get_controls(task)[0]
"""
for rel in element.HasAssignments:
if rel.is_a("IfcRelAssignsToControl"):
yield rel.RelatingControl
def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
"""Get the parent in the spatial heirarchy
@@ -23,6 +23,7 @@ import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
from typing import NamedTuple, Optional, Union
from decimal import Decimal, ROUND_HALF_UP
MatrixType = ifcopenshell.util.placement.MatrixType
@@ -39,39 +40,54 @@ class HelmertTransformation(NamedTuple):
factor_z: float
def dms2dd(degrees: int, minutes: int, seconds: int, ms: int = 0) -> float:
"""Convert degrees, minutes, and (milli)seconds to decimal degrees
def dms2dd(degrees: int, minutes: int, seconds: int, us: int = 0) -> float:
"""Convert degrees, minutes, and (micro)seconds to decimal degrees
All components must be either positive or negative.
:param degrees: The degrees component
:param minutes: The minutes component
:param seconds: The seconds component
:param ms: The milliseconds component
:param us: The microseconds component
:return: The angle in decimal degrees.
"""
dd = float(degrees) + float(minutes) / 60.0 + float(seconds) / (3600.0) + float(ms / 3600000000.0)
return dd
all_positive_or_zero = degrees >= 0 and minutes >= 0 and seconds >= 0 and us >= 0
all_negative_or_zero = degrees <= 0 and minutes <= 0 and seconds <= 0 and us <= 0
assert all_positive_or_zero or all_negative_or_zero
return degrees + minutes / 60.0 + seconds / 3600.0 + us / 3600000000.0
def dd2dms(dd: float, use_ms: bool = False) -> Union[tuple[float, float, float, float], tuple[float, float, float]]:
"""Convert decimal degrees to degrees, minutes, and (milli)seconds format
def dd2dms(dd: float, use_us: bool = False) -> Union[tuple[int, int, int, int], tuple[int, int, float]]:
"""Convert decimal degrees to degrees, minutes, and (micro)seconds format
:param dd: The decimal degrees
:param use_ms: True if to include milliseconds and false otherwise. Defaults to false.
:return: The angle in a tuple of either 3 or 4 values, being degrees,
minutes, seconds, and optionally milliseconds.
:param use_us: True if to include microseconds and false otherwise. Defaults to false.
:return: The angle in a tuple of either 3 or 4 values,
4 values: integer number of degrees, integer number of minutes, integer number of seconds and integer number of microseconds
3 values: integer number of degrees, integer number of minutes, and a float number for seconds
:note: the tuple follows the format of IfcCompoundPlaneAngleMeasure. Namely all of its components are either positive or negative.
"""
dd = float(dd)
sign = 1 if dd >= 0 else -1
dd = abs(dd)
if use_ms:
seconds, ms = divmod(dd * 60 * 60 * 1000000, 1000000)
minutes, seconds = divmod(dd * 60 * 60, 60)
degrees, minutes = divmod(minutes, 60)
if dd < 0:
degrees = -degrees
if use_ms:
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign, int(ms) * sign)
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign)
dd_decimal = Decimal(str(dd))
degrees = int(dd_decimal)
degrees_decimal = Decimal(degrees)
fractional_part = dd_decimal - degrees_decimal
minutes_decimal = fractional_part * Decimal(60)
minutes = int(minutes_decimal)
minutes_decimal_int = Decimal(minutes)
seconds_decimal = (minutes_decimal - minutes_decimal_int) * Decimal(60)
if use_us:
seconds = int(seconds_decimal)
seconds_decimal_int = Decimal(seconds)
microseconds_decimal = (seconds_decimal - seconds_decimal_int) * Decimal(1000000)
microseconds = int(microseconds_decimal.quantize(Decimal(1), rounding=ROUND_HALF_UP))
return (degrees, minutes, seconds, microseconds)
else:
seconds_float = float(seconds_decimal)
return (degrees, minutes, seconds_float)
def xyz2enh(
@@ -692,7 +692,8 @@ def calculate_unit_scale(ifc_file: ifcopenshell.file, unit_type: str = "LENGTHUN
:returns: The scale factor
"""
if (
unit_type
type(ifc_file) is ifcopenshell.file
and unit_type
not in ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier)
.declaration_by_name("IfcUnitEnum")
.enumeration_items()