diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape.py b/src/ifcopenshell-python/ifcopenshell/util/shape.py index 06d5af3fd3..75c15b6935 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape.py @@ -26,7 +26,7 @@ import ifcopenshell.util.placement import ifcopenshell.util.representation from ifcopenshell.util.shape_builder import VectorType from math import radians, cos -from ifcopenshell.geom import ShapeElementType, ShapeType +from ifcopenshell.geom import ShapeElementType from typing import Optional, Literal, Union tol = 1e-6 @@ -36,7 +36,7 @@ VECTOR_3D = tuple[float, float, float] MatrixType = npt.NDArray[np.float64] """`npt.NDArray[np.float64]`""" -# NOTE: See IfcGeomRepresentation.h for ShapeType buffer types. +# NOTE: See IfcGeomRepresentation.h for W.Triangulation buffer types. # NOTE: For functions that return a single scalar ensure to use .item() to # return the Python float instead of numpy float @@ -58,7 +58,7 @@ def is_x(value: float, x: float, tolerance: Optional[float] = None) -> bool: return abs(x - value) < tolerance -def get_volume(geometry: ShapeType) -> float: +def get_volume(geometry: W.Triangulation) -> float: """Calculates the total internal volume of a geometry Volumes of non-manifold geometry will be unpredictable. @@ -88,7 +88,7 @@ def get_volume(geometry: ShapeType) -> float: return abs(sum(volumes)) -def get_x(geometry: ShapeType) -> float: +def get_x(geometry: W.Triangulation) -> float: """Calculates the X length of the geometry :param geometry: Geometry output calculated by IfcOpenShell @@ -98,7 +98,7 @@ def get_x(geometry: ShapeType) -> float: return (np.max(verts_flat[0::3]) - np.min(verts_flat[0::3])).item() -def get_y(geometry: ShapeType) -> float: +def get_y(geometry: W.Triangulation) -> float: """Calculates the Y length of the geometry :param geometry: Geometry output calculated by IfcOpenShell @@ -108,7 +108,7 @@ def get_y(geometry: ShapeType) -> float: return (np.max(verts_flat[1::3]) - np.min(verts_flat[1::3])).item() -def get_z(geometry: ShapeType) -> float: +def get_z(geometry: W.Triangulation) -> float: """Calculates the Z length of the geometry :param geometry: Geometry output calculated by IfcOpenShell @@ -118,7 +118,7 @@ def get_z(geometry: ShapeType) -> float: return (np.max(verts_flat[2::3]) - np.min(verts_flat[2::3])).item() -def get_max_xy(geometry: ShapeType) -> float: +def get_max_xy(geometry: W.Triangulation) -> float: """Gets the maximum X or Y length of the geometry :param geometry: Geometry output calculated by IfcOpenShell @@ -127,7 +127,7 @@ def get_max_xy(geometry: ShapeType) -> float: return max(get_x(geometry), get_y(geometry)) -def get_max_xyz(geometry: ShapeType) -> float: +def get_max_xyz(geometry: W.Triangulation) -> float: """Gets the maximum X, Y, or Z length of the geometry :param geometry: Geometry output calculated by IfcOpenShell @@ -136,7 +136,7 @@ def get_max_xyz(geometry: ShapeType) -> float: return max(get_x(geometry), get_y(geometry), get_z(geometry)) -def get_min_xyz(geometry: ShapeType) -> float: +def get_min_xyz(geometry: W.Triangulation) -> float: """Gets the minimum X, Y, or Z length of the geometry :param geometry: Geometry output calculated by IfcOpenShell @@ -154,7 +154,7 @@ def get_shape_matrix(shape: ShapeElementType) -> MatrixType: return np.frombuffer(shape.transformation_buffer, "d").reshape((4, 4), order="F") -def get_bbox_centroid(geometry: ShapeType) -> tuple[float, float, float]: +def get_bbox_centroid(geometry: W.Triangulation) -> tuple[float, float, float]: """Calculates the bounding box centroid of the geometry The centroid is in local coordinates relative to the object's placement. @@ -166,7 +166,7 @@ def get_bbox_centroid(geometry: ShapeType) -> tuple[float, float, float]: return (np.min(vertices_array, axis=0) + np.max(vertices_array, axis=0)) / 2 -def get_vert_centroid(geometry: ShapeType) -> tuple[float, float, float]: +def get_vert_centroid(geometry: W.Triangulation) -> tuple[float, float, float]: """Calculates the average vertex centroid of the geometry The centroid is in local coordinates relative to the object's placement. @@ -177,7 +177,9 @@ def get_vert_centroid(geometry: ShapeType) -> tuple[float, float, float]: return np.mean(get_vertices(geometry), axis=0) -def get_element_bbox_centroid(element: ifcopenshell.entity_instance, geometry: ShapeType) -> npt.NDArray[np.float64]: +def get_element_bbox_centroid( + element: ifcopenshell.entity_instance, geometry: W.Triangulation +) -> npt.NDArray[np.float64]: """Calculates the element's bounding box centroid The centroid is in global coordinates. Note that if you have the shape, it @@ -194,7 +196,7 @@ def get_element_bbox_centroid(element: ifcopenshell.entity_instance, geometry: S return (mat @ np.array([*centroid, 1.0]))[0:3] -def get_shape_bbox_centroid(shape: ShapeElementType, geometry: ShapeType) -> npt.NDArray[np.float64]: +def get_shape_bbox_centroid(shape: ShapeElementType, geometry: W.Triangulation) -> npt.NDArray[np.float64]: """Calculates the shape's bounding box centroid The centroid is in global coordinates. Note that if you do not have the @@ -208,7 +210,7 @@ def get_shape_bbox_centroid(shape: ShapeElementType, geometry: ShapeType) -> npt return (get_shape_matrix(shape) @ np.array([*centroid, 1.0]))[0:3] -def get_vertices(geometry: ShapeType, is_2d: bool = False) -> npt.NDArray[np.float64]: +def get_vertices(geometry: W.Triangulation, is_2d: bool = False) -> npt.NDArray[np.float64]: """Get all the vertices as a numpy array Vertices are in local coordinates. @@ -223,7 +225,7 @@ def get_vertices(geometry: ShapeType, is_2d: bool = False) -> npt.NDArray[np.flo return np.frombuffer(geometry.verts_buffer, "d").reshape(-1, 3) -def get_edges(geometry: ShapeType) -> npt.NDArray[np.int32]: +def get_edges(geometry: W.Triangulation) -> npt.NDArray[np.int32]: """Get all the edges as a numpy array Results are a nested numpy array e.g. [[e1v1, e1v2], [e2v1, e2v2], ...] @@ -239,7 +241,7 @@ def get_edges(geometry: ShapeType) -> npt.NDArray[np.int32]: return np.frombuffer(geometry.edges_buffer, dtype="i").reshape(-1, 2) -def get_faces(geometry: ShapeType) -> npt.NDArray[np.int32]: +def get_faces(geometry: W.Triangulation) -> npt.NDArray[np.int32]: """Get all the faces as a numpy array Faces are always triangulated. If the shape is a BRep and you want to get @@ -254,7 +256,7 @@ def get_faces(geometry: ShapeType) -> npt.NDArray[np.int32]: return np.frombuffer(geometry.faces_buffer, dtype="i").reshape(-1, 3) -def get_material_colors(geometry: ShapeType) -> npt.NDArray[np.float64]: +def get_material_colors(geometry: W.Triangulation) -> npt.NDArray[np.float64]: """Get material colors as a numpy array. :return: A numpy array listing RGBA color for each shape's material. @@ -265,7 +267,7 @@ def get_material_colors(geometry: ShapeType) -> npt.NDArray[np.float64]: return np.frombuffer(geometry.colors_buffer, dtype="d").reshape(-1, 4) -def get_normals(geometry: ShapeType) -> npt.NDArray[np.float64]: +def get_normals(geometry: W.Triangulation) -> npt.NDArray[np.float64]: """Get vertex normals as a numpy array. See geometry settings documentation for settings that affect normals. @@ -276,12 +278,12 @@ def get_normals(geometry: ShapeType) -> npt.NDArray[np.float64]: return np.frombuffer(geometry.normals_buffer, dtype="d").reshape(-1, 3) -def get_shape_material_styles(geometry: ShapeType) -> tuple[W.style, ...]: +def get_shape_material_styles(geometry: W.Triangulation) -> tuple[W.style, ...]: """Get list of material styles.""" return geometry.materials -def get_faces_material_style_ids(geometry: ShapeType) -> npt.NDArray[np.int32]: +def get_faces_material_style_ids(geometry: W.Triangulation) -> npt.NDArray[np.int32]: """Get material styles ids for the geometry faces. Return a list of corresponding indices of styles from get_shape_material_styles for each face. @@ -290,12 +292,12 @@ def get_faces_material_style_ids(geometry: ShapeType) -> npt.NDArray[np.int32]: return np.frombuffer(geometry.material_ids_buffer, dtype="i") -def get_faces_representation_item_ids(geometry: ShapeType) -> npt.NDArray[np.int32]: +def get_faces_representation_item_ids(geometry: W.Triangulation) -> npt.NDArray[np.int32]: """Get representation item ids for the geometry faces.""" return np.frombuffer(geometry.item_ids_buffer, dtype="i") -def get_edges_representation_item_ids(geometry: ShapeType) -> npt.NDArray[np.int32]: +def get_edges_representation_item_ids(geometry: W.Triangulation) -> npt.NDArray[np.int32]: """Get representation item ids for the geometry edges. Can be useful for geometry without faces and in general is more universal @@ -304,7 +306,7 @@ def get_edges_representation_item_ids(geometry: ShapeType) -> npt.NDArray[np.int return np.frombuffer(geometry.edges_item_ids_buffer, dtype="i") -def get_shape_vertices(shape: ShapeElementType, geometry: ShapeType) -> npt.NDArray[np.float64]: +def get_shape_vertices(shape: ShapeElementType, geometry: W.Triangulation) -> npt.NDArray[np.float64]: """Get the shape's vertices as a numpy array Vertices are in global coordinates. If you do not have the shape, you can @@ -322,7 +324,7 @@ def get_shape_vertices(shape: ShapeElementType, geometry: ShapeType) -> npt.NDAr return np.delete((mat @ np.hstack((verts, np.ones((len(verts), 1)))).T).T, -1, axis=1) -def get_element_vertices(element: ifcopenshell.entity_instance, geometry: ShapeType) -> npt.NDArray[np.float64]: +def get_element_vertices(element: ifcopenshell.entity_instance, geometry: W.Triangulation) -> npt.NDArray[np.float64]: """Get the element's vertices as a numpy array Vertices are in global coordinates. Note that if you have the shape, it is @@ -341,7 +343,7 @@ def get_element_vertices(element: ifcopenshell.entity_instance, geometry: ShapeT return np.delete((mat @ np.hstack((verts, np.ones((len(verts), 1)))).T).T, -1, axis=1) -def get_bottom_elevation(geometry: ShapeType) -> float: +def get_bottom_elevation(geometry: W.Triangulation) -> float: """Gets the lowest local Z ordinate of the geometry :param geometry: Geometry output calculated by IfcOpenShell @@ -351,7 +353,7 @@ def get_bottom_elevation(geometry: ShapeType) -> float: return np.min(verts_flat[2::3]).item() -def get_top_elevation(geometry: ShapeType) -> float: +def get_top_elevation(geometry: W.Triangulation) -> float: """Gets the highest local Z ordinate of the geometry :param geometry: Geometry output calculated by IfcOpenShell @@ -361,7 +363,7 @@ def get_top_elevation(geometry: ShapeType) -> float: return np.max(verts_flat[2::3]).item() -def get_shape_bottom_elevation(shape: ShapeType, geometry: ShapeType) -> float: +def get_shape_bottom_elevation(shape: ShapeElementType, geometry: W.Triangulation) -> float: """Gets the lowest global Z ordinate of the shape If you do not have the shape, you can use :func:`get_element_bottom_elevation` @@ -374,7 +376,7 @@ def get_shape_bottom_elevation(shape: ShapeType, geometry: ShapeType) -> float: return min([v[2] for v in get_shape_vertices(shape, geometry)]) -def get_shape_top_elevation(shape: ShapeType, geometry: ShapeType) -> float: +def get_shape_top_elevation(shape: ShapeElementType, geometry: W.Triangulation) -> float: """Gets the highest global Z ordinate of the shape If you do not have the shape, you can use :func:`get_element_top_elevation` @@ -387,7 +389,7 @@ def get_shape_top_elevation(shape: ShapeType, geometry: ShapeType) -> float: return max([v[2] for v in get_shape_vertices(shape, geometry)]) -def get_element_bottom_elevation(element: ifcopenshell.entity_instance, geometry: ShapeType) -> float: +def get_element_bottom_elevation(element: ifcopenshell.entity_instance, geometry: W.Triangulation) -> float: """Gets the lowest global Z ordinate of the element Note that if you have the shape, it is more efficient to use @@ -400,7 +402,7 @@ def get_element_bottom_elevation(element: ifcopenshell.entity_instance, geometry return min([v[2] for v in get_element_vertices(element, geometry)]) -def get_element_top_elevation(element: ifcopenshell.entity_instance, geometry: ShapeType) -> float: +def get_element_top_elevation(element: ifcopenshell.entity_instance, geometry: W.Triangulation) -> float: """Gets the highest global Z ordinate of the element Note that if you have the shape, it is more efficient to use @@ -446,7 +448,7 @@ def get_area_vf(vertices: npt.NDArray[np.float64], faces: npt.NDArray[np.int32]) return mesh_area.item() -def get_area(geometry: ShapeType) -> float: +def get_area(geometry: W.Triangulation) -> float: """Calculates the surface area of the geometry :param geometry: Geometry output calculated by IfcOpenShell @@ -458,7 +460,7 @@ def get_area(geometry: ShapeType) -> float: def get_side_area( - geometry: ShapeType, + geometry: W.Triangulation, axis: AXIS_LITERAL = "Y", direction: Optional[VectorType] = None, angle: float = 90.0, @@ -508,7 +510,7 @@ def get_side_area( return get_area_vf(vertices, filtered_faces) -def get_max_side_area(geometry: ShapeType) -> float: +def get_max_side_area(geometry: W.Triangulation) -> float: """Returns the maximum X, Y, or Z side area See :func:`get_side_area` for how side area is calculated. @@ -519,12 +521,12 @@ def get_max_side_area(geometry: ShapeType) -> float: return max(get_side_area(geometry, axis="X"), get_side_area(geometry, axis="Y"), get_side_area(geometry, axis="Z")) -def get_top_area(geometry: ShapeType) -> float: +def get_top_area(geometry: W.Triangulation) -> float: return get_side_area(geometry, axis="Z", angle=45) def get_footprint_area( - geometry: ShapeType, + geometry: W.Triangulation, axis: AXIS_LITERAL = "Z", direction: Optional[VECTOR_3D] = None, ) -> float: @@ -602,7 +604,7 @@ def get_footprint_area( return unioned_polygon.area -def get_outer_surface_area(geometry: ShapeType) -> float: +def get_outer_surface_area(geometry: W.Triangulation) -> 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 @@ -628,7 +630,7 @@ def get_outer_surface_area(geometry: ShapeType) -> float: return get_area_vf(vertices, filtered_faces) -def get_footprint_perimeter(geometry: ShapeType) -> float: +def get_footprint_perimeter(geometry: W.Triangulation) -> float: """Calculates the footprint perimeter of the geometry All faces with a negative Z normal are considered and the distance of all @@ -731,7 +733,7 @@ def get_base_extrusions(element: ifcopenshell.entity_instance) -> Union[list[ifc return extrusions -def get_total_edge_length(geometry: ShapeType) -> float: +def get_total_edge_length(geometry: W.Triangulation) -> float: """Calculates the total length of edges in a given geometry. :param geometry: Geometry output calculated by IfcOpenShell