mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
typing
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import importlib
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import ifcpatch
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
|
||||
@@ -18,9 +18,16 @@
|
||||
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
from typing import Optional, Union, TypedDict
|
||||
|
||||
|
||||
def get_context(ifc_file, context, subcontext=None, target_view=None):
|
||||
def get_context(
|
||||
ifc_file: ifcopenshell.file,
|
||||
context: str,
|
||||
subcontext: Optional[str] = None,
|
||||
target_view: Optional[str] = None,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if subcontext or target_view:
|
||||
elements = ifc_file.by_type("IfcGeometricRepresentationSubContext")
|
||||
else:
|
||||
@@ -35,7 +42,12 @@ def get_context(ifc_file, context, subcontext=None, target_view=None):
|
||||
return element
|
||||
|
||||
|
||||
def is_representation_of_context(representation, context, subcontext=None, target_view=None):
|
||||
def is_representation_of_context(
|
||||
representation: ifcopenshell.entity_instance,
|
||||
context: Union[ifcopenshell.entity_instance, str],
|
||||
subcontext: Optional[str] = None,
|
||||
target_view: Optional[str] = None,
|
||||
) -> bool:
|
||||
if isinstance(context, ifcopenshell.entity_instance):
|
||||
return representation.ContextOfItems == context
|
||||
|
||||
@@ -52,11 +64,16 @@ def is_representation_of_context(representation, context, subcontext=None, targe
|
||||
and representation.ContextOfItems.ContextIdentifier == subcontext
|
||||
and representation.ContextOfItems.ContextType == context
|
||||
)
|
||||
elif representation.ContextOfItems.ContextType == context:
|
||||
return True
|
||||
|
||||
return representation.ContextOfItems.ContextType == context
|
||||
|
||||
|
||||
def get_representation(element, context, subcontext=None, target_view=None):
|
||||
def get_representation(
|
||||
element: ifcopenshell.entity_instance,
|
||||
context: Union[ifcopenshell.entity_instance, str],
|
||||
subcontext: Optional[str] = None,
|
||||
target_view: Optional[str] = None,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if element.is_a("IfcProduct") and element.Representation:
|
||||
for r in element.Representation.Representations:
|
||||
if is_representation_of_context(r, context, subcontext, target_view):
|
||||
@@ -67,16 +84,30 @@ def get_representation(element, context, subcontext=None, target_view=None):
|
||||
return r.MappedRepresentation
|
||||
|
||||
|
||||
def resolve_representation(representation):
|
||||
def resolve_representation(representation: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Resolve possibly mapped representation.
|
||||
|
||||
:param representation: IfcRepresentation
|
||||
:type representation: ifcopenshell.entity_instance.entity_instance
|
||||
:return: Representation resolved from mappings
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
"""
|
||||
if len(representation.Items) == 1 and representation.Items[0].is_a("IfcMappedItem"):
|
||||
return resolve_representation(representation.Items[0].MappingSource.MappedRepresentation)
|
||||
return representation
|
||||
|
||||
|
||||
def resolve_items(representation, matrix=None):
|
||||
class ResolvedItemDict(TypedDict):
|
||||
matrix: np.array
|
||||
item: ifcopenshell.entity_instance
|
||||
|
||||
|
||||
def resolve_items(
|
||||
representation: ifcopenshell.entity_instance, matrix: Optional[np.array] = None
|
||||
) -> list[ResolvedItemDict]:
|
||||
if matrix is None:
|
||||
matrix = np.eye(4)
|
||||
results = []
|
||||
results: list[ResolvedItemDict] = []
|
||||
for item in representation.Items or []: # Be forgiving of invalid IFCs because Revit :(
|
||||
if item.is_a("IfcMappedItem"):
|
||||
rep_matrix = ifcopenshell.util.placement.get_mappeditem_transformation(item)
|
||||
@@ -84,5 +115,5 @@ def resolve_items(representation, matrix=None):
|
||||
rep_matrix = rep_matrix @ matrix.copy()
|
||||
results.extend(resolve_items(item.MappingSource.MappedRepresentation, rep_matrix))
|
||||
else:
|
||||
results.append({"matrix": matrix.copy(), "item": item})
|
||||
results.append(ResolvedItemDict(matrix=matrix.copy(), item=item))
|
||||
return results
|
||||
|
||||
@@ -28,6 +28,7 @@ import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.classification
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.shape
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Any, Union
|
||||
|
||||
@@ -373,8 +374,7 @@ def set_element_value(
|
||||
coord_i = "xyz".index(key)
|
||||
prev_value = matrix[coord_i][3]
|
||||
new_value = float(value) if value else 0.0
|
||||
TOLERANCE = 1.0e-5
|
||||
if new_value + TOLERANCE > prev_value > new_value - TOLERANCE:
|
||||
if ifcopenshell.util.shape.is_x(new_value, prev_value):
|
||||
return
|
||||
|
||||
matrix[coord_i][3] = new_value
|
||||
|
||||
@@ -21,11 +21,14 @@ import numpy as np
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
from typing import Optional, Literal, Union, Iterable
|
||||
|
||||
tol = 1e-6
|
||||
AXIS_LITERAL = Union[Literal["X"], Literal["Y"], Literal["Z"]]
|
||||
VECTOR_3D = tuple[float, float, float]
|
||||
|
||||
|
||||
def is_x(value, x, tolerance=None):
|
||||
def is_x(value: float, x: float, tolerance: Optional[float] = None) -> bool:
|
||||
"""Checks whether a value is equivalent to X given a tolerance
|
||||
|
||||
:param value: Input value
|
||||
@@ -42,7 +45,7 @@ def is_x(value, x, tolerance=None):
|
||||
return abs(x - value) < tolerance
|
||||
|
||||
|
||||
def get_volume(geometry):
|
||||
def get_volume(geometry) -> float:
|
||||
"""Calculates the total internal volume of a geometry
|
||||
|
||||
Volumes of non-manifold geometry will be unpredictable.
|
||||
@@ -73,7 +76,7 @@ def get_volume(geometry):
|
||||
return abs(sum(volumes))
|
||||
|
||||
|
||||
def get_x(geometry):
|
||||
def get_x(geometry) -> float:
|
||||
"""Calculates the X length of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
@@ -85,7 +88,7 @@ def get_x(geometry):
|
||||
return max(x_values) - min(x_values)
|
||||
|
||||
|
||||
def get_y(geometry):
|
||||
def get_y(geometry) -> float:
|
||||
"""Calculates the Y length of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
@@ -97,7 +100,7 @@ def get_y(geometry):
|
||||
return max(y_values) - min(y_values)
|
||||
|
||||
|
||||
def get_z(geometry):
|
||||
def get_z(geometry) -> float:
|
||||
"""Calculates the Z length of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
@@ -109,7 +112,7 @@ def get_z(geometry):
|
||||
return max(z_values) - min(z_values)
|
||||
|
||||
|
||||
def get_shape_matrix(shape):
|
||||
def get_shape_matrix(shape) -> np.ndarray:
|
||||
"""Formats the transformation matrix of a shape as a 4x4 numpy array
|
||||
|
||||
:param shape: Shape output calculated by IfcOpenShell
|
||||
@@ -121,7 +124,7 @@ def get_shape_matrix(shape):
|
||||
return np.array(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]))
|
||||
|
||||
|
||||
def get_bbox_centroid(geometry):
|
||||
def get_bbox_centroid(geometry) -> tuple[float]:
|
||||
"""Calculates the bounding box centroid of the geometry
|
||||
|
||||
The centroid is in local coordinates relative to the object's placement.
|
||||
@@ -143,7 +146,7 @@ def get_bbox_centroid(geometry):
|
||||
return (minx + ((maxx - minx) / 2), miny + ((maxy - miny) / 2), minz + ((maxz - minz) / 2))
|
||||
|
||||
|
||||
def get_element_bbox_centroid(element, geometry):
|
||||
def get_element_bbox_centroid(element: ifcopenshell.entity_instance, geometry) -> tuple[float]:
|
||||
"""Calculates the element's bounding box centroid
|
||||
|
||||
The centroid is in global coordinates. Note that if you have the shape, it
|
||||
@@ -163,7 +166,7 @@ def get_element_bbox_centroid(element, geometry):
|
||||
return (mat @ np.array([*centroid, 1.0]))[0:3]
|
||||
|
||||
|
||||
def get_shape_bbox_centroid(shape, geometry):
|
||||
def get_shape_bbox_centroid(shape, geometry) -> tuple[float]:
|
||||
"""Calculates the shape's bounding box centroid
|
||||
|
||||
The centroid is in global coordinates. Note that if you do not have the
|
||||
@@ -180,7 +183,7 @@ def get_shape_bbox_centroid(shape, geometry):
|
||||
return (get_shape_matrix(shape) @ np.array([*centroid, 1.0]))[0:3]
|
||||
|
||||
|
||||
def get_vertices(geometry):
|
||||
def get_vertices(geometry) -> np.ndarray[np.ndarray[float]]:
|
||||
"""Get all the vertices as a numpy array
|
||||
|
||||
Vertices are in local coordinates.
|
||||
@@ -196,7 +199,7 @@ def get_vertices(geometry):
|
||||
return np.array([np.array([verts[i], verts[i + 1], verts[i + 2]]) for i in range(0, len(verts), 3)])
|
||||
|
||||
|
||||
def get_edges(geometry):
|
||||
def get_edges(geometry) -> np.ndarray[np.ndarray[int]]:
|
||||
"""Get all the edges as a numpy array
|
||||
|
||||
Results are a nested numpy array e.g. [[e1v1, e1v2], [e2v1, e2v2], ...]
|
||||
@@ -214,7 +217,7 @@ def get_edges(geometry):
|
||||
return [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)]
|
||||
|
||||
|
||||
def get_faces(geometry):
|
||||
def get_faces(geometry) -> np.ndarray[np.ndarray[int]]:
|
||||
"""Get all the faces as a numpy array
|
||||
|
||||
Faces are always triangulated. If the shape is a BRep and you want to get
|
||||
@@ -231,7 +234,7 @@ def get_faces(geometry):
|
||||
return [[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)]
|
||||
|
||||
|
||||
def get_shape_vertices(shape, geometry):
|
||||
def get_shape_vertices(shape, geometry) -> np.ndarray[np.ndarray[float]]:
|
||||
"""Get the shape's vertices as a numpy array
|
||||
|
||||
Vertices are in global coordinates. If you do not have the shape, you can
|
||||
@@ -251,7 +254,7 @@ def get_shape_vertices(shape, geometry):
|
||||
return np.delete((mat @ np.hstack((verts, np.ones((len(verts), 1)))).T).T, -1, axis=1)
|
||||
|
||||
|
||||
def get_element_vertices(element, geometry):
|
||||
def get_element_vertices(element: ifcopenshell.entity_instance, geometry) -> np.ndarray[np.ndarray[float]]:
|
||||
"""Get the element's vertices as a numpy array
|
||||
|
||||
Vertices are in global coordinates. Note that if you have the shape, it is
|
||||
@@ -273,7 +276,7 @@ def get_element_vertices(element, geometry):
|
||||
return np.delete((mat @ np.hstack((verts, np.ones((len(verts), 1)))).T).T, -1, axis=1)
|
||||
|
||||
|
||||
def get_bottom_elevation(geometry):
|
||||
def get_bottom_elevation(geometry) -> float:
|
||||
"""Gets the lowest local Z ordinate of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
@@ -285,7 +288,7 @@ def get_bottom_elevation(geometry):
|
||||
return min(z_values)
|
||||
|
||||
|
||||
def get_top_elevation(geometry):
|
||||
def get_top_elevation(geometry) -> float:
|
||||
"""Gets the highest local Z ordinate of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
@@ -297,7 +300,7 @@ def get_top_elevation(geometry):
|
||||
return max(z_values)
|
||||
|
||||
|
||||
def get_shape_bottom_elevation(shape, geometry):
|
||||
def get_shape_bottom_elevation(shape, geometry) -> float:
|
||||
"""Gets the lowest global Z ordinate of the shape
|
||||
|
||||
If you do not have the shape, you can use ``get_element_bottom_elevation``
|
||||
@@ -313,7 +316,7 @@ def get_shape_bottom_elevation(shape, geometry):
|
||||
return min([v[2] for v in get_shape_vertices(shape, geometry)])
|
||||
|
||||
|
||||
def get_shape_top_elevation(shape, geometry):
|
||||
def get_shape_top_elevation(shape, geometry) -> float:
|
||||
"""Gets the highest global Z ordinate of the shape
|
||||
|
||||
If you do not have the shape, you can use ``get_element_top_elevation``
|
||||
@@ -329,7 +332,7 @@ def get_shape_top_elevation(shape, geometry):
|
||||
return max([v[2] for v in get_shape_vertices(shape, geometry)])
|
||||
|
||||
|
||||
def get_element_bottom_elevation(element, geometry):
|
||||
def get_element_bottom_elevation(element: ifcopenshell.entity_instance, geometry) -> float:
|
||||
"""Gets the lowest global Z ordinate of the element
|
||||
|
||||
Note that if you have the shape, it is more efficient to use
|
||||
@@ -345,7 +348,7 @@ def get_element_bottom_elevation(element, geometry):
|
||||
return min([v[2] for v in get_element_vertices(element, geometry)])
|
||||
|
||||
|
||||
def get_element_top_elevation(element, geometry):
|
||||
def get_element_top_elevation(element: ifcopenshell.entity_instance, geometry) -> float:
|
||||
"""Gets the highest global Z ordinate of the element
|
||||
|
||||
Note that if you have the shape, it is more efficient to use
|
||||
@@ -361,7 +364,7 @@ def get_element_top_elevation(element, geometry):
|
||||
return max([v[2] for v in get_element_vertices(element, geometry)])
|
||||
|
||||
|
||||
def get_bbox(vertices):
|
||||
def get_bbox(vertices: Iterable[VECTOR_3D]) -> tuple[np.ndarray[float]]:
|
||||
"""Gets the bounding box of vertices
|
||||
|
||||
:param vertices: An iterable of vertices
|
||||
@@ -384,7 +387,7 @@ def get_bbox(vertices):
|
||||
return (np.array([minx, miny, minz]), np.array([maxx, maxy, maxz]))
|
||||
|
||||
|
||||
def get_area_vf(vertices, faces):
|
||||
def get_area_vf(vertices: np.ndarray[VECTOR_3D], faces: np.ndarray[Iterable[int]]) -> float:
|
||||
"""Calculates the surface area given a list of vertices and triangulated faces
|
||||
|
||||
:param vertices: A list of 3D vertices, such as returned from get_vertices.
|
||||
@@ -408,7 +411,7 @@ def get_area_vf(vertices, faces):
|
||||
return mesh_area
|
||||
|
||||
|
||||
def get_area(geometry):
|
||||
def get_area(geometry) -> float:
|
||||
"""Calculates the surface area of the geometry
|
||||
|
||||
:param geometry: Geometry output calculated by IfcOpenShell
|
||||
@@ -423,7 +426,11 @@ def get_area(geometry):
|
||||
return get_area_vf(vertices, faces)
|
||||
|
||||
|
||||
def get_side_area(geometry, axis="Y", direction=None):
|
||||
def get_side_area(
|
||||
geometry,
|
||||
axis: AXIS_LITERAL = "Y",
|
||||
direction: Optional[VECTOR_3D] = None,
|
||||
) -> float:
|
||||
"""Calculates the total surface area of surfaces that are visible from the specified axis
|
||||
|
||||
This is typically useful for calculating elevational areas. For example,
|
||||
@@ -471,7 +478,11 @@ def get_side_area(geometry, axis="Y", direction=None):
|
||||
return get_area_vf(vertices, filtered_faces)
|
||||
|
||||
|
||||
def get_footprint_area(geometry, axis="Z", direction=None):
|
||||
def get_footprint_area(
|
||||
geometry,
|
||||
axis: AXIS_LITERAL = "Z",
|
||||
direction: Optional[VECTOR_3D] = None,
|
||||
) -> float:
|
||||
"""Calculates the total footprint (i.e. projected) surface area visible from along an axis
|
||||
|
||||
This is typically useful for calculating footprint areas. For example, you
|
||||
@@ -551,7 +562,7 @@ def get_footprint_area(geometry, axis="Z", direction=None):
|
||||
return unioned_polygon.area
|
||||
|
||||
|
||||
def get_outer_surface_area(geometry):
|
||||
def get_outer_surface_area(geometry) -> float:
|
||||
"""Calculates the outer surface area (i.e. all sides except for top and bottom)
|
||||
|
||||
This is typically useful for calculating painted areas of beams which
|
||||
@@ -581,7 +592,7 @@ def get_outer_surface_area(geometry):
|
||||
return get_area_vf(vertices, filtered_faces)
|
||||
|
||||
|
||||
def get_footprint_perimeter(geometry):
|
||||
def get_footprint_perimeter(geometry) -> float:
|
||||
"""Calculates the footprint perimeter of the geometry
|
||||
|
||||
All faces with a negative Z normal are considered and the distance of all
|
||||
@@ -630,7 +641,7 @@ def get_footprint_perimeter(geometry):
|
||||
return sum([np.linalg.norm(vertices[e[0]] - vertices[e[1]]) for e in (all_edges - shared_edges)])
|
||||
|
||||
|
||||
def get_profiles(element):
|
||||
def get_profiles(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
"""Gets all 2D profiles used in the definition of a parametric shape
|
||||
|
||||
Profiles may be retrieved either from material profile sets or from swept
|
||||
@@ -647,7 +658,7 @@ def get_profiles(element):
|
||||
return [e.SweptArea for e in get_extrusions(element)]
|
||||
|
||||
|
||||
def get_extrusions(element):
|
||||
def get_extrusions(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
"""Gets all extruded area solids used to define an element's model body geometry
|
||||
|
||||
:param element: The element occurrence
|
||||
|
||||
Reference in New Issue
Block a user