From 176c5cc0fbb7bdaa692e65a437a6b6f9ebd0af9f Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 13 Jun 2025 13:28:18 +0500 Subject: [PATCH] typing --- src/bonsai/bonsai/tool/polyline.py | 2 - src/bonsai/scripts/extract.py | 3 +- .../api/geometry/remove_representation.py | 6 +-- .../ifcopenshell/entity_instance.py | 16 ++++--- src/ifcopenshell-python/ifcopenshell/file.py | 18 +++++--- .../ifcopenshell/ifcopenshell_wrapper.pyi | 43 ++++++++++++++++--- .../ifcopenshell/util/element.py | 2 +- .../nodes/ifc/add_spatial_element.py | 1 - 8 files changed, 63 insertions(+), 28 deletions(-) diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py index 875dd867a6..cc869f2459 100644 --- a/src/bonsai/bonsai/tool/polyline.py +++ b/src/bonsai/bonsai/tool/polyline.py @@ -200,8 +200,6 @@ class Polyline(bonsai.core.tool.Polyline): input_ui.set_value("WORLD_ANGLE", orientation_angle) return - return - @classmethod def calculate_area(cls, context: bpy.types.Context, input_ui: PolylineUI) -> Union[PolylineUI, None]: try: diff --git a/src/bonsai/scripts/extract.py b/src/bonsai/scripts/extract.py index aa5d233dfa..dffaaf6d40 100644 --- a/src/bonsai/scripts/extract.py +++ b/src/bonsai/scripts/extract.py @@ -82,9 +82,8 @@ class IfcElementHandler(xml.sax.ContentHandler): for detail in soup.find_all("details"): if detail.summary.string == "Entity definition" and detail.p: return str(detail.p.text.replace("\n", " ")) - return None except: - return None + pass # print('Failed to get description for {}'.format(name)) return None diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py index 0618b407ec..080ba29d64 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py @@ -41,9 +41,9 @@ def remove_representation( styled_items = set() presentation_layer_assignments_items: set[ifcopenshell.entity_instance] = set() presentation_layer_assignments_reps: set[ifcopenshell.entity_instance] = set() - textures = set() - colours = set() - named_profiles = set() + textures: set[ifcopenshell.entity_instance] = set() + colours: set[ifcopenshell.entity_instance] = set() + named_profiles: set[ifcopenshell.entity_instance] = set() for subelement in file.traverse(representation): if subelement.is_a("IfcRepresentationItem"): [styled_items.add(s) for s in subelement.StyledByItem or []] diff --git a/src/ifcopenshell-python/ifcopenshell/entity_instance.py b/src/ifcopenshell-python/ifcopenshell/entity_instance.py index 2d9d3d3989..60fb9a58e1 100644 --- a/src/ifcopenshell-python/ifcopenshell/entity_instance.py +++ b/src/ifcopenshell-python/ifcopenshell/entity_instance.py @@ -154,8 +154,14 @@ class entity_instance: wrapped_data: ifcopenshell_wrapper.entity_instance - def __init__(self, e: ifcopenshell_wrapper.entity_instance, file: Union[ifcopenshell.file] = None): - # TODO: when it is a tuple? + def __init__( + self, + e: Union[ifcopenshell_wrapper.entity_instance, tuple[str, str]], + file: Union[ifcopenshell.file, None] = None, + ): + """ + :param e: Wrapper's ``entity_instance`` or a tuple ``(schema_identifier, ifc_class)``. + """ if isinstance(e, tuple): e = ifcopenshell_wrapper.new_IfcBaseClass(*e) super().__setattr__("wrapped_data", e) @@ -290,11 +296,11 @@ class entity_instance: return value @staticmethod - def wrap_value(v, file): - def wrap(e): + def wrap_value(v, file: ifcopenshell.file): + def wrap(e: ifcopenshell_wrapper.entity_instance) -> entity_instance: return entity_instance(e, file) - def is_instance(e): + def is_instance(e: Any) -> bool: return isinstance(e, ifcopenshell_wrapper.entity_instance) return entity_instance.walk(is_instance, wrap, v) diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index 739c545fbf..79d67fb35b 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -23,6 +23,7 @@ import numbers import zipfile import functools import ifcopenshell +import weakref from pathlib import Path from typing import Any, Optional, TYPE_CHECKING, Union, overload, Literal, TypedDict from collections.abc import Callable, Generator @@ -241,7 +242,11 @@ class Transaction: pass -file_dict = {} +file_dict: dict[int, weakref.ReferenceType[file]] = {} +"""Mapping of internal IfcFile pointer addressed to existing ``ifcopenshell.file``. + +Needed only to quickly access related from ``entity_instance`` it's ``file``. +""" READ_ERROR = ifcopenshell_wrapper.file_open_status.READ_ERROR NO_HEADER = ifcopenshell_wrapper.file_open_status.NO_HEADER @@ -349,9 +354,7 @@ class file: self.future = [] self.transaction: Optional[Transaction] = None - import weakref - - file_dict[self.file_pointer()] = weakref.ref(self) + file_dict[self.wrapped_data.file_pointer()] = weakref.ref(self) def __del__(self) -> None: # Avoid infinite recursion if file is failed to initialize @@ -402,7 +405,7 @@ class file: raise UndoSystemError("Error during transaction redo.", transaction) from e self.history.append(transaction) - def create_entity(self, type: str, *args, **kwargs) -> ifcopenshell.entity_instance: + def create_entity(self, type: str, *args: Any, **kwargs: Any) -> ifcopenshell.entity_instance: """Create a new IFC entity in the file. You can also use dynamic methods similar to `ifc_file.createIfcWall(...)` @@ -768,8 +771,9 @@ class file: return file(ifcopenshell_wrapper.read(s)) @staticmethod - def from_pointer(v) -> file: - return file_dict.get(v)() + def from_pointer(address: int) -> file: + assert (f := file_dict[address]()) is not None + return f def to_string(self) -> str: return self.wrapped_data.to_string() diff --git a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi index 482a558dc7..d0e5c07f4f 100644 --- a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi +++ b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi @@ -780,7 +780,10 @@ class entity_instance: def data(self, *args): ... def declaration(self) -> declaration: ... def file_pointer(self): - """Internal IfcFile pointer address.""" + """Internal IfcFile pointer address. + + Same as ``file.file_pointer``). + """ ... def get_argument(self, *args): ... @@ -865,7 +868,28 @@ class file: def FreshId(self): ... def add(self, entity: entity_instance, id: int) -> entity_instance: ... def addEntities(self, entities): ... - def batch(self): ... + def batch(self) -> None: + """Enable batch mode. + + Batch mode: + 1. Calling ``remove(entity)`` does not immediately remove the entity; + it marks it for deletion instead. + 2. When you call ``unbatch()``, all marked entities are deleted in a single operation. + + Difference from usual removal: + - In normal mode, removing an entity immediately traverses and removes all inverse references to it. + - In batch mode, inverse references are not updated per entity. + Instead, the entire inverse reference map is scanned during ``unbatch()`` + to remove references to all deleted entities. + + Batch deletion may be slower than immediate deletion, depending on the size of the inverse reference map. + """ + ... + + def unbatch(self) -> None: + """Exit batch mode.""" + ... + def build_inverses(self): ... def by_guid(self, guid: str) -> entity_instance: ... def by_id(self, id: int) -> entity_instance: ... @@ -873,9 +897,15 @@ class file: def by_type_excl_subtypes(self, *args): ... @staticmethod def createTimestamp(): ... - def entity_names(self): ... + def entity_names(self) -> tuple[int, ...]: + """Get a tuple of step ids present in the file.""" + ... + def file_pointer(self) -> int: - """Internal IfcFile pointer address.""" + """Internal IfcFile pointer address. + + Same as ``int(self.this)``. + """ ... def get_inverses_by_declaration( @@ -907,7 +937,7 @@ class file: def guid_map(*args): ... @property def header(self) -> IfcSpfHeader: ... - def ifcroot_type(self): ... + def ifcroot_type(self) -> entity: ... def instance_by_guid(self, guid): ... def instances_by_reference(self, id): ... def internal_guid_map(self): ... @@ -931,7 +961,6 @@ class file: def types_begin(self): ... def types_end(self): ... - def unbatch(self): ... def write(self, fn): ... class file_open_status: @@ -1501,7 +1530,7 @@ def less(arg1, arg2): ... def line_segments_to_polygons(s, eps, segments): ... def map_shape(settings, instance): ... def nary_union(sequence): ... -def new_IfcBaseClass(schema_identifier, name): ... +def new_IfcBaseClass(schema_identifier: str, name: str) -> entity_instance: ... def open(fn): ... def parse_ifcxml(filename): ... def polygons_to_svg(*args): ... diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 825befdb7b..c7821390df 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -1519,7 +1519,7 @@ def unbatch_remove_deep2(ifc_file: ifcopenshell.file) -> ifcopenshell.file: lines = iter(ifc_string.split("\n")) ids_to_delete = iter(sorted([e.id() for e in ifc_file.to_delete])) id_to_delete = next(ids_to_delete, None) - result = [] + result: list[str] = [] for line in lines: if id_to_delete is None: diff --git a/src/ifcsverchok/nodes/ifc/add_spatial_element.py b/src/ifcsverchok/nodes/ifc/add_spatial_element.py index 78cae88e1b..c96b5e2d14 100644 --- a/src/ifcsverchok/nodes/ifc/add_spatial_element.py +++ b/src/ifcsverchok/nodes/ifc/add_spatial_element.py @@ -102,7 +102,6 @@ class SvIfcAddSpatialElement(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.h self.elements = [list(chain.from_iterable(el)) for el in self.elements] if not self.elements[0][0]: raise Exception('Mandatory input "Element(s)" is missing.') - return self.file = SvIfcStore.get_file() self.elements = [[self.file.by_id(step_id) for step_id in element] for element in self.elements]