From b67b5e8d9f73be4b7b20de4ab738e91da804c61d Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 18 Mar 2024 16:09:35 +0500 Subject: [PATCH] typing --- src/ifccsv/ifccsv.py | 31 ++++++++++++++----- .../ifcopenshell/util/classification.py | 10 +++--- .../ifcopenshell/util/placement.py | 13 ++++---- .../ifcopenshell/util/schema.py | 26 ++++++++++------ .../ifcopenshell/util/selector.py | 16 +++++++--- 5 files changed, 63 insertions(+), 33 deletions(-) diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 474de0b93a..74c1e55edb 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -29,6 +29,7 @@ import ifcopenshell.util.selector import ifcopenshell.util.element import ifcopenshell.util.schema from statistics import mean +from typing import Optional, Union try: from odf.namespaces import OFFICENS @@ -269,7 +270,7 @@ class IfcCsv: elif not include_global_id: self.results = sorted(self.results, key=lambda x: x[0]) - def export_csv(self, output, delimiter=None): + def export_csv(self, output: str, delimiter: Optional[str] = None) -> None: with open(output, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f, delimiter=delimiter) writer.writerow(self.headers) @@ -387,8 +388,16 @@ class IfcCsv: self.import_xlsx(ifc_file, table, attributes, null, empty, bool_true, bool_false) def import_csv( - self, ifc_file, table, attributes=None, delimiter=",", null="-", empty="", bool_true="YES", bool_false="NO" - ): + self, + ifc_file: ifcopenshell.file, + table: str, + attributes: Optional[list[Union[str, None]]] = None, + delimiter: str = ",", + null: str = "-", + empty: str = "", + bool_true: str = "YES", + bool_false: str = "NO", + ) -> None: with open(table, newline="", encoding="utf-8") as f: reader = csv.reader(f, delimiter=delimiter) headers = [] @@ -421,7 +430,17 @@ class IfcCsv: for _, row in df.iterrows(): self.process_row(ifc_file, row.tolist(), headers, attributes, null, empty, bool_true, bool_false) - def process_row(self, ifc_file, row, headers, attributes, null, empty, bool_true, bool_false): + def process_row( + self, + ifc_file: ifcopenshell.file, + row: list[str], + headers: list[str], + attributes: list[Union[str, None]], + null: str, + empty: str, + bool_true: str, + bool_false: str, + ) -> None: try: element = ifc_file.by_guid(row[0]) except: @@ -448,9 +467,7 @@ if __name__ == "__main__": parser.add_argument("-s", "--spreadsheet", type=str, default="data.csv", help="The spreadsheet file") parser.add_argument("-f", "--format", type=str, default="csv", help="The format, chosen from csv, ods, or xlsx") parser.add_argument("-d", "--delimiter", type=str, default=",", help="The delimiter in CSV. Defaults to a comma.") - parser.add_argument( - "-n", "--null", type=str, default="N/A", help="How to represent null values. Defaults to N/A." - ) + parser.add_argument("-n", "--null", type=str, default="N/A", help="How to represent null values. Defaults to N/A.") parser.add_argument( "-e", "--empty", type=str, default="-", help="How to represent empty strings. Defaults to a hyphen." ) diff --git a/src/ifcopenshell-python/ifcopenshell/util/classification.py b/src/ifcopenshell-python/ifcopenshell/util/classification.py index 65378f21a7..12e82139cb 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/classification.py +++ b/src/ifcopenshell-python/ifcopenshell/util/classification.py @@ -17,14 +17,15 @@ # along with IfcOpenShell. If not, see . import ifcopenshell.util.element +from typing import Optional -def get_references(element, should_inherit=True): +def get_references(element: ifcopenshell.entity_instance, should_inherit=True) -> set[ifcopenshell.entity_instance]: results = set() if not element.is_a("IfcRoot"): if hasattr(element, "HasExternalReferences"): return {r.RelatingReference for r in element.HasExternalReferences or []} - elif hasattr(element, "HasExternalReference"): # Seriously, IFC? + elif hasattr(element, "HasExternalReference"): # Seriously, IFC? return {r.RelatingReference for r in element.HasExternalReference or []} if should_inherit: element_type = ifcopenshell.util.element.get_type(element) @@ -50,13 +51,13 @@ def get_references(element, should_inherit=True): return occurrence_results -def get_classification(reference): +def get_classification(reference: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: if reference.is_a("IfcClassification"): return reference return get_classification(reference.ReferencedSource) -def get_inherited_references(reference): +def get_inherited_references(reference: Optional[ifcopenshell.entity_instance]) -> list[ifcopenshell.entity_instance]: results = [] while True: if not reference or reference.is_a("IfcClassification"): @@ -64,4 +65,3 @@ def get_inherited_references(reference): results.append(reference) reference = reference.ReferencedSource return results - diff --git a/src/ifcopenshell-python/ifcopenshell/util/placement.py b/src/ifcopenshell-python/ifcopenshell/util/placement.py index 1f40fcc497..6b82a293a2 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/placement.py +++ b/src/ifcopenshell-python/ifcopenshell/util/placement.py @@ -18,9 +18,10 @@ import numpy as np import ifcopenshell +from typing import Literal, Iterable -def a2p(o, z, x): +def a2p(o: Iterable[float], z: Iterable[float], x: Iterable[float]) -> np.array: """Converts a location, X, and Z axis vector to a 4x4 transformation matrix IFC uses a right-handed coordinate system, so it is not necessary to @@ -73,7 +74,7 @@ def get_axis2placement(placement: ifcopenshell.entity_instance) -> np.array: return a2p(o, z, x) -def get_local_placement(placement): +def get_local_placement(placement: ifcopenshell.entity_instance) -> np.array: """Parse a local placement into a 4x4 transformation matrix This is typically used to find the location and rotation of an element. The @@ -107,7 +108,7 @@ def get_local_placement(placement): return np.dot(parent, get_axis2placement(placement.RelativePlacement)) -def get_cartesiantransformationoperator3d(inst): +def get_cartesiantransformationoperator3d(inst: ifcopenshell.entity_instance) -> np.array: """Parses an IfcCartesianTransformationOperator into a 4x4 transformation matrix Note that in general you will not need to call this directly. See @@ -152,7 +153,7 @@ def get_cartesiantransformationoperator3d(inst): return m4 -def get_mappeditem_transformation(item): +def get_mappeditem_transformation(item: ifcopenshell.entity_instance) -> np.array: """Parse an IfcMappedItem into a 4x4 transformation matrix Mapped items take a representation with an origin and transform them with a @@ -170,7 +171,7 @@ def get_mappeditem_transformation(item): return get_cartesiantransformationoperator3d(item.MappingTarget) @ m4 -def get_storey_elevation(storey): +def get_storey_elevation(storey: ifcopenshell.entity_instance) -> float: """Get the Z elevation in project units of a buildling storey Building storeys store elevation in two possible locations: the Z value of @@ -187,7 +188,7 @@ def get_storey_elevation(storey): return getattr(storey, "Elevation", 0.0) or 0.0 -def rotation(angle, axis, is_degrees=True): +def rotation(angle: float, axis: Literal["X", "Y", "Z"], is_degrees=True) -> np.array: """Create a 4x4 numpy matrix representing an euler rotation :param angle: The angle of rotation diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema.py b/src/ifcopenshell-python/ifcopenshell/util/schema.py index eb9da034a2..6bba052bf2 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/schema.py +++ b/src/ifcopenshell-python/ifcopenshell/util/schema.py @@ -21,6 +21,7 @@ import json import time import ifcopenshell import ifcopenshell.util.attribute +import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper # This is highly experimental and incomplete, however, it may work for simple datasets. # In this simple implementation, we only support 2X3<->4 right now @@ -28,7 +29,7 @@ import ifcopenshell.util.attribute cwd = os.path.dirname(os.path.realpath(__file__)) -def get_fallback_schema(version): +def get_fallback_schema(version: str) -> str: """fallback to the schema version we do have docs and mapping for, needed to support IFC versions like 4X3_RC1, 4X1 etc""" if version.startswith("IFC4X3"): @@ -38,7 +39,7 @@ def get_fallback_schema(version): return version -def is_a(entity, ifc_class): +def is_a(entity: ifcopenshell.entity_instance, ifc_class: str) -> bool: ifc_class = ifc_class.upper() if entity.name_uc() == ifc_class: return True @@ -59,7 +60,9 @@ def get_subtypes(entity): return get_classes(entity) -def reassign_class(ifc_file, element, new_class): +def reassign_class( + ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance, new_class: str +) -> ifcopenshell.entity_instance: """ Attempts to change the class (entity name) of `element` to `new_class` by removing element and recreating a similar instance of type `new_class` @@ -74,7 +77,7 @@ def reassign_class(ifc_file, element, new_class): It's unlikely that this affects real-world usage of this function. """ - schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema) + schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(ifc_file.schema) try: declaration = schema.declaration_by_name(new_class) except: @@ -116,11 +119,11 @@ def reassign_class(ifc_file, element, new_class): class BatchReassignClass: - def __init__(self, file): + def __init__(self, file: ifcopenshell.file): self.file = file self.purge() - def reassign(self, element, new_class): + def reassign(self, element: ifcopenshell.entity_instance, new_class: str) -> ifcopenshell.entity_instance: try: new_element = self.file.create_entity(new_class) except: @@ -150,9 +153,12 @@ class BatchReassignClass: self.file.remove(element) self.purge() - def purge(self): - self.replacements = {} - self.to_delete = set() + def purge(self) -> None: + # mapping {inverse: {attribute_index: {old_element: new_element} } } + self.replacements: dict[ + ifcopenshell.entity_instance, dict[int, dict[ifcopenshell.entity_instance, ifcopenshell.entity_instance]] + ] = {} + self.to_delete: set[ifcopenshell.entity_instance] = set() class Migrator: @@ -217,7 +223,7 @@ class Migrator: "User": None, } - def migrate(self, element, new_file): + def migrate(self, element: ifcopenshell.entity_instance, new_file: ifcopenshell.file) -> ifcopenshell.entity_instance: if element.id() == 0: return new_file.create_entity(element.is_a(), element.wrappedValue) try: diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 198a14fca2..9889e8c373 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -25,8 +25,9 @@ import ifcopenshell.util.element import ifcopenshell.util.placement import ifcopenshell.util.geolocation import ifcopenshell.util.classification +import ifcopenshell.util.schema from decimal import Decimal -from typing import Optional +from typing import Optional, Any, Union filter_elements_grammar = lark.Lark( @@ -258,8 +259,8 @@ def format(query): return FormatTransformer().transform(format_grammar.parse(query)) -def get_element_value(element, query): - keys = GetElementTransformer().transform(get_element_grammar.parse(query)) +def get_element_value(element: ifcopenshell.entity_instance, query: str) -> Any: + keys: list[str] = GetElementTransformer().transform(get_element_grammar.parse(query)) return Selector.get_element_value(element, keys) @@ -309,7 +310,12 @@ def filter_elements( return transformer.elements -def set_element_value(ifc_file, element, query, value): +def set_element_value( + ifc_file: ifcopenshell.file, + element: ifcopenshell.entity_instance, + query: Union[str, list[str]], + value: Optional[str], +) -> Union[ifcopenshell.entity_instance, None]: if isinstance(query, (list, tuple)): keys = query else: @@ -885,7 +891,7 @@ class Selector: return {"keys": keys, "is_regex": is_regex} @classmethod - def get_element_value(cls, element, keys): + def get_element_value(cls, element: ifcopenshell.entity_instance, keys: list[str]) -> Any: value = element for key in keys: if value is None: