From 170b6fc67e088eae4890ea0bd351fe1192c3ec7b Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 23 Dec 2024 11:35:48 +0500 Subject: [PATCH] typing --- .../bonsai/bim/module/project/operator.py | 27 +++-- src/bonsai/bonsai/bim/prop.py | 9 +- .../api/geometry/add_window_representation.py | 2 +- src/ifcopenshell-python/ifcopenshell/file.py | 2 +- .../ifcopenshell/util/shape.py | 102 ++---------------- 5 files changed, 38 insertions(+), 104 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 41410d84fb..f3b3b9120f 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -1521,17 +1521,20 @@ class LoadLinkedProject(bpy.types.Operator): ) self.meshes = {} self.blender_mats = {} - blender_mats = {} + blender_mats: dict[tuple[float, float, float, float], bpy.types.Material] = {} default_mat = np.array([[1, 1, 1, 1]], dtype=np.float32) - chunked_guids = [] - chunked_guid_ids = [] - chunked_verts = [] - chunked_faces = [] - chunked_materials = [] - chunked_material_ids = [] + chunked_guids: list[str] = [] + chunked_guid_ids: list[int] = [] + chunked_verts: list[np.ndarray] = [] + chunked_faces: list[np.ndarray] = [] + # List of material colors. + chunked_materials: list[np.ndarray] = [] + # List of material indices for each face. + chunked_material_ids: list[np.ndarray] = [] material_offset = 0 chunk_size = 10000 + # Vertex offset. offset = 0 ci = 0 @@ -1771,7 +1774,15 @@ class LoadLinkedProject(bpy.types.Operator): self.collection.objects.link(obj) - def create_object(self, verts, faces, materials: list[bpy.types.Material], material_ids, guids, guid_ids): + def create_object( + self, + verts: np.ndarray, + faces: np.ndarray, + materials: list[bpy.types.Material], + material_ids: np.ndarray, + guids: list[str], + guid_ids: list[int], + ) -> None: num_vertices = len(verts) // 3 if not num_vertices: return diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py index d20f30f6d1..4f2bbfe66d 100644 --- a/src/bonsai/bonsai/bim/prop.py +++ b/src/bonsai/bonsai/bim/prop.py @@ -272,10 +272,10 @@ AttributeDataType = Literal["string", "integer", "float", "boolean", "enum", "fi class Attribute(PropertyGroup): tooltip = "`Right Click > IFC Description` to read the attribute description and online documentation" - name: StringProperty(name="Name") + name: StringProperty(name="Name") # type: ignore [reportRedeclaration] display_name: StringProperty(name="Display Name", get=get_display_name) - description: StringProperty(name="Description") - ifc_class: StringProperty(name="Ifc Class") + description: StringProperty(name="Description") # type: ignore [reportRedeclaration] + ifc_class: StringProperty(name="Ifc Class") # type: ignore [reportRedeclaration] data_type: EnumProperty( # type: ignore [reportRedeclaration] name="Data Type", items=[(i, i, "") for i in get_args(AttributeDataType)], @@ -316,6 +316,9 @@ class Attribute(PropertyGroup): metadata: StringProperty(name="Metadata", description="For storing some additional information about the attribute") if TYPE_CHECKING: + name: str + description: str + ifc_class: str data_type: AttributeDataType def get_value(self) -> Union[str, float, int, bool, None]: diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py index 0d5b2a5d2c..b6ba723695 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py @@ -451,7 +451,7 @@ class Usecase: mullion_thickness: float = lining_props["MullionThickness"] / 2 first_mullion_offset: float = lining_props["FirstMullionOffset"] - second_mullion_offset: flaot = lining_props["SecondMullionOffset"] + second_mullion_offset: float = lining_props["SecondMullionOffset"] transom_thickness: float = lining_props["TransomThickness"] / 2 first_transom_offset: float = lining_props["FirstTransomOffset"] second_transom_offset: float = lining_props["SecondTransomOffset"] diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index da7db0dcce..5a8400eb4b 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -628,7 +628,7 @@ class file: def __iter__(self) -> Generator[ifcopenshell.entity_instance, None, None]: return iter(self[id] for id in self.wrapped_data.entity_names()) - def assign_header_from(self, other): + def assign_header_from(self, other: ifcopenshell.file) -> None: for k, vs in HEADER_FIELDS.items(): for v in vs: setattr(getattr(self.header, k), v, getattr(getattr(other.header, k), v)) diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape.py b/src/ifcopenshell-python/ifcopenshell/util/shape.py index ec6caec705..30e4125351 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape.py @@ -41,13 +41,9 @@ 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 - :type value: float :param x: The value to compare to - :type x: float :param tolerance: The tolerance to use. Defaults to 1e-6. - :type tolerance: float :return: True or false - :rtype: bool """ if tolerance is None: tolerance = tol @@ -60,9 +56,7 @@ def get_volume(geometry: ShapeType) -> float: Volumes of non-manifold geometry will be unpredictable. :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The volume in m3 - :rtype: float """ # https://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up @@ -90,9 +84,7 @@ def get_x(geometry: ShapeType) -> float: """Calculates the X length of the geometry :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The X dimension - :rtype: float """ verts_flat = get_vertices(geometry).ravel() return np.max(verts_flat[0::3]) - np.min(verts_flat[0::3]) @@ -102,9 +94,7 @@ def get_y(geometry: ShapeType) -> float: """Calculates the Y length of the geometry :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The Y dimension - :rtype: float """ verts_flat = get_vertices(geometry).ravel() return np.max(verts_flat[1::3]) - np.min(verts_flat[1::3]) @@ -114,9 +104,7 @@ def get_z(geometry: ShapeType) -> float: """Calculates the Z length of the geometry :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The Z dimension - :rtype: float """ verts_flat = get_vertices(geometry).ravel() return np.max(verts_flat[2::3]) - np.min(verts_flat[2::3]) @@ -126,9 +114,7 @@ def get_max_xy(geometry: ShapeType) -> float: """Gets the maximum X or Y length of the geometry :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The maximum possible value out of the X and Y dimension - :rtype: float """ return max(get_x(geometry), get_y(geometry)) @@ -137,9 +123,7 @@ def get_max_xyz(geometry: ShapeType) -> float: """Gets the maximum X, Y, or Z length of the geometry :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The maximum possible value out of the X, Y, and Z dimension - :rtype: float """ return max(get_x(geometry), get_y(geometry), get_z(geometry)) @@ -148,9 +132,7 @@ def get_min_xyz(geometry: ShapeType) -> float: """Gets the minimum X, Y, or Z length of the geometry :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The minimum possible value out of the X, Y, and Z dimension - :rtype: float """ return min(get_x(geometry), get_y(geometry), get_z(geometry)) @@ -159,9 +141,7 @@ def get_shape_matrix(shape: ShapeElementType) -> MatrixType: """Formats the transformation matrix of a shape as a 4x4 numpy array :param shape: Shape output calculated by IfcOpenShell - :type shape: shape :return: A 4x4 numpy array representing the transformation matrix - :rtype: MatrixType """ return np.array(shape.transformation.matrix).reshape((4, 4), order="F") @@ -172,9 +152,7 @@ def get_bbox_centroid(geometry: ShapeType) -> tuple[float, float, float]: The centroid is in local coordinates relative to the object's placement. :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: A tuple representing the XYZ centroid - :rtype: tuple[float, float, float] """ vertices_array = get_vertices(geometry) return (np.min(vertices_array, axis=0) + np.max(vertices_array, axis=0)) / 2 @@ -186,9 +164,7 @@ def get_vert_centroid(geometry: ShapeType) -> tuple[float, float, float]: The centroid is in local coordinates relative to the object's placement. :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: A tuple representing the XYZ centroid - :rtype: tuple[float, float, float] """ return np.mean(get_vertices(geometry), axis=0) @@ -200,11 +176,8 @@ def get_element_bbox_centroid(element: ifcopenshell.entity_instance, geometry) - is more efficient to use ``get_shape_bbox_centroid``. :param element: The element occurrence - :type: ifcopenshell.entity_instance :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: A tuple representing the XYZ centroid - :rtype: npt.NDArray[np.float64] """ centroid = get_bbox_centroid(geometry) if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"): @@ -220,11 +193,8 @@ def get_shape_bbox_centroid(shape: ShapeType, geometry: ShapeType) -> npt.NDArra shape, you can use ``get_element_bbox_centroid``. :param shape: Shape output calculated by IfcOpenShell - :type shape: shape :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: A tuple representing the XYZ centroid - :rtype: npt.NDArray[np.float64] """ centroid = get_bbox_centroid(geometry) return (get_shape_matrix(shape) @ np.array([*centroid, 1.0]))[0:3] @@ -235,13 +205,10 @@ def get_vertices(geometry: ShapeType, is_2d: bool = False) -> npt.NDArray[np.flo Vertices are in local coordinates. - Results are a nested numpy array e.g. [[v1x, v1y, v1z], [v2x, v2y, v2z], ...] - :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :param is_2d: Set to True to to get XY coordinates only. - :return: A numpy array listing all the vertices. Each vertex is a numpy array with XYZ coordinates. - :rtype: np.array[np.array[float]] + :return: A numpy array listing all the vertices and their coordinates. + Array shape: (n, 3), where n - number of vertices. """ if is_2d: return np.frombuffer(geometry.verts_buffer, "d").reshape(-1, 3)[:, :2] @@ -258,9 +225,8 @@ def get_edges(geometry: ShapeType) -> npt.NDArray[np.int32]: ngons. :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry - :return: A numpy array listing all the edges. Each edge is a numpy array with two vertex indices. - :rtype: np.array[np.array[int]] + :return: A numpy array listing all the edges. + Array shape: (n, 2), where n - number of edges. """ return np.frombuffer(geometry.edges_buffer, dtype="i").reshape(-1, 2) @@ -274,9 +240,8 @@ def get_faces(geometry: ShapeType) -> npt.NDArray[np.int32]: Results are a nested numpy array e.g. [[f1v1, f1v2, f1v3], [f2v1, f2v2, f2v3], ...] :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry - :return: A numpy array listing all the faces. Each face is a numpy array with three vertex indices. - :rtype: np.array[np.array[int]] + :return: A numpy array listing all the faces. + Array shape: (n, 3), where n - number of faces. """ return np.frombuffer(geometry.faces_buffer, dtype="i").reshape(-1, 3) @@ -285,6 +250,7 @@ def get_material_colors(geometry: ShapeType) -> npt.NDArray[np.float64]: """Get material colors as a numpy array. :return: A numpy array listing RGBA color for each shape's material. + Array shape: (1, 4). """ # colors_buffer comes from geometry.materials and doesn't account # for colors that can be set by some other way (e.g. IfcIndexedColourMap). @@ -297,6 +263,7 @@ def get_normals(geometry: ShapeType) -> npt.NDArray[np.float64]: See geometry settings documentation for settings that affect normals. :return: A numpy array listing normal for each shape vertex. + Array shape: (1, 3). """ return np.frombuffer(geometry.normals_buffer, dtype="d").reshape(-1, 3) @@ -348,11 +315,9 @@ def get_shape_vertices(shape: ShapeType, geometry: ShapeType) -> npt.NDArray[np. Results are a nested numpy array e.g. [[v1x, v1y, v1z], [v2x, v2y, v2z], ...] :param shape: Shape output calculated by IfcOpenShell - :type shape: shape :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: A numpy array listing all the vertices. Each vertex is a numpy array with XYZ coordinates. - :rtype: np.array[np.array[float]] + Array shape: (n, 3), where n - number of vertices. """ verts = get_vertices(geometry) mat = get_shape_matrix(shape) @@ -368,11 +333,8 @@ def get_element_vertices(element: ifcopenshell.entity_instance, geometry: ShapeT Results are a nested numpy array e.g. [[v1x, v1y, v1z], [v2x, v2y, v2z], ...] :param element: The element occurrence - :type: ifcopenshell.entity_instance :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: A numpy array listing all the vertices. Each vertex is a numpy array with XYZ coordinates. - :rtype: np.array[np.array[float]] """ verts = get_vertices(geometry) if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"): @@ -385,9 +347,7 @@ def get_bottom_elevation(geometry: ShapeType) -> float: """Gets the lowest local Z ordinate of the geometry :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The Z value - :rtype: float """ z_values = [geometry.verts[i + 2] for i in range(0, len(geometry.verts), 3)] return min(z_values) @@ -397,9 +357,7 @@ def get_top_elevation(geometry: ShapeType) -> float: """Gets the highest local Z ordinate of the geometry :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The Z value - :rtype: float """ verts_flat = get_vertices(geometry).ravel() return np.max(verts_flat[2::3]) @@ -412,11 +370,8 @@ def get_shape_bottom_elevation(shape: ShapeType, geometry: ShapeType) -> float: instead. :param shape: Shape output calculated by IfcOpenShell - :type shape: shape :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The Z value - :rtype: float """ return min([v[2] for v in get_shape_vertices(shape, geometry)]) @@ -428,11 +383,8 @@ def get_shape_top_elevation(shape: ShapeType, geometry: ShapeType) -> float: instead. :param shape: Shape output calculated by IfcOpenShell - :type shape: shape :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The Z value - :rtype: float """ return max([v[2] for v in get_shape_vertices(shape, geometry)]) @@ -444,11 +396,8 @@ def get_element_bottom_elevation(element: ifcopenshell.entity_instance, geometry ``get_shape_bottom_elevation``. :param element: The element occurrence - :type: ifcopenshell.entity_instance :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The Z value - :rtype: float """ return min([v[2] for v in get_element_vertices(element, geometry)]) @@ -460,11 +409,8 @@ def get_element_top_elevation(element: ifcopenshell.entity_instance, geometry: S ``get_shape_top_elevation``. :param element: The element occurrence - :type: ifcopenshell.entity_instance :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The Z value - :rtype: float """ return max([v[2] for v in get_element_vertices(element, geometry)]) @@ -473,12 +419,10 @@ def get_bbox(vertices: Iterable[VECTOR_3D]) -> tuple[npt.NDArray[np.float64], np """Gets the bounding box of vertices :param vertices: An iterable of vertices - :type: iterable :return: The bounding box value represented as a tuple of two numpy arrays. The first holds the bottom left corner and the second holds the top right. E.g. (np.array([minx, miny, minz]), np.array([maxx, maxy, maxz])) - :rtype: tuple[np.array[float]] """ x_values = [v[0] for v in vertices] y_values = [v[1] for v in vertices] @@ -496,11 +440,8 @@ def get_area_vf(vertices: npt.NDArray[np.float64], faces: npt.NDArray[np.int32]) """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. - :type: np.array[iterable[float]] :param faces: A list of faces, such as returned from get_faces. - :type: np.array[iterable[int]] :return: The surface area. - :rtype: float """ # Calculate the triangle normal vectors v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]] @@ -520,9 +461,7 @@ def get_area(geometry: ShapeType) -> float: """Calculates the surface area of the geometry :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The surface area. - :rtype: float """ vertices = get_vertices(geometry) faces = get_faces(geometry) @@ -548,12 +487,9 @@ def get_side_area( you want the projected area, use ``get_footprint_area``. :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :param axis: Either X, Y, or Z. Defaults to Y, which is used for standard walls. - :type axis: str :return: The surface area. - :rtype: float """ if direction is None: direction = {"X": (1.0, 0.0, 0.0), "Y": (0.0, 1.0, 0.0), "Z": (0.0, 0.0, 1.0)}[axis] @@ -585,9 +521,7 @@ def get_max_side_area(geometry: ShapeType) -> float: See :func:`get_side_area` for how side area is calculated. :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The maximum surface area from either the X, Y, or Z axis. - :rtype: float """ return max(get_side_area(geometry, axis="X"), get_side_area(geometry, axis="Y"), get_side_area(geometry, axis="Z")) @@ -611,14 +545,10 @@ def get_footprint_area( area. If you want the actual area, use ``get_side_area``. :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :param axis: Either X, Y, or Z. Defaults to Z. - :type axis: str,optional :param direction: An XYZ iterable (e.g. (0., 0., 1.)). If a direction vector is specified, this overrides the axis argument. - :type axis: iterable[float],optional :return: The surface area. - :rtype: float """ if direction is None: direction = {"X": (1.0, 0.0, 0.0), "Y": (0.0, 1.0, 0.0), "Z": (0.0, 0.0, 1.0)}[axis] @@ -682,9 +612,7 @@ def get_outer_surface_area(geometry: ShapeType) -> float: exclude the end faces (at the minimum and maximum local Z). :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The surface area. - :rtype: float """ vertices = get_vertices(geometry) faces = get_faces(geometry) @@ -710,9 +638,7 @@ def get_footprint_perimeter(geometry: ShapeType) -> float: perimeter edges are totaled. :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The perimeter length - :rtype: float """ vertices = get_vertices(geometry) faces = get_faces(geometry) @@ -757,9 +683,7 @@ def get_profiles(element: ifcopenshell.entity_instance) -> list[ifcopenshell.ent solid extrusions. This is useful for later doing 2D take-off from profiles. :param element: The element occurrence - :type: ifcopenshell.entity_instance :return: A list of profiles - :rtype: list[ifcopenshell.entity_instance] """ material = ifcopenshell.util.element.get_material(element, should_skip_usage=True) if material and material.is_a("IfcMaterialProfileSet"): @@ -767,13 +691,11 @@ def get_profiles(element: ifcopenshell.entity_instance) -> list[ifcopenshell.ent return [e.SweptArea for e in get_extrusions(element)] -def get_extrusions(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: +def get_extrusions(element: ifcopenshell.entity_instance) -> Union[list[ifcopenshell.entity_instance], None]: """Gets all extruded area solids used to define an element's model body geometry :param element: The element occurrence - :type: ifcopenshell.entity_instance - :return: A list of extrusion representation items - :rtype: list[ifcopenshell.entity_instance] + :return: A list of extrusion representation items or `None` if element has no representation. """ representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") if not representation: @@ -796,9 +718,7 @@ def get_total_edge_length(geometry: ShapeType) -> float: """Calculates the total length of edges in a given geometry. :param geometry: Geometry output calculated by IfcOpenShell - :type geometry: geometry :return: The total length of all edges in the geometry. - :rtype: float """ vertices = get_vertices(geometry) vertices = vertices[get_edges(geometry)]