diff --git a/doc/source/_ext/autodocclass.py b/doc/source/_ext/autodocclass.py index 190970e2..9cd5ecb1 100644 --- a/doc/source/_ext/autodocclass.py +++ b/doc/source/_ext/autodocclass.py @@ -1,5 +1,5 @@ from enum import IntEnum -from typing import Any, Optional +from typing import Any from docutils.statemachine import StringList from sphinx.application import Sphinx @@ -28,7 +28,7 @@ def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) self.add_line(" ", self.get_sourcename()) - def add_content(self, more_content: Optional[StringList]) -> None: + def add_content(self, more_content: StringList | None) -> None: super().add_content(more_content) source_name = self.get_sourcename() diff --git a/pyproject.toml b/pyproject.toml index d85f649b..864f05c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ pyside6 = { version = "~=6.8.1", optional = true } [tool.poetry.extras] interactive = ["pyvistaqt"] single-window = ["pyvistaqt", "pyside6"] +ci_types = ["pyvistaqt", "pyside6", "ipython"] [tool.poetry.urls] "Documentation" = "https://visualization.fluent.docs.pyansys.com/" @@ -77,3 +78,12 @@ skips = [ "B604", "B607", ] + +[tool.basedpyright] +reportUnknownMemberType = false +reportExplicitAny = false +reportPrivateUsage = false +reportUnusedCallResult = false +reportUnannotatedClassAttribute = false +reportPrivateImportUsage = false +ignore = ["doc"] diff --git a/src/ansys/fluent/interface/post_objects/check_in_notebook.py b/src/ansys/fluent/interface/post_objects/check_in_notebook.py index 9014ed83..894b77ff 100644 --- a/src/ansys/fluent/interface/post_objects/check_in_notebook.py +++ b/src/ansys/fluent/interface/post_objects/check_in_notebook.py @@ -26,16 +26,18 @@ from ansys.fluent.core import PyFluentDeprecationWarning -def in_jupyter(): +def in_jupyter() -> bool: """Checks if the library is being used in a Jupyter environment.""" try: from IPython import get_ipython - return "IPKernelApp" in get_ipython().config + return ( + "IPKernelApp" in get_ipython().config + ) # pyright: ignore[reportOptionalMemberAccess] except (ImportError, AttributeError): return False -def in_notebook(): +def in_notebook() -> bool: warnings.warn("Please use 'in_jupyter' instead.", PyFluentDeprecationWarning) return in_jupyter() diff --git a/src/ansys/fluent/interface/post_objects/meta.py b/src/ansys/fluent/interface/post_objects/meta.py index f57a3292..c80f2ac9 100644 --- a/src/ansys/fluent/interface/post_objects/meta.py +++ b/src/ansys/fluent/interface/post_objects/meta.py @@ -22,21 +22,65 @@ """Metaclasses used in various explicit classes in PyFluent.""" -from abc import ABCMeta -from collections.abc import MutableMapping +from abc import ABC +from collections.abc import Callable, Iterator, Mapping, MutableMapping, Sequence import inspect -from typing import List - -from ansys.fluent.core.exceptions import DisallowedValuesError, InvalidArgument +from typing import ( + TYPE_CHECKING, + Any, + Concatenate, + Generic, + Never, + Protocol, + Self, + Unpack, + cast, + dataclass_transform, + overload, + override, +) + +from ansys.fluent.core.exceptions import DisallowedValuesError +from ansys.fluent.core.session_solver import Solver +from typing_extensions import ( + NotRequired, + ParamSpec, + TypedDict, + TypeVar, + get_args, + get_original_bases, +) + +from ansys.fluent.interface.post_objects.post_object_definitions import ( + BasePostObjectDefn, +) + +if TYPE_CHECKING: + from ansys.fluent.core.services.field_data import LiveFieldData + from ansys.fluent.core.streaming_services.monitor_streaming import MonitorsManager + + from ansys.fluent.interface.post_objects.post_object_definitions import ( + GraphicsDefn, + PlotDefn, + ) + from ansys.fluent.interface.post_objects.post_objects_container import Container # pylint: disable=unused-private-member # pylint: disable=bad-mcs-classmethod-argument +_SelfT = TypeVar("_SelfT", covariant=True) +_T_co = TypeVar("_T_co", covariant=True) + + +class HasAttributes(Protocol): + # attributes: NotRequired[set[str]] # technically this but this is just object + attributes: set[str] + -class Attribute: +class Attribute(Generic[_SelfT, _T_co]): """Attributes.""" - VALID_NAMES = [ + VALID_NAMES = ( "range", "allowed_values", "display_name_allowed_values", @@ -59,40 +103,60 @@ class Attribute: "widget", "dir_info", "extensions", - ] + ) - def __init__(self, function): + def __init__(self, function: Callable[[_SelfT], _T_co], /): self.function = function + self.__doc__: str | None = getattr(function, "__doc__", None) + self.name: str - def __set_name__(self, obj, name): + def __set_name__(self, owner: HasAttributes, name: str): if name not in self.VALID_NAMES: raise DisallowedValuesError("attribute", name, self.VALID_NAMES) self.name = name - if not hasattr(obj, "attributes"): - obj.attributes = set() - obj.attributes.add(name) - - def __set__(self, obj, value): + if not hasattr(owner, "attributes"): + owner.attributes = set[str]() + assert isinstance(owner.attributes, set) + owner.attributes.add(name) + + def __set__( + self, instance: _SelfT, value: _T_co # pyright: ignore[reportGeneralTypeIssues] + ) -> Never: raise AttributeError("Attributes are read only.") - def __get__(self, obj, objtype=None): - return self.function(obj) + @overload + def __get__(self, instance: None, _) -> Self: ... + + @overload + def __get__( + self, instance: _SelfT, _ # pyright: ignore[reportGeneralTypeIssues] + ) -> _T_co: ... + + def __get__(self, instance: _SelfT | None, _) -> _T_co | Self: + if instance is None: + return self + return self.function(instance) -class Command: +P = ParamSpec("P") + + +class Command(Generic[_SelfT, P]): """Executes command.""" - def __init__(self, method): + def __init__(self, method: Callable[Concatenate[_SelfT, P], None]): self.arguments_attrs = {} + self.owner: type + cmd_args = inspect.signature(method).parameters for arg_name in cmd_args: if arg_name != "self": self.arguments_attrs[arg_name] = {} - def _init(_self, obj): + def _init(_self: _SelfT, obj): _self.obj = obj - def _execute(_self, *args, **kwargs): + def _execute(_self: _SelfT, *args: Any, **kwargs: Any): for arg, attr_data in self.arguments_attrs.items(): arg_value = None if arg in kwargs: @@ -137,591 +201,486 @@ def _execute(_self, *args, **kwargs): { "__init__": _init, "__call__": _execute, - "argument_attribute": lambda _self, argument_name, attr_name: self.arguments_attrs[ # noqa: E501 - argument_name - ][ - attr_name - ]( - _self.obj + "argument_attribute": ( + lambda _self, argument_name, attr_name: self.arguments_attrs[ + argument_name + ][attr_name](_self.obj) ), "arguments": lambda _self: list(self.arguments_attrs.keys()), }, ) - def __set_name__(self, obj, name): - self.obj = obj - if not hasattr(obj, "commands"): - obj.commands = {} - obj.commands[name] = {} + def __set_name__(self, owner: type, name: str) -> None: + self.owner = owner + if not hasattr(owner, "commands"): + owner.commands = {} + owner.commands[name] = {} + + def __get__(self, instance: _SelfT, _): # pyright: ignore[reportGeneralTypeIssues] + return self.command_cls(instance) + + +TT = TypeVar("TT", bound=type) +T = TypeVar("T") +T2 = TypeVar("T2") + + +class PyLocalBase: + """Local base.""" + + def get_ancestors_by_type( + self, instance: BasePostObjectDefn, owner: "PyLocalBase | None" = None + ): + owner = self if owner is None else owner + parent = None + if getattr(owner, "_parent", None): + if isinstance(owner._parent, instance): + return owner._parent + parent = self.get_ancestors_by_type(instance, owner._parent) + return parent + + def get_ancestors_by_name(self, instance, owner: type | None = None): + instance = self if instance is None else instance + parent = None + if getattr(instance, "_parent", None): + if instance._parent.__class__.__name__ == owner: + return instance._parent + if getattr(instance._parent, "PLURAL", None) == owner: + return instance._parent._parent + parent = self.get_ancestors_by_name(owner, instance._parent) + return parent + + def get_root(self, instance=None) -> "PyLocalBase": + instance = self if instance is None else instance + parent = instance + if getattr(instance, "_parent", None): + parent = self.get_root(instance._parent) + return parent + + def get_session(self, instance) -> Solver: + root = self.get_root(instance) + return root.session + + def get_path(self) -> str: + if getattr(self, "_parent", None): + return self._parent.get_path() + "/" + self._name + return self._name + + @property + def root(self) -> "PyLocalBase": + """Top-most parent object.""" + return self.get_root(self) + + @property + def path(self) -> str: + """Path to the current object.""" + return self.get_path() + + @property + def session(self) -> "Solver": + """Session associated with the current object.""" + return self.get_session(self) + + @property + def field_data(self) -> "LiveFieldData": + """Field data associated with the current object.""" + return self.session.fields.field_data + + @property + def monitors(self) -> "MonitorsManager": + """Monitors associated with the current object.""" + return self.session.monitors + + +class PyLocalProperty(PyLocalBase, Generic[T]): + """Local property classes.""" + + value: T # pyright: ignore[reportUninitializedInstanceVariable] + + def __init__(self, parent, api_helper: Callable[[Self], APIHelper], name: str = ""): + self._name = name + self._api_helper = api_helper(self) + self._parent = parent + self._on_change_cbs = [] + self.type = get_args(get_original_bases(self.__class__)[0])[ + 0 + ] # T for the class + reset_on_change = ( + hasattr(self, "_reset_on_change") and getattr(self, "_reset_on_change")() + ) - def __get__(self, obj, obj_type=None): - if hasattr(self, "command"): - return self.command + try: + on_change = self.on_change + except AttributeError: + pass else: - return self.command_cls(obj) + self._register_on_change_cb(on_change) + if reset_on_change: + for obj in reset_on_change: + def reset() -> None: + setattr(self, "_value", None) + for on_change_cb in self._on_change_cbs: + on_change_cb() -def CommandArgs(command_object, argument_name): - """Command arguments.""" + obj._register_on_change_cb(reset) - def wrapper(attribute): - if argument_name in command_object.arguments_attrs: - command_object.arguments_attrs[argument_name].update( - {attribute.__name__: attribute} - ) + def __call__(self) -> T: + rv = self.value + + try: + allowed_values = self.allowed_values + except AttributeError: + return rv else: - raise InvalidArgument(f"{argument_name} not a valid argument.") - return attribute - - return wrapper - - -class PyLocalBaseMeta(type): - """Local base metaclass.""" - - @classmethod - def __create_get_ancestors_by_type(cls): - def wrapper(self, obj_type, obj=None): - obj = self if obj is None else obj - parent = None - if getattr(obj, "_parent", None): - if isinstance(obj._parent, obj_type): - return obj._parent - parent = self.get_ancestors_by_type(obj_type, obj._parent) - return parent - - return wrapper - - @classmethod - def __create_get_ancestors_by_name(cls): - def wrapper(self, obj_type, obj=None): - obj = self if obj is None else obj - parent = None - if getattr(obj, "_parent", None): - if obj._parent.__class__.__name__ == obj_type: - return obj._parent - if getattr(obj._parent, "PLURAL", None) == obj_type: - return obj._parent._parent - parent = self.get_ancestors_by_name(obj_type, obj._parent) - return parent - - return wrapper - - @classmethod - def __create_get_root(cls): - def wrapper(self, obj=None): - obj = self if obj is None else obj - parent = obj - if getattr(obj, "_parent", None): - parent = self.get_root(obj._parent) - return parent - - return wrapper - - @classmethod - def __create_get_session(cls): - def wrapper(self, obj=None): - root = self.get_root(obj) - return root.session - - return wrapper - - @classmethod - def __create_get_session_handle(cls): - def wrapper(self, obj=None): - root = self.get_root(obj) - return getattr(root, "session_handle", None) - - return wrapper - - @classmethod - def __create_get_path(cls): - def wrapper(self): - if getattr(self, "_parent", None): - return self._parent.get_path() + "/" + self._name - return self._name - - return wrapper - - def __new__(cls, name, bases, attrs): - attrs["get_ancestors_by_type"] = cls.__create_get_ancestors_by_type() - attrs["get_ancestors_by_name"] = cls.__create_get_ancestors_by_name() - attrs["get_root"] = cls.__create_get_root() - attrs["get_session"] = cls.__create_get_session() - attrs["get_session_handle"] = cls.__create_get_session_handle() - if "get_path" not in attrs: - attrs["get_path"] = cls.__create_get_path() - attrs["root"] = property(lambda self: self.get_root()) - attrs["path"] = property(lambda self: self.get_path()) - attrs["session"] = property(lambda self: self.get_session()) - attrs["field_data"] = property(lambda self: self.get_session().field_data) - attrs["monitors"] = property(lambda self: self.get_session().monitors) - attrs["session_handle"] = property(lambda self: self.get_session_handle()) - return super(PyLocalBaseMeta, cls).__new__(cls, name, bases, attrs) - - -class PyLocalPropertyMeta(PyLocalBaseMeta): - """Metaclass for local property classes.""" - - @classmethod - def __create_init(cls): - def wrapper(self, parent, api_helper, name=""): - """Create the initialization method for 'PyLocalPropertyMeta'.""" - self._name = name - self._api_helper = api_helper(self) - self._parent = parent - self._on_change_cbs = [] - annotations = self.__class__.__dict__.get("__annotations__") - if isinstance(getattr(self.__class__, "value", None), property): - value_annotation = annotations.get("_value") - else: - value_annotation = annotations.get("value") - self.type = value_annotation - reset_on_change = ( - hasattr(self, "_reset_on_change") - and getattr(self, "_reset_on_change")() - ) + if len(allowed_values) > 0 and ( + rv is None or (not isinstance(rv, list) and rv not in allowed_values) + ): + self.set_state(allowed_values[0]) + rv = self.value + + return rv - on_change = getattr(self, "on_change", None) - if on_change is not None: - self._register_on_change_cb(on_change) - if reset_on_change: - for obj in reset_on_change: + if TYPE_CHECKING: # TODO double check this is on the right thing - def reset(): - setattr(self, "_value", None) - for on_change_cb in self._on_change_cbs: - on_change_cb() + def __set__(self, instance: object, value: T) -> None: ... - obj._register_on_change_cb(reset) + def set_state(self, value: T): + self.value = value + for on_change_cb in self._on_change_cbs: + on_change_cb() - return wrapper + def _register_on_change_cb(self, on_change_cb: Callable[[], None]): + self._on_change_cbs.append(on_change_cb) - @classmethod - def __create_get_state(cls, show_attributes=False): - def wrapper(self): - rv = self.value + @Attribute + @overload + def allowed_values(self: "PyLocalProperty[Sequence[T2]]") -> Sequence[T2]: ... + @Attribute + @overload + def allowed_values(self: "PyLocalProperty[T2]") -> Sequence[T2]: ... + @Attribute + def allowed_values(self) -> Sequence[object]: + """Get allowed values.""" + raise NotImplementedError("allowed_values not implemented.") - if hasattr(self, "allowed_values"): - allowed_values = self.allowed_values - if len(allowed_values) > 0 and ( - rv is None - or (not isinstance(rv, list) and rv not in allowed_values) + +class PyReferenceObject: + """Local object classes.""" + + def __init__(self, parent, path, location, session_id, name=""): + self._parent = parent + self.type = "object" + self.parent = parent + self._path = path + self.location = location + self.session_id = session_id + + def update(clss): + for name, cls in clss.__dict__.items(): + if cls.__class__.__name__ in ( + "PyLocalPropertyMeta", + "PyLocalObjectMeta", ): - self.set_state(allowed_values[0]) - rv = self.value + setattr( + self, + name, + cls(self, lambda arg: None, name), + ) + if cls.__class__.__name__ in { + "PyLocalNamedObjectMeta", + "PyLocalNamedObjectMetaAbstract", + }: + setattr( + self, + cls.PLURAL, + PyLocalContainer(self, cls, lambda arg: None, cls.PLURAL), + ) + for base_class in clss.__bases__: + update(base_class) + + update(self.__class__) - return rv + def get_path(self): + return self._path - return wrapper - - @classmethod - def __create_set_state(cls): - def wrapper(self, value): - self.value = value - for on_change_cb in self._on_change_cbs: - on_change_cb() - - return wrapper - - @classmethod - def __create_register_on_change(cls): - def wrapper(self, on_change_cb): - self._on_change_cbs.append(on_change_cb) - - return wrapper - - def __new__(cls, name, bases, attrs): - attrs["__init__"] = cls.__create_init() - attrs["__call__"] = cls.__create_get_state() - attrs["_register_on_change_cb"] = cls.__create_register_on_change() - attrs["set_state"] = cls.__create_set_state() - return super(PyLocalPropertyMeta, cls).__new__(cls, name, bases, attrs) - - -class PyReferenceObjectMeta(PyLocalBaseMeta): - """Metaclass for local object classes.""" - - @classmethod - def __create_init(cls): - def wrapper(self, parent, path, location, session_id, name=""): - """Create the initialization method for 'PyReferenceObjectMeta'.""" - self._parent = parent - self.type = "object" - self.parent = parent - self._path = path - self.location = location - self.session_id = session_id - - def update(clss): - for name, cls in clss.__dict__.items(): - if cls.__class__.__name__ in ( - "PyLocalPropertyMeta", - "PyLocalObjectMeta", - ): - setattr( - self, - name, - cls(self, lambda arg: None, name), - ) - if ( - cls.__class__.__name__ == "PyLocalNamedObjectMeta" - or cls.__class__.__name__ == "PyLocalNamedObjectMetaAbstract" - ): - setattr( - self, - cls.PLURAL, - PyLocalContainer(self, cls, lambda arg: None, cls.PLURAL), - ) - for base_class in clss.__bases__: - update(base_class) - - update(self.__class__) - - return wrapper - - @classmethod - def __create_get_path(cls): - def wrapper(self): - return self._path - - return wrapper - - @classmethod - def __create_reset(cls): - def wrapper(self, path, location, session_id): - self._path = path - self.location = location - self.session_id = session_id - if hasattr(self, "_object"): - delattr(self, "_object") - - return wrapper - - @classmethod - def __create_getattr(cls): - def wrapper(self, item): - if item == "_object": - top_most_parent = self.get_root(self) - - if self.session_id is None: - self.session_id = top_most_parent.session.id - property_editor_data = top_most_parent.accessor( - "AnsysUser", self.session_id - ) - ( - obj, - cmd_data, - ) = property_editor_data.get_object_and_command_data_from_properties_info( # noqa: E501 - {"path": self.path, "properties": {}, "type": self.location} - ) - if obj is not None: - self._object = obj - return obj - if item == "ref": - return self._object._object - - return wrapper - - def __new__(cls, name, bases, attrs): - attrs["__init__"] = attrs.get("__init__", cls.__create_init()) - attrs["__getattr__"] = attrs.get("__getattr__", cls.__create_getattr()) - attrs["reset"] = cls.__create_reset() - attrs["get_path"] = cls.__create_get_path() - return super(PyReferenceObjectMeta, cls).__new__(cls, name, bases, attrs) - - -class PyLocalObjectMeta(PyLocalBaseMeta): - """Metaclass for local object classes.""" - - @classmethod - def __create_init(cls): - def wrapper(self, parent, api_helper, name=""): - """Create the initialization method for 'PyLocalObjectMeta'.""" - self._parent = parent - self._name = name - self._api_helper = api_helper(self) - self._command_names = [] - self.type = "object" - - def update(clss): - for name, cls in clss.__dict__.items(): - if cls.__class__.__name__ in ("PyLocalCommandMeta"): - self._command_names.append(name) - - if cls.__class__.__name__ in ( - "PyLocalPropertyMeta", - "PyLocalObjectMeta", - "PyLocalCommandMeta", - ): - setattr( - self, - name, - cls(self, api_helper, name), - ) - if ( - cls.__class__.__name__ == "PyLocalNamedObjectMeta" - or cls.__class__.__name__ == "PyLocalNamedObjectMetaAbstract" - ): - setattr( - self, - cls.PLURAL, - PyLocalContainer(self, cls, api_helper, cls.PLURAL), - ) - if cls.__class__.__name__ == "PyReferenceObjectMeta": - setattr( - self, - name, - cls(self, cls.PATH, cls.LOCATION, cls.SESSION, name), - ) - for base_class in clss.__bases__: - update(base_class) - - update(self.__class__) - - return wrapper - - @classmethod - def __create_getattribute(cls): - def wrapper(self, name): - obj = object.__getattribute__(self, name) - return obj - - return wrapper - - @classmethod - def __create_updateitem(cls): - def wrapper(self, value): - properties = value - sort_by = None - if hasattr(self, "sort_by"): - sort_by = self.sort_by - elif hasattr(self, "include"): - sort_by = self.include - if sort_by: - sorted_properties = { - prop: properties[prop] for prop in sort_by if prop in properties - } - sorted_properties.update( - {k: v for k, v in properties.items() if k not in sort_by} - ) - properties.clear() - properties.update(sorted_properties) - for name, val in properties.items(): - obj = getattr(self, name) - if obj.__class__.__class__.__name__ == "PyLocalPropertyMeta": - obj.set_state(val) - else: - if obj.__class__.__class__.__name__ == "PyReferenceObjectMeta": - obj = obj.ref - obj.update(val) + def reset(self, path: str, location: str, session_id: str) -> None: + self._path = path + self.location = location + self.session_id = session_id + if hasattr(self, "_object"): + delattr(self, "_object") - wrapper.__doc__ = "Update object." - return wrapper - @classmethod - def __create_get_state(cls): - def wrapper(self, show_attributes=False): - state = {} +ParentT = TypeVar("ParentT") - if not getattr(self, "is_active", True): - return - def update_state(clss): - for name, cls in clss.__dict__.items(): - o = getattr(self, name) - if o is None or name.startswith("_") or name.startswith("__"): - continue +# TODO try poking around this more cause it is kinda what we are doing? +# @dataclass_transform(field_specifiers=(type,)) +class PyLocalObject(PyLocalBase, Generic[ParentT]): + """Local object classes.""" - if cls.__class__.__name__ == "PyReferenceObjectMeta": - if o.LOCATION == "local": - o = o.ref - else: - continue - elif cls.__class__.__name__ == "PyLocalCommandMeta": - args = {} - for arg in o._args: - args[arg] = getattr(o, arg)() - state[name] = args - if ( - cls.__class__.__name__ == "PyLocalObjectMeta" - or cls.__class__.__name__ == "PyReferenceObjectMeta" - ): - if getattr(o, "is_active", True): - state[name] = o(show_attributes) - elif ( - cls.__class__.__name__ == "PyLocalNamedObjectMeta" - or cls.__class__.__name__ == "PyLocalNamedObjectMetaAbstract" - ): - container = getattr(self, cls.PLURAL) - if getattr(container, "is_active", True): - state[cls.PLURAL] = {} - for child_name in container: - o = container[child_name] - if getattr(o, "is_active", True): - state[cls.PLURAL][child_name] = o() - - elif cls.__class__.__name__ == "PyLocalPropertyMeta": - if getattr(o, "is_active", True): - state[name] = o() - attrs = show_attributes and getattr(o, "attributes", None) - if attrs: - for attr in attrs: - state[name + "." + attr] = getattr(o, attr) - - for base_class in clss.__bases__: - update_state(base_class) - - update_state(self.__class__) - return state - - return wrapper - - @classmethod - def __create_setattr(cls): - def wrapper(self, name, value): - attr = getattr(self, name, None) - if attr and attr.__class__.__class__.__name__ == "PyLocalPropertyMeta": - attr.set_state(value) + def __init__( + self, parent: ParentT, api_helper: Callable[[Self], APIHelper], name: str = "" + ): + """Create the initialization method for 'PyLocalObjectMeta'.""" + self._parent = parent + self._name = name + self._api_helper = api_helper(self) + self._command_names = [] + self.type = "object" + + def update(clss: type[PyLocalBase]): + for name, cls in clss.__dict__.items(): + if cls.__name__ in {"PyLocalCommand"}: + self._command_names.append(name) + + if cls.__name__ in { + "PyLocalProperty", + "PyLocalObject", + "PyLocalCommand", + }: + setattr( + self, + name, + cls(self, api_helper, name), + ) + if cls.__name__ in { + "PyLocalNamedObject", + "PyLocalNamedObjectAbstract", + }: + setattr( + self, + cls.PLURAL, + PyLocalContainer(self, cls, api_helper, cls.PLURAL), + ) + if cls.__class__.__name__ == "PyReferenceObject": + setattr( + self, + name, + cls(self, cls.PATH, cls.LOCATION, cls.SESSION, name), + ) + for base_class in clss.__bases__: + update(base_class) + + update(self.__class__) + + def update(self, value: dict[str, Any]): + """Update object.""" + properties = value + sort_by = None + if hasattr(self, "sort_by"): + sort_by = self.sort_by + elif hasattr(self, "include"): + sort_by = self.include + if sort_by: + sorted_properties = { + prop: properties[prop] for prop in sort_by if prop in properties + } + sorted_properties.update( + {k: v for k, v in properties.items() if k not in sort_by} + ) + properties.clear() + properties.update(sorted_properties) + for name, val in properties.items(): + obj = getattr(self, name) + if obj.__class__.__name__ == "PyLocalProperty": + obj.set_state(val) else: - object.__setattr__(self, name, value) + if obj.__class__.__name__ == "PyReferenceObject": + obj = obj.ref + obj.update(val) + + def get_state(self, show_attributes: bool = False) -> dict[str, Any] | None: + state: dict[str, Any] = {} + + if not getattr(self, "is_active", True): + return + + def update_state(clss): + for name, cls in clss.__dict__.items(): + o = getattr(self, name) + if o is None or name.startswith("_") or name.startswith("__"): + continue + + if cls.__name__ == "PyReferenceObject": + if o.LOCATION == "local": + o = o.ref + else: + continue + elif cls.__name__ == "PyLocalCommand": + args = {} + for arg in o._args: + args[arg] = getattr(o, arg)() + state[name] = args + if ( + cls.__name__ == "PyLocalObject" + or cls.__name__ == "PyReferenceObject" + ): + if getattr(o, "is_active", True): + state[name] = o(show_attributes) + elif ( + cls.__name__ == "PyLocalNamedObject" + or cls.__name__ == "PyLocalNamedObjectAbstract" + ): + container = getattr(self, cls.PLURAL) + if getattr(container, "is_active", True): + state[cls.PLURAL] = {} + for child_name in container: + o = container[child_name] + if getattr(o, "is_active", True): + state[cls.PLURAL][child_name] = o() + + elif cls.__name__ == "PyLocalProperty": + if getattr(o, "is_active", True): + state[name] = o() + attrs = show_attributes and getattr(o, "attributes", None) + if attrs: + for attr in attrs: + state[name + "." + attr] = getattr(o, attr) + + for base_class in clss.__bases__: + update_state(base_class) + + update_state(self.__class__) + return state + + __call__ = get_state + + def __setattr__(self, name: str, value: Any): + attr = getattr(self, name, None) + if attr and attr.__class__.__name__ == "PyLocalProperty": + attr.set_state(value) + else: + object.__setattr__(self, name, value) - return wrapper - def __new__(cls, name, bases, attrs): - attrs["__getattribute__"] = cls.__create_getattribute() - attrs["__init__"] = attrs.get("__init__", cls.__create_init()) - if "__call__" not in attrs: - attrs["__call__"] = cls.__create_get_state() - attrs["__setattr__"] = cls.__create_setattr() - attrs["update"] = cls.__create_updateitem() - return super(PyLocalObjectMeta, cls).__new__(cls, name, bases, attrs) +CallKwargs = TypeVar("CallKwargs", bound=TypedDict) -class PyLocalCommandMeta(PyLocalObjectMeta): +class PyLocalCommand(PyLocalObject[ParentT], Generic[ParentT, CallKwargs]): """Local object metaclass.""" - @classmethod - def __create_init(cls): - def wrapper(self, parent, api_helper, name=""): - """Create the initialization method for 'PyLocalObjectMeta'.""" - self._parent = parent - self._name = name - self._api_helper = api_helper(self) - self.type = "object" - self._args = [] - self._command_names = [] - self._exe_cmd = getattr(self, "_exe_cmd") - - def update(clss): - for name, cls in clss.__dict__.items(): - if cls.__class__.__name__ in ( - "PyLocalCommandArgMeta", - "PyLocalPropertyMeta", - ): - self._args.append(name) - setattr( - self, - name, - cls(self, api_helper, name), - ) - for base_class in clss.__bases__: - update(base_class) - - update(self.__class__) - - return wrapper - - @classmethod - def __execute_command(cls): - def wrapper(self, **kwargs): - for arg_name, arg_value in kwargs.items(): - getattr(self, arg_name).set_state(arg_value) - cmd_args = {} - for arg_name in self._args: - cmd_args[arg_name] = getattr(self, arg_name)() - rv = self._exe_cmd(**cmd_args) - return rv + def __init__(self, parent, api_helper: Callable[[Self], APIHelper], name=""): + self._parent = parent + self._name = name + self._api_helper = api_helper(self) + self.type = "object" + self._args = [] + self._command_names = [] + self._exe_cmd = getattr(self, "_exe_cmd") - return wrapper - - def __new__(cls, name, bases, attrs): - attrs["__init__"] = cls.__create_init() - attrs["__call__"] = cls.__execute_command() - return super(PyLocalCommandMeta, cls).__new__(cls, name, bases, attrs) - - -class PyLocalNamedObjectMeta(PyLocalObjectMeta): - """Metaclass for local named object classes.""" - - @classmethod - def __create_init(cls): - def wrapper(self, name, parent, api_helper): - """Create the initialization method for 'PyLocalNamedObjectMeta'.""" - self._name = name - self._api_helper = api_helper(self) - self._parent = parent - self._command_names = [] - self.type = "object" - - def update(clss): - for name, cls in clss.__dict__.items(): - if cls.__class__.__name__ in ("PyLocalCommandMeta"): - self._command_names.append(name) - - if cls.__class__.__name__ in ( - "PyLocalPropertyMeta", - "PyLocalObjectMeta", - "PyLocalCommandMeta", - ): - # delete old property if overridden - if getattr(self, name).__class__.__name__ == name: - delattr(self, name) - setattr( - self, - name, - cls(self, api_helper, name), - ) - elif ( - cls.__class__.__name__ == "PyLocalNamedObjectMeta" - or cls.__class__.__name__ == "PyLocalNamedObjectMetaAbstract" - ): - setattr( - self, - cls.PLURAL, - PyLocalContainer(self, cls, api_helper, cls.PLURAL), - ) - elif cls.__class__.__name__ == "PyReferenceObjectMeta": - setattr( - self, name, cls(self, cls.PATH, cls.LOCATION, cls.SESSION) - ) - for base_class in clss.__bases__: - update(base_class) - - update(self.__class__) - - return wrapper - - def __new__(cls, name, bases, attrs): - attrs["__init__"] = cls.__create_init() - return super(PyLocalNamedObjectMeta, cls).__new__(cls, name, bases, attrs) - - -class PyLocalNamedObjectMetaAbstract(ABCMeta, PyLocalNamedObjectMeta): - """Local named object abstract metaclass.""" + def update(clss): + for name, cls in clss.__dict__.items(): + if cls.__class__.__name__ in ( + "PyLocalCommandArgMeta", + "PyLocalPropertyMeta", + ): + self._args.append(name) + setattr( + self, + name, + cls(self, api_helper, name), + ) + for base_class in clss.__bases__: + update(base_class) + + update(self.__class__) + + def __call__(self, **kwargs: Unpack[CallKwargs]): + for arg_name, arg_value in kwargs.items(): + getattr(self, arg_name).set_state(arg_value) + cmd_args = {} + for arg_name in self._args: + cmd_args[arg_name] = getattr(self, arg_name)() + return self._exe_cmd(**cmd_args) + + +class PyLocalNamedObject(PyLocalObject): + """Base class for local named object classes.""" + + def __init__(self, name: str, parent, api_helper: Callable[[Self], APIHelper]): + self._name = name + self._api_helper = api_helper(self) + self._parent = parent + self._command_names = [] + self.type = "object" + + def update(clss): + for name, cls in clss.__dict__.items(): + if cls.__name__ in ("PyLocalCommand"): + self._command_names.append(name) + + if cls.__name__ in ( + "PyLocalProperty", + "PyLocalObject", + "PyLocalCommand", + ): + # delete old property if overridden + if getattr(self, name).__name__ == name: + delattr(self, name) + setattr( + self, + name, + cls(self, api_helper, name), + ) + elif ( # TODO these are gone, can we not use instance checks here? + cls.__name__ == "PyLocalNamedObject" + or cls.__name__ == "PyLocalNamedObjectAbstract" + ): + setattr( + self, + cls.PLURAL, + PyLocalContainer(self, cls, api_helper, cls.PLURAL), + ) + elif cls.__name__ == "PyReferenceObject": + setattr(self, name, cls(self, cls.PATH, cls.LOCATION, cls.SESSION)) + for base_class in clss.__bases__: + update(base_class) + + update(self.__class__) + + if TYPE_CHECKING: + + def create(cls) -> Self: ... + + +class PyLocalNamedObjectAbstract(ABC, PyLocalNamedObject): + """Local named object abstract class.""" pass -class PyLocalContainer(MutableMapping): +DefnT = TypeVar("DefnT", bound=GraphicsDefn | PlotDefn, default=GraphicsDefn | PlotDefn) + + +def if_type_checking_instantiate(type: type[T]) -> T: + return cast(T, type) # this is hopefully obviously unsafe + + +class _DeleteKwargs(TypedDict, total=False): + names: list[str] + + +class _CreateKwargs(TypedDict, total=False): + name: str | None + + +class PyLocalContainer(MutableMapping[str, DefnT]): """Local container for named objects.""" - def __init__(self, parent, object_class, api_helper, name=""): + def __init__( + self, + parent: "Container", + object_class: type[DefnT], + api_helper: Callable[[Self], APIHelper], + name: str = "", + ): """Initialize the 'PyLocalContainer' object.""" self._parent = parent self._name = name self.__object_class = object_class - self._local_collection = {} + self._local_collection: dict[str, DefnT] = {} self.__api_helper = api_helper self.type = "named-object" self._command_names = [] @@ -734,7 +693,7 @@ def __init__(self, parent, object_class, api_helper, name=""): PyLocalContainer.exclude = property( lambda self: self.__object_class.EXCLUDE(self) ) - if hasattr(object_class, "INCLUDE"): + if hasattr(object_class, "INCLUDE"): # TODO sort_by? PyLocalContainer.include = property( lambda self: self.__object_class.INCLUDE(self) ) @@ -766,12 +725,6 @@ def __init__(self, parent, object_class, api_helper, name=""): cls(self, api_helper, name), ) - def update(self, value): - """Updates this object with the provided dictionary.""" - for name, val in value.items(): - o = self[name] - o.update(val) - def get_root(self, obj=None): """Returns the top-most parent object.""" obj = self if obj is None else obj @@ -780,103 +733,102 @@ def get_root(self, obj=None): parent = self.get_root(obj._parent) return parent - def get_session(self, obj=None): + def get_session(self, obj=None) -> "Solver": """Returns the session object.""" root = self.get_root(obj) return root.session - def get_path(self): + def get_path(self) -> str: """Path to the current object.""" if getattr(self, "_parent", None): return self._parent.get_path() + "/" + self._name return self._name @property - def path(self): + def path(self) -> str: """Path to the current object.""" return self.get_path() @property - def session(self): + def session(self) -> "Solver": """Returns the session object.""" return self.get_session() - def get_session_handle(self, obj=None): - """Returns the session-handle object.""" - root = self.get_root(obj) - return getattr(root, "session_handle", None) - - @property - def session_handle(self): - """Returns the session-handle object.""" - return self.get_session_handle() - - def __iter__(self): + @override + def __iter__(self) -> Iterator[str]: return iter(self._local_collection) - def __len__(self): + @override + def __len__(self) -> int: return len(self._local_collection) - def __getitem__(self, name): + @override + def __getitem__(self, name: str) -> DefnT: o = self._local_collection.get(name, None) if not o: o = self._local_collection[name] = self.__object_class( name, self, self.__api_helper ) - on_create = getattr(self._PyLocalContainer__object_class, "on_create", None) + on_create = getattr(self.__object_class, "on_create", None) if on_create: on_create(self, name) return o - def __setitem__(self, name, value): + @override + def __setitem__(self, name: str, value: DefnT) -> None: o = self[name] o.update(value) - def __delitem__(self, name): + @override + def __delitem__(self, name: str) -> None: del self._local_collection[name] - on_delete = getattr(self._PyLocalContainer__object_class, "on_delete", None) + on_delete = getattr(self.__object_class, "on_delete", None) if on_delete: on_delete(self, name) - def _get_unique_chid_name(self): + def _get_unique_chid_name(self) -> str: children = list(self) index = 0 while True: - unique_name = ( - f"{self._PyLocalContainer__object_class.__name__.lower()}-{index}" - ) + unique_name = f"{self.__object_class.__name__.lower()}-{index}" if unique_name not in children: break index += 1 return unique_name - class Delete(metaclass=PyLocalCommandMeta): + class Delete(PyLocalCommand[Self, _DeleteKwargs]): """Local delete command.""" - def _exe_cmd(self, names): + def _exe_cmd(self, names: list[str]) -> None: for item in names: self._parent.__delitem__(item) - class names(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class names(PyLocalProperty[list[str]]): """Local names property.""" - value: List[str] = [] + value = [] @Attribute def allowed_values(self): """Get allowed values.""" return list(self._parent._parent) - class Create(metaclass=PyLocalCommandMeta): + class Create(PyLocalCommand[Self, _CreateKwargs]): """Local create command.""" - def _exe_cmd(self, name=None): + def _exe_cmd(self, name: str | None = None): if name is None: name = self._parent._get_unique_chid_name() new_object = self._parent.__getitem__(name) return new_object._name - class name(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class name(PyLocalProperty[str | None]): """Local name property.""" - value: str = None + value = None + + # added by __init__ + delete: Delete + create: Create diff --git a/src/ansys/fluent/interface/post_objects/post_helper.py b/src/ansys/fluent/interface/post_objects/post_helper.py index 48db651f..6b42f139 100644 --- a/src/ansys/fluent/interface/post_objects/post_helper.py +++ b/src/ansys/fluent/interface/post_objects/post_helper.py @@ -55,20 +55,20 @@ def __init__(self, obj): self._surface_name_on_server = self.surface_name_on_server(obj._name) @staticmethod - def surface_name_on_server(local_surface_name): + def surface_name_on_server(local_surface_name: str) -> str: """Return the surface name on server.""" return local_surface_name.lower() def _get_api_handle(self): return self.obj.get_root().session.results.surfaces - def _delete_if_exists_on_server(self): + def _delete_if_exists_on_server(self) -> None: field_data = self.obj._api_helper.field_data() surfaces_list = list(field_data.surfaces()) if self._surface_name_on_server in surfaces_list: self.delete_surface_on_server() - def create_surface_on_server(self): + def create_surface_on_server(self) -> None: """Create the surface on server. Raises @@ -135,21 +135,21 @@ def create_surface_on_server(self): if self._surface_name_on_server not in surfaces_list: raise SurfaceCreationError() - def delete_surface_on_server(self): + def delete_surface_on_server(self) -> None: """Deletes the surface on server.""" if self.obj.definition.type() == "iso-surface": del self._get_api_handle().iso_surface[self._surface_name_on_server] elif self.obj.definition.type() == "plane-surface": del self._get_api_handle().plane_surface[self._surface_name_on_server] - def __init__(self, obj): + def __init__(self, obj: Surface): """__init__ method of PostAPIHelper class.""" self.obj = obj self.field_data = lambda: obj.get_root().session.fields.field_data if obj.__class__.__name__ == "Surface": self.surface_api = PostAPIHelper._SurfaceAPI(obj) - def remote_surface_name(self, local_surface_name): + def remote_surface_name(self, local_surface_name: str): """Return the surface name.""" local_surfaces_provider = self.obj.get_root()._local_surfaces_provider() @@ -159,7 +159,7 @@ def remote_surface_name(self, local_surface_name): else: return local_surface_name - def get_field_unit(self, field): + def get_field_unit(self, field: str) -> str | None: """Return the unit of the field.""" session = self.obj.get_root().session if FluentVersion(session.scheme.version) < FluentVersion.v252: @@ -172,11 +172,11 @@ def get_field_unit(self, field): fields_info = self.field_data().scalar_fields() return get_si_unit_for_fluent_quantity(fields_info[field]["quantity_name"]) - def _field_unit_quantity(self, field): + def _field_unit_quantity(self, field: str) -> str: scheme_eval_str = f"(cdr (assq 'units (%fill-render-info '{field})))" return self._scheme_str_to_py_list(scheme_eval_str)[0] - def _scheme_str_to_py_list(self, scheme_eval_str): + def _scheme_str_to_py_list(self, scheme_eval_str: str) -> list[str]: session = self.obj.get_root().session if hasattr(session, "scheme_eval"): str_val = session.scheme.string_eval(scheme_eval_str) diff --git a/src/ansys/fluent/interface/post_objects/post_object_definitions.py b/src/ansys/fluent/interface/post_objects/post_object_definitions.py index 29309677..bf78df60 100644 --- a/src/ansys/fluent/interface/post_objects/post_object_definitions.py +++ b/src/ansys/fluent/interface/post_objects/post_object_definitions.py @@ -22,24 +22,36 @@ """Module providing visualization objects definition.""" +import abc from abc import abstractmethod +from collections.abc import Callable, Sequence import logging -from typing import List, NamedTuple +from typing import TYPE_CHECKING, Literal, NamedTuple, Protocol, Self, cast, final from ansys.fluent.interface.post_objects.meta import ( Attribute, - PyLocalNamedObjectMetaAbstract, - PyLocalObjectMeta, - PyLocalPropertyMeta, + PyLocalNamedObject, + PyLocalObject, + PyLocalProperty, + if_type_checking_instantiate, ) +if TYPE_CHECKING: + from ansys.fluent.interface.post_objects.post_objects_container import Container + logger = logging.getLogger("pyfluent.post_objects") -class BasePostObjectDefn: +class BasePostObjectDefn(Protocol, metaclass=abc.ABCMeta): """Base class for visualization objects.""" - def _pre_display(self): + # @abc.abstractmethod + # def get_root(self) -> Container: + # raise NotImplementedError + + surfaces: Callable[[], Sequence[str]] + + def _pre_display(self) -> None: local_surfaces_provider = self.get_root()._local_surfaces_provider() for surf_name in self.surfaces(): if surf_name in list(local_surfaces_provider): @@ -47,7 +59,7 @@ def _pre_display(self): surf_api = surf_obj._api_helper.surface_api surf_api.create_surface_on_server() - def _post_display(self): + def _post_display(self) -> None: local_surfaces_provider = self.get_root()._local_surfaces_provider() for surf_name in self.surfaces(): if surf_name in list(local_surfaces_provider): @@ -56,11 +68,11 @@ def _post_display(self): surf_api.delete_surface_on_server() -class GraphicsDefn(BasePostObjectDefn, metaclass=PyLocalNamedObjectMetaAbstract): +class GraphicsDefn(BasePostObjectDefn, PyLocalNamedObject, abc.ABC): """Abstract base class for graphics objects.""" @abstractmethod - def display(self, window_id: str | None = None): + def display(self, window_id: str | None = None) -> None: """Display graphics. Parameters @@ -71,11 +83,11 @@ def display(self, window_id: str | None = None): pass -class PlotDefn(BasePostObjectDefn, metaclass=PyLocalNamedObjectMetaAbstract): +class PlotDefn(BasePostObjectDefn, PyLocalNamedObject, abc.ABC): """Abstract base class for plot objects.""" @abstractmethod - def plot(self, window_id: str | None = None): + def plot(self, window_id: str | None = None) -> None: """Draw plot. Parameters @@ -86,77 +98,82 @@ def plot(self, window_id: str | None = None): pass -class Vector(NamedTuple): - """Class for vector definition.""" - - x: float - y: float - z: float - - -class MonitorDefn(PlotDefn): +class MonitorDefn(PlotDefn, abc.ABC): """Monitor Definition.""" PLURAL = "Monitors" - class monitor_set_name(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class monitor_set_name(PyLocalProperty[str | None]): """Monitor set name.""" - value: str = None + value = None @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Monitor set allowed values.""" return self.monitors.get_monitor_set_names() -class XYPlotDefn(PlotDefn): +class XYPlotDefn(PlotDefn, abc.ABC): """XYPlot Definition.""" PLURAL = "XYPlots" - class node_values(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class node_values(PyLocalProperty[bool]): """Plot nodal values.""" - value: bool = True + value = True - class boundary_values(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class boundary_values(PyLocalProperty[bool]): """Plot Boundary values.""" - value: bool = True + value = True - class direction_vector(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class direction_vector(PyLocalProperty[tuple[int, int, int]]): """Direction Vector.""" - value: Vector = [1, 0, 0] + value = (1, 0, 0) - class y_axis_function(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class y_axis_function(PyLocalProperty[str | None]): """Y Axis Function.""" - value: str = None + value = None @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Y axis function allowed values.""" return list(self.field_data.scalar_fields()) - class x_axis_function(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class x_axis_function(PyLocalProperty[Literal["direction-vector"]]): """X Axis Function.""" - value: str = "direction-vector" + value = "direction-vector" @Attribute - def allowed_values(self): + def allowed_values(self) -> Sequence[Literal["direction-vector"]]: """X axis function allowed values.""" return ["direction-vector"] - class surfaces(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class surfaces(PyLocalProperty[list[str]]): """List of surfaces for plotting.""" - value: List[str] = [] + value = [] @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Surface list allowed values.""" return list(self.field_data.surfaces()) + list( self.get_root()._local_surfaces_provider() @@ -168,32 +185,36 @@ class MeshDefn(GraphicsDefn): PLURAL = "Meshes" - class surfaces(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class surfaces(PyLocalProperty[list[str]]): """List of surfaces for mesh graphics.""" - value: List[str] = [] + value = [] @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Surface list allowed values.""" return list(self.field_data.surfaces()) + list( self.get_root()._local_surfaces_provider() ) - class show_edges(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class show_edges(PyLocalProperty[bool]): """Show edges for mesh.""" - value: bool = False + value = False - class show_nodes(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class show_nodes(PyLocalProperty[bool]): """Show nodes for mesh.""" - value: bool = False + value = False - class show_faces(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class show_faces(PyLocalProperty[bool]): """Show faces for mesh.""" - value: bool = True + value = True class PathlinesDefn(GraphicsDefn): @@ -201,23 +222,25 @@ class PathlinesDefn(GraphicsDefn): PLURAL = "Pathlines" - class field(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class field(PyLocalProperty[str | None]): """Pathlines field.""" - value: str = None + value = None @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Field allowed values.""" return list(self.field_data.scalar_fields()) - class surfaces(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class surfaces(PyLocalProperty[list[str]]): """List of surfaces for pathlines.""" - value: List[str] = [] + value = [] @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Surface list allowed values.""" return list(self.field_data.surfaces()) + list( self.get_root()._local_surfaces_provider() @@ -234,222 +257,281 @@ def name(self) -> str: """Return name of the surface.""" return self._name - class show_edges(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class show_edges(PyLocalProperty[bool]): """Show edges for surface.""" - value: bool = True + value = True - class definition(metaclass=PyLocalObjectMeta): + class definition(PyLocalObject[Self]): """Specify surface definition type.""" - class type(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class type(PyLocalProperty[Literal["plane-surface", "iso-surface"]]): """Surface type.""" - value: str = "iso-surface" + value = "iso-surface" @Attribute - def allowed_values(self): + def allowed_values( + self, + ) -> Sequence[Literal["plane-surface", "iso-surface"]]: """Surface type allowed values.""" return ["plane-surface", "iso-surface"] - class plane_surface(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class plane_surface(PyLocalObject[Self]): """Plane surface definition.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.type() == "plane-surface" - class creation_method(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class creation_method( + PyLocalProperty[ + Literal["xy-plane", "yz-plane", "zx-plane", "point-and-normal"] + ] + ): """Creation Method.""" - value: str = "xy-plane" + value = "xy-plane" @Attribute - def allowed_values(self): + def allowed_values( + self, + ) -> Sequence[ + Literal["xy-plane", "yz-plane", "zx-plane", "point-and-normal"] + ]: """Surface type allowed values.""" return ["xy-plane", "yz-plane", "zx-plane", "point-and-normal"] - class point(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class point(PyLocalObject[Self]): """Point entry for point-and-normal surface.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.creation_method() == "point-and-normal" - class x(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class x(PyLocalProperty[float]): """X value.""" - value: float = 0 + value = 0 @Attribute - def range(self): + def range(self) -> tuple[float, float]: """X value range.""" - return self.field_data.scalar_fields.range("x-coordinate", True) + return cast( + tuple[float, float], + cast( + object, + self.field_data.scalar_fields.range( + "x-coordinate", True + ), + ), + ) - class y(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class y(PyLocalProperty[float]): """Y value.""" - value: float = 0 + value = 0 @Attribute - def range(self): + def range(self) -> tuple[float, float]: """Y value range.""" - return self.field_data.scalar_fields.range("y-coordinate", True) + return cast( + tuple[float, float], + cast( + object, + self.field_data.scalar_fields.range( + "y-coordinate", True + ), + ), + ) - class z(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class z(PyLocalProperty[float]): """Z value.""" - value: float = 0 + value = 0 @Attribute - def range(self): + def range(self) -> tuple[float, float]: """Z value range.""" - return self.field_data.scalar_fields.range("z-coordinate", True) + return cast( + tuple[float, float], + cast( + object, + self.field_data.scalar_fields.range( + "z-coordinate", True + ), + ), + ) - class normal(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class normal(PyLocalObject[Self]): """Normal entry for point-and-normal surface.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.creation_method() == "point-and-normal" - class x(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class x(PyLocalProperty[float]): """X value.""" - value: float = 0 + value = 0 @Attribute - def range(self): + def range(self) -> list[int]: """X value range.""" return [-1, 1] - class y(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class y(PyLocalProperty[float]): """Y value.""" - value: float = 0 + value = 0 @Attribute - def range(self): + def range(self) -> list[int]: """Y value range.""" return [-1, 1] - class z(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class z(PyLocalProperty[float]): """Z value.""" - value: float = 0 + value = 0 @Attribute - def range(self): + def range(self) -> list[int]: """Z value range.""" return [-1, 1] - class xy_plane(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class xy_plane(PyLocalObject[Self]): """XY Plane definition.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.creation_method() == "xy-plane" - class z(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class z(PyLocalProperty[float]): """Z value.""" - value: float = 0 + value = 0 @Attribute - def range(self): + def range(self) -> tuple[float, float]: """Z value range.""" return self.field_data.scalar_fields.range("z-coordinate", True) - class yz_plane(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class yz_plane(PyLocalObject[Self]): """YZ Plane definition.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.creation_method() == "yz-plane" - class x(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class x(PyLocalProperty[float]): """X value.""" - value: float = 0 + value = 0 @Attribute - def range(self): + def range(self) -> tuple[float, float]: """X value range.""" return self.field_data.scalar_fields.range("x-coordinate", True) - class zx_plane(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class zx_plane(PyLocalObject[Self]): """ZX Plane definition.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.creation_method() == "zx-plane" - class y(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class y(PyLocalProperty[float]): """Y value.""" - value: float = 0 + value = 0 @Attribute - def range(self): + def range(self) -> tuple[float, float]: """Y value range.""" return self.field_data.scalar_fields.range("y-coordinate", True) - class iso_surface(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class iso_surface(PyLocalObject[Self]): """Iso surface definition.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.type() == "iso-surface" - class field(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class field(PyLocalProperty[str | None]): """Iso surface field.""" - value: str = None + value = None @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Field allowed values.""" return list(self.field_data.scalar_fields()) - class rendering(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class rendering(PyLocalProperty[Literal["mesh", "contour"]]): """Iso surface rendering.""" - value: str = "mesh" + value = "mesh" @Attribute - def allowed_values(self): + def allowed_values(self) -> Sequence[Literal["mesh", "contour"]]: """Surface rendering allowed values.""" return ["mesh", "contour"] - class iso_value(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class iso_value(PyLocalProperty[float | None]): """Iso value for field.""" - _value: float = None + _value = None - def _reset_on_change(self): + def _reset_on_change(self) -> list: return [self._parent.field] @property - def value(self): - """Iso value property setter.""" + def value(self) -> float | None: + """Iso value property.""" if getattr(self, "_value", None) is None: rnge = self.range self._value = (rnge[0] + rnge[1]) / 2.0 if rnge else None return self._value @value.setter - def value(self, value): + def value(self, value: float | None) -> None: self._value = value @Attribute - def range(self): + def range(self) -> tuple[float, float] | None: """Iso value range.""" field = self._parent.field() if field: @@ -461,40 +543,44 @@ class ContourDefn(GraphicsDefn): PLURAL = "Contours" - class field(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class field(PyLocalProperty[str | None]): """Contour field.""" - value: str = None + value = None @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Field allowed values.""" return list(self.field_data.scalar_fields()) - class surfaces(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class surfaces(PyLocalProperty[list[str]]): """Contour surfaces.""" - value: List[str] = [] + value = [] @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Surfaces list allowed values.""" return list(self.field_data.surfaces()) + list( self.get_root()._local_surfaces_provider() ) - class filled(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class filled(PyLocalProperty[bool]): """Draw filled contour.""" - value: bool = True + value = True - class node_values(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class node_values(PyLocalProperty[bool]): """Draw nodal data.""" - _value: bool = True + _value = True @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" filled = self.get_ancestors_by_type(ContourDefn).filled() auto_range_off = self.get_ancestors_by_type( @@ -508,87 +594,99 @@ def is_active(self): return True @property - def value(self): + def value(self) -> bool: """Node value property setter.""" if self.is_active is False: return True return self._value @value.setter - def value(self, value): + def value(self, value: bool) -> None: if value is False and self.is_active is False: raise ValueError( "For unfilled and clipped contours, node values must be displayed. " ) self._value = value - class boundary_values(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class boundary_values(PyLocalProperty[bool]): """Draw boundary values.""" - value: bool = False + value = False - class contour_lines(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class contour_lines(PyLocalProperty[bool]): """Draw contour lines.""" - value: bool = False + value = False - class show_edges(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class show_edges(PyLocalProperty[bool]): """Show edges.""" - value: bool = False + value = False - class range(metaclass=PyLocalObjectMeta): + class range(PyLocalObject[Self]): """Range definition.""" - class option(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class option(PyLocalProperty[Literal["auto-range-on", "auto-range-off"]]): """Range option.""" - value: str = "auto-range-on" + value = "auto-range-on" @Attribute - def allowed_values(self): + def allowed_values( + self, + ) -> Sequence[Literal["auto-range-on", "auto-range-off"]]: """Range option allowed values.""" return ["auto-range-on", "auto-range-off"] - class auto_range_on(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class auto_range_on(PyLocalObject[Self]): """Auto range on definition.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.option() == "auto-range-on" - class global_range(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class global_range(PyLocalProperty[bool]): """Show global range.""" - value: bool = False + value = False - class auto_range_off(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class auto_range_off(PyLocalObject[Self]): """Auto range off definition.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.option() == "auto-range-off" - class clip_to_range(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class clip_to_range(PyLocalProperty[bool]): """Clip contour within range.""" - value: bool = False + value = False - class minimum(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class minimum(PyLocalProperty[float | None]): """Range minimum.""" - _value: float = None + _value = None - def _reset_on_change(self): + def _reset_on_change(self) -> list: return [ self.get_ancestors_by_type(ContourDefn).field, self.get_ancestors_by_type(ContourDefn).node_values, ] @property - def value(self): + def value(self) -> float | None: """Range minimum property setter.""" if getattr(self, "_value", None) is None: field = self.get_ancestors_by_type(ContourDefn).field() @@ -602,22 +700,23 @@ def value(self): return self._value @value.setter - def value(self, value): + def value(self, value: float | None) -> None: self._value = value - class maximum(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class maximum(PyLocalProperty[float | None]): """Range maximum.""" - _value: float = None + _value = None - def _reset_on_change(self): + def _reset_on_change(self) -> list: return [ self.get_ancestors_by_type(ContourDefn).field, self.get_ancestors_by_type(ContourDefn).node_values, ] @property - def value(self): + def value(self) -> float | None: """Range maximum property setter.""" if getattr(self, "_value", None) is None: field = self.get_ancestors_by_type(ContourDefn).field() @@ -632,7 +731,7 @@ def value(self): return self._value @value.setter - def value(self, value): + def value(self, value: float | None) -> None: self._value = value @@ -641,99 +740,115 @@ class VectorDefn(GraphicsDefn): PLURAL = "Vectors" - class vectors_of(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class vectors_of(PyLocalProperty[str | None]): """Vector type.""" - value: str = None + value = None @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Vectors of allowed values.""" return list(self.field_data.vector_fields()) - class field(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class field(PyLocalProperty[str | None]): """Vector color field.""" - value: str = None + value = None @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Field allowed values.""" return list(self.field_data.scalar_fields()) - class surfaces(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class surfaces(PyLocalProperty[list[str]]): """List of surfaces for vector graphics.""" - value: List[str] = [] + value = [] @Attribute - def allowed_values(self): + def allowed_values(self) -> list[str]: """Surface list allowed values.""" return list(self.field_data.surfaces()) + list( self.get_root()._local_surfaces_provider() ) - class scale(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class scale(PyLocalProperty[float]): """Vector scale.""" - value: float = 1.0 + value = 1.0 - class skip(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class skip(PyLocalProperty[int]): """Vector skip.""" - value: int = 0 + value = 0 - class show_edges(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class show_edges(PyLocalProperty[bool]): """Show edges.""" - value: bool = False + value = False - class range(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class range(PyLocalObject[Self]): """Range definition.""" - class option(metaclass=PyLocalPropertyMeta): + @final + @if_type_checking_instantiate + class option(PyLocalProperty[Literal["auto-range-on", "auto-range-off"]]): """Range option.""" - value: str = "auto-range-on" + value = "auto-range-on" @Attribute - def allowed_values(self): + def allowed_values( + self, + ) -> Sequence[Literal["auto-range-on", "auto-range-off"]]: """Range option allowed values.""" return ["auto-range-on", "auto-range-off"] - class auto_range_on(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class auto_range_on(PyLocalObject[Self]): """Auto range on definition.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.option() == "auto-range-on" - class global_range(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class global_range(PyLocalProperty[bool]): """Show global range.""" - value: bool = False + value = False - class auto_range_off(metaclass=PyLocalObjectMeta): + @if_type_checking_instantiate + class auto_range_off(PyLocalObject[Self]): """Auto range off definition.""" @Attribute - def is_active(self): + def is_active(self) -> bool: """Check whether current object is active or not.""" return self._parent.option() == "auto-range-off" - class clip_to_range(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class clip_to_range(PyLocalProperty[bool]): """Clip vector within range.""" - value: bool = False + value = False - class minimum(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class minimum(PyLocalProperty[float | None]): """Range minimum.""" - _value: float = None + _value = None @property - def value(self): + def value(self) -> float | None: """Range minimum property setter.""" if getattr(self, "_value", None) is None: field_data = self.field_data @@ -745,16 +860,17 @@ def value(self): return self._value @value.setter - def value(self, value): + def value(self, value: float | None) -> None: self._value = value - class maximum(metaclass=PyLocalPropertyMeta): + @if_type_checking_instantiate + class maximum(PyLocalProperty[float | None]): """Range maximum.""" - _value: float = None + _value = None @property - def value(self): + def value(self) -> float | None: """Range maximum property setter.""" if getattr(self, "_value", None) is None: field_data = self.field_data @@ -766,5 +882,5 @@ def value(self): return self._value @value.setter - def value(self, value): + def value(self, value: float | None) -> None: self._value = value diff --git a/src/ansys/fluent/interface/post_objects/post_objects_container.py b/src/ansys/fluent/interface/post_objects/post_objects_container.py index 02d34ec7..7334ae5c 100644 --- a/src/ansys/fluent/interface/post_objects/post_objects_container.py +++ b/src/ansys/fluent/interface/post_objects/post_objects_container.py @@ -24,7 +24,11 @@ import inspect +from ansys.fluent.core.session import BaseSession + from ansys.fluent.interface.post_objects.meta import PyLocalContainer +from ansys.fluent.visualization import Contour, Graphics, Plots, Surface, Vector +from ansys.fluent.visualization.graphics.graphics_objects import Mesh class Container: @@ -48,8 +52,8 @@ class Container: def __init__( self, - session, - container_type, + session: BaseSession, + container_type: type[Plots | Graphics], module, post_api_helper, local_surfaces_provider=None, @@ -218,6 +222,10 @@ class Graphics(Container): """ _sessions_state = {} + Meshes: PyLocalContainer[Mesh] + Surfaces: PyLocalContainer[Surface] + Contours: PyLocalContainer[Contour] + Vectors: PyLocalContainer[Vector] def __init__(self, session, module, post_api_helper, local_surfaces_provider=None): """__init__ method of Graphics class.""" @@ -225,19 +233,19 @@ def __init__(self, session, module, post_api_helper, local_surfaces_provider=Non session, self.__class__, module, post_api_helper, local_surfaces_provider ) - def add_outline_mesh(self): + def add_outline_mesh(self) -> Mesh | None: """Add a mesh outline. - Parameters - ---------- - None - Returns ------- - None + Mesh | None + The outline mesh object if it exists, otherwise ``None``. """ - meshes = getattr(self, "Meshes", None) - if meshes is not None: + try: + meshes = self.Meshes + except AttributeError: + return + else: outline_mesh_id = "mesh-outline" outline_mesh = meshes[outline_mesh_id] outline_mesh.surfaces = [ diff --git a/src/ansys/fluent/interface/post_objects/singleton_meta.py b/src/ansys/fluent/interface/post_objects/singleton_meta.py index 202fe251..ebbae4a8 100644 --- a/src/ansys/fluent/interface/post_objects/singleton_meta.py +++ b/src/ansys/fluent/interface/post_objects/singleton_meta.py @@ -23,20 +23,23 @@ """Provides a module for metaclasses.""" from abc import ABCMeta +from typing import TYPE_CHECKING, Any, Self class SingletonMeta(type): """Provides the metaclass for the singleton type.""" - _single_instance = None + _single_instance: Self | None = None # pyright: ignore[reportGeneralTypeIssues] - def __call__(cls, *args, **kwargs): - if not cls._single_instance: - cls._single_instance = super(SingletonMeta, cls).__call__(*args, **kwargs) - return cls._single_instance + if ( + not TYPE_CHECKING + ): # some type checkers may see this and erase the type otherwise + + def __call__(cls, *args: Any, **kwargs: Any) -> Self: + if not cls._single_instance: + cls._single_instance = super().__call__(*args, **kwargs) + return cls._single_instance class AbstractSingletonMeta(ABCMeta, SingletonMeta): """Provides the metaclass for the abstract singleton type.""" - - pass diff --git a/src/ansys/fluent/visualization/__init__.py b/src/ansys/fluent/visualization/__init__.py index ee40503f..e306d05c 100644 --- a/src/ansys/fluent/visualization/__init__.py +++ b/src/ansys/fluent/visualization/__init__.py @@ -46,19 +46,19 @@ def version_info() -> str: return _VERSION_INFO if _VERSION_INFO is not None else __version__ -from ansys.fluent.visualization.config import config, get_config, set_config -from ansys.fluent.visualization.containers import ( # noqa: F401 - Contour, - IsoSurface, - Mesh, - Monitor, - Pathline, - PlaneSurface, - Surface, - Vector, - XYPlot, -) -from ansys.fluent.visualization.graphics import Graphics # noqa: F401 -from ansys.fluent.visualization.plotter import Plots # noqa: F401 -from ansys.fluent.visualization.registrar import register_renderer -from ansys.fluent.visualization.renderer import GraphicsWindow +from ansys.fluent.visualization.config import config as config +from ansys.fluent.visualization.config import get_config as get_config +from ansys.fluent.visualization.config import set_config as set_config +from ansys.fluent.visualization.containers import Contour as Contour +from ansys.fluent.visualization.containers import IsoSurface as IsoSurface +from ansys.fluent.visualization.containers import Mesh as Mesh +from ansys.fluent.visualization.containers import Monitor as Monitor +from ansys.fluent.visualization.containers import Pathline as Pathline +from ansys.fluent.visualization.containers import PlaneSurface as PlaneSurface +from ansys.fluent.visualization.containers import Surface as Surface +from ansys.fluent.visualization.containers import Vector as Vector +from ansys.fluent.visualization.containers import XYPlot as XYPlot +from ansys.fluent.visualization.graphics import Graphics as Graphics +from ansys.fluent.visualization.plotter import Plots as Plots +from ansys.fluent.visualization.registrar import register_renderer as register_renderer +from ansys.fluent.visualization.renderer import GraphicsWindow as GraphicsWindow diff --git a/src/ansys/fluent/visualization/base/renderer.py b/src/ansys/fluent/visualization/base/renderer.py index 11652a98..1673b496 100644 --- a/src/ansys/fluent/visualization/base/renderer.py +++ b/src/ansys/fluent/visualization/base/renderer.py @@ -23,13 +23,27 @@ """Abstract module providing rendering functionality.""" from abc import ABC, abstractmethod +from typing import Any, TypedDict + + +class SurfaceToRender(TypedDict): + """TypedDict for mesh surface definition.""" + + data: object + position: tuple[int, int] + opacity: float + title: str + kwargs: dict[str, Any] + + +SubPlot = list[SurfaceToRender] class AbstractRenderer(ABC): """Abstract class for rendering graphics and plots.""" @abstractmethod - def render(self, meshes: list[list[dict]]) -> None: + def render(self, meshes: list[SubPlot]) -> None: """Render graphics and plots in a window. Parameters @@ -49,7 +63,7 @@ def render(self, meshes: list[list[dict]]) -> None: - 'data': The mesh or 2d plot object to be plotted. - 'position': tuple(int, int), Location of subplot. Defaults to (0, 0). - - 'opacity': int, Sets the transparency of the subplot. Defaults to 1, + - 'opacity': float, Sets the transparency of the subplot. Defaults to 1, meaning fully opaque. - 'title': str, Title of the subplot. - 'kwargs': A dictionary of additional keyword arguments passed diff --git a/src/ansys/fluent/visualization/containers.py b/src/ansys/fluent/visualization/containers.py index e830249d..8a473e6e 100644 --- a/src/ansys/fluent/visualization/containers.py +++ b/src/ansys/fluent/visualization/containers.py @@ -21,38 +21,65 @@ # SOFTWARE. """Containers for graphics.""" + +from typing import TYPE_CHECKING, Any, Literal, Self, TypedDict, Unpack import warnings from ansys.fluent.core.field_data_interfaces import _to_field_name_str from ansys.fluent.core.utils.context_managers import _get_active_session from ansys.units import VariableDescriptor +from typing_extensions import override from ansys.fluent.visualization.graphics import Graphics from ansys.fluent.visualization.plotter import Plots +if TYPE_CHECKING: + from ansys.fluent.core.session import BaseSession + + from ansys.fluent.interface.post_objects.meta import _DeleteKwargs + from ansys.fluent.interface.post_objects.post_object_definitions import ( + BasePostObjectDefn, + ContourDefn, + GraphicsDefn, + MonitorDefn, + SurfaceDefn, + VectorDefn, + ) + class _GraphicsContainer: """Base class for graphics containers.""" - def __init__(self, solver, **kwargs): + solver: BaseSession + _obj: BasePostObjectDefn + + def __init__(self, solver: BaseSession | None, **kwargs: Any): self.__dict__["solver"] = solver or _get_active_session() self.__dict__["kwargs"] = kwargs if self.solver is None: raise RuntimeError("No solver session provided and none found in context.") if "field" in self.kwargs: - self.kwargs["field"] = _to_field_name_str(self.kwargs["field"]) + self.kwargs["field"] = kwargs["field"] = _to_field_name_str( + self.kwargs["field"] + ) if "vectors_of" in self.kwargs: - self.kwargs["vectors_of"] = _to_field_name_str(self.kwargs["vectors_of"]) + self.kwargs["vectors_of"] = kwargs["vectors_of"] = _to_field_name_str( + self.kwargs["vectors_of"] + ) - def __getattr__(self, attr): - return getattr(self._obj, attr) + if not TYPE_CHECKING: - def __setattr__(self, attr, value): - if attr == "surfaces": - value = list(value) - setattr(self._obj, attr, value) + def __getattr__(self, attr): + return getattr(self._obj, attr) - def __dir__(self): + @override + def __setattr__(self, attr, value): + if attr == "surfaces": # TODO typing? + value = list(value) + setattr(self._obj, attr, value) + + @override + def __dir__(self) -> list[str]: return sorted(set(super().__dir__()) | set(dir(self._obj))) @@ -87,8 +114,14 @@ class Mesh(_GraphicsContainer): >>> ) """ + _obj: GraphicsDefn + def __init__( - self, surfaces: list[str], show_edges: bool = False, solver=None, **kwargs + self, + surfaces: list[str], + show_edges: bool = False, + solver: BaseSession | None = None, + **kwargs: Unpack[_DeleteKwargs], ): """__init__ method of Mesh class.""" kwargs.update( @@ -103,7 +136,20 @@ def __init__( ) -class Surface(_GraphicsContainer): +class SurfaceKwargs(TypedDict, total=False): + type: str | None + creation_method: str | None + x: float | None + y: float | None + z: float | None + field: str | VariableDescriptor | None + iso_value: float | None + rendering: str | None + point: tuple[float, float, float] | None + normal: tuple[float, float, float] | None + + +class Surface(_GraphicsContainer, SurfaceDefn if TYPE_CHECKING else object): """Surface definition for Fluent post-processing. The ``Surface`` class represents any Fluent surface generated for @@ -152,86 +198,138 @@ class determines the session. >>> surf_outlet_plane.iso_value = -0.125017 """ - def __init__(self, type: str, solver=None, **kwargs): + _obj: SurfaceDefn + + def __init__( + self, + type: str, + solver: BaseSession | None = None, + **kwargs: Unpack[SurfaceKwargs], + ): """__init__ method of Surface class.""" - kwargs.update( - { - "type": type, - } - ) + kwargs.update({"type": type}) super().__init__(solver, **kwargs) - self.__dict__.update( - dict( - type=self.kwargs.pop("type", None), - creation_method=self.kwargs.pop("creation_method", None), - x=self.kwargs.pop("x", None), - y=self.kwargs.pop("y", None), - z=self.kwargs.pop("z", None), - field=self.kwargs.pop("field", None), - iso_value=self.kwargs.pop("iso_value", None), - rendering=self.kwargs.pop("rendering", None), - point=self.kwargs.pop("point", None), - normal=self.kwargs.pop("normal", None), - _obj=Graphics(session=self.solver).Surfaces.create(**self.kwargs), - ) + super().__setattr__( + "_obj", Graphics(session=self.solver).Surfaces.create(**kwargs) ) - for attr in [ - "type", - "creation_method", - "x", - "y", - "z", - "field", - "iso_value", - "rendering", - "point", - "normal", - ]: - val = getattr(self, attr) - if val is not None: - setattr(self, attr, val) - - def __setattr__(self, attr, value): - if attr == "type": - self._obj.definition.type = value - elif attr == "creation_method": - self._obj.definition.plane_surface.creation_method = value - elif attr == "z": - if self._obj.definition.plane_surface.creation_method() != "xy-plane": - raise ValueError("Expected plane creation method to be 'xy-plane'") - self._obj.definition.plane_surface.xy_plane.z = value - elif attr == "y": - if self._obj.definition.plane_surface.creation_method() != "zx-plane": - raise ValueError("Expected plane creation method to be 'zx-plane'") - self._obj.definition.plane_surface.zx_plane.y = value - elif attr == "x": - if self._obj.definition.plane_surface.creation_method() != "yz-plane": - raise ValueError("Expected plane creation method to be 'yz-plane'") - self._obj.definition.plane_surface.yz_plane.x = value - elif attr == "field": - self._obj.definition.iso_surface.field = _to_field_name_str(value) - elif attr == "iso_value": - self._obj.definition.iso_surface.iso_value = value - elif attr == "rendering": - self._obj.definition.iso_surface.rendering = value - elif attr in ["point", "normal"]: - if ( - self._obj.definition.plane_surface.creation_method() - != "point-and-normal" - ): - raise ValueError( - "Expected plane creation method to be 'point-and-normal'" - ) - if attr == "point": - self._obj.definition.plane_surface.point.x = value[0] - self._obj.definition.plane_surface.point.y = value[1] - self._obj.definition.plane_surface.point.z = value[2] - elif attr == "normal": - self._obj.definition.plane_surface.normal.x = value[0] - self._obj.definition.plane_surface.normal.y = value[1] - self._obj.definition.plane_surface.normal.z = value[2] - else: - setattr(self._obj, attr, value) + self.type = kwargs.get("type") + self.creation_method = kwargs.get("creation_method") + self.x = kwargs.get("x") + self.y = kwargs.get("y") + self.z = kwargs.get("z") + self.field = kwargs.get("field") + self.iso_value = kwargs.get("iso_value") + self.rendering = kwargs.get("rendering") + self.point = kwargs.get("point") + self.normal = kwargs.get("normal") + + @property + def type(self) -> Literal["plane-surface", "iso-surface"]: + """Surface definition type.""" + return self._obj.definition.type() + + @type.setter + def type(self, value: Literal["plane-surface", "iso-surface"]) -> None: + self._obj.definition.type = value + + @property + def creation_method( + self, + ) -> Literal["xy-plane", "yz-plane", "zx-plane", "point-and-normal"]: + """Plane surface creation method.""" + return self._obj.definition.plane_surface.creation_method() + + @creation_method.setter + def creation_method( + self, value: Literal["xy-plane", "yz-plane", "zx-plane", "point-and-normal"] + ) -> None: + self._obj.definition.plane_surface.creation_method = value + + @property + def x(self) -> float: + """X coordinate for yz-plane.""" + return self._obj.definition.plane_surface.yz_plane.x() + + @x.setter + def x(self, value: float) -> None: + if self._obj.definition.plane_surface.creation_method() != "yz-plane": + raise ValueError("Expected plane creation method to be 'yz-plane'") + self._obj.definition.plane_surface.yz_plane.x = value + + @property + def y(self) -> float: + """Y coordinate for zx-plane.""" + return self._obj.definition.plane_surface.zx_plane.y() + + @y.setter + def y(self, value: float) -> None: + if self._obj.definition.plane_surface.creation_method() != "zx-plane": + raise ValueError("Expected plane creation method to be 'zx-plane'") + self._obj.definition.plane_surface.zx_plane.y = value + + @property + def z(self) -> float: + """Z coordinate for xy-plane.""" + return self._obj.definition.plane_surface.xy_plane.z() + + @z.setter + def z(self, value: float) -> None: + if self._obj.definition.plane_surface.creation_method() != "xy-plane": + raise ValueError("Expected plane creation method to be 'xy-plane'") + self._obj.definition.plane_surface.xy_plane.z = value + + @property + def field(self) -> str | None: + """Iso-surface field.""" + return self._obj.definition.iso_surface.field() + + @field.setter + def field(self, value: str | VariableDescriptor | None) -> None: + self._obj.definition.iso_surface.field = _to_field_name_str(value) + + @property + def iso_value(self) -> float | None: + """Iso-surface value.""" + return self._obj.definition.iso_surface.iso_value() + + @iso_value.setter + def iso_value(self, value: float | None) -> None: + self._obj.definition.iso_surface.iso_value = value + + @property + def rendering(self) -> Literal["mesh", "contour"]: + """Iso-surface rendering method.""" + return self._obj.definition.iso_surface.rendering() + + @rendering.setter + def rendering(self, value: Literal["mesh", "contour"]) -> None: + self._obj.definition.iso_surface.rendering = value + + @property + def point(self) -> tuple[float, float, float]: + """Point for point-and-normal surface.""" + pt = self._obj.definition.plane_surface.point + return (pt.x(), pt.y(), pt.z()) + + @point.setter + def point(self, value: tuple[float, float, float]) -> None: + if self._obj.definition.plane_surface.creation_method() != "point-and-normal": + raise ValueError("Expected plane creation method to be 'point-and-normal'") + pt = self._obj.definition.plane_surface.point + pt.x, pt.y, pt.z = value + + @property + def normal(self) -> tuple[float, float, float]: + """Normal vector for point-and-normal surface.""" + norm = self._obj.definition.plane_surface.normal + return (norm.x(), norm.y(), norm.z()) + + @normal.setter + def normal(self, value: tuple[float, float, float]) -> None: + if self._obj.definition.plane_surface.creation_method() != "point-and-normal": + raise ValueError("Expected plane creation method to be 'point-and-normal'") + norm = self._obj.definition.plane_surface.normal + norm.x, norm.y, norm.z = value class PlaneSurface(Surface): @@ -248,16 +346,19 @@ class PlaneSurface(Surface): >>> solver=solver_session, >>> point=[0.0, 0.0, -0.0441921], >>> normal=[0.0, 0.0, 1.0], - >>> ) + >>> ) + >>> >>> # Create same plane using 'create_xy_plane' method >>> surf_xy_plane = PlaneSurface.create_xy_plane( >>> solver=solver_session, >>> z=-0.0441921, - >>> ) + >>> ) """ @classmethod - def create_xy_plane(cls, solver=None, z: float = 0.0, **kwargs): + def create_xy_plane( + cls, *, solver: BaseSession | None = None, z: float = 0.0, **kwargs: Any + ) -> Self: """Create a plane surface in the XY plane at a given Z value.""" return cls( solver=solver, @@ -268,7 +369,9 @@ def create_xy_plane(cls, solver=None, z: float = 0.0, **kwargs): ) @classmethod - def create_yz_plane(cls, solver=None, x=0.0, **kwargs): + def create_yz_plane( + cls, solver: BaseSession | None = None, x: float = 0.0, **kwargs: Any + ) -> Self: """Create a plane surface in the YZ plane at a given X value.""" return cls( solver=solver, @@ -279,7 +382,9 @@ def create_yz_plane(cls, solver=None, x=0.0, **kwargs): ) @classmethod - def create_zx_plane(cls, solver=None, y=0.0, **kwargs): + def create_zx_plane( + cls, solver: BaseSession | None = None, y: float = 0.0, **kwargs: Any + ): """Create a plane surface in the ZX plane at a given Y value.""" return cls( solver=solver, @@ -291,13 +396,13 @@ def create_zx_plane(cls, solver=None, y=0.0, **kwargs): @classmethod def create_from_point_and_normal( - cls, solver=None, point=None, normal=None, **kwargs + cls, + solver: BaseSession | None = None, + point: tuple[float, float, float] = (0, 0, 0), + normal: tuple[float, float, float] = (0, 0, 0), + **kwargs: Any, ): """Create a plane surface from a point and a normal vector.""" - if normal is None: - normal = [0.0, 0.0, 0.0] - if point is None: - point = [0.0, 0.0, 0.0] return cls( solver=solver, type="plane-surface", @@ -346,11 +451,11 @@ class IsoSurface(Surface): def __init__( self, - solver=None, + solver: BaseSession | None = None, field: str | VariableDescriptor | None = None, rendering: str | None = None, iso_value: float | None = None, - **kwargs + **kwargs: Any, ): """Create an iso-surface.""" super().__init__( @@ -398,12 +503,14 @@ class Contour(_GraphicsContainer): >>> ) """ + _obj: ContourDefn + def __init__( self, field: str | VariableDescriptor, surfaces: list[str], solver=None, - **kwargs + **kwargs, ): """__init__ method of Contour class.""" kwargs.update( @@ -452,6 +559,8 @@ class Vector(_GraphicsContainer): >>> ) """ + _obj: VectorDefn + def __init__( self, field: str | VariableDescriptor, @@ -459,7 +568,7 @@ def __init__( color_by: str | VariableDescriptor | None = None, scale: float = 1.0, solver=None, - **kwargs + **kwargs, ): """__init__ method of Vector class.""" if color_by is None: @@ -541,12 +650,14 @@ class Pathline(_GraphicsContainer): >>> ) """ + _obj: GraphicsDefn + def __init__( self, field: str | VariableDescriptor, surfaces: list[str], solver=None, - **kwargs + **kwargs, ): """__init__ method of Pathline class.""" kwargs.update( @@ -596,13 +707,15 @@ class XYPlot(_GraphicsContainer): >>> ) """ + _obj: GraphicsDefn + def __init__( self, surfaces: list[str], y_axis_function: str | VariableDescriptor, - solver=None, + solver: BaseSession | None = None, local_surfaces_provider=None, - **kwargs + **kwargs, ): """__init__ method of XYPlot class.""" kwargs.update( @@ -649,6 +762,8 @@ class Monitor(_GraphicsContainer): >>> residual = Monitor(solver=solver_session, monitor_set_name="residual") """ + _obj: MonitorDefn + def __init__( self, monitor_set_name: str, solver=None, local_surfaces_provider=None, **kwargs ): diff --git a/src/ansys/fluent/visualization/contour.py b/src/ansys/fluent/visualization/contour.py index d4fcb88a..8c52dfc3 100644 --- a/src/ansys/fluent/visualization/contour.py +++ b/src/ansys/fluent/visualization/contour.py @@ -22,7 +22,7 @@ """Contour objects based on field name and surfaces list.""" -from typing import List, Optional +from typing import Optional class Contour: @@ -57,7 +57,7 @@ def _error_check(self, solver): f"{surface} is not valid surface. Valid surfaces are {allowed_surfaces}" # noqa: E501 ) - def __init__(self, field: str, surfaces: List[str], solver: Optional = None): + def __init__(self, field: str, surfaces: list[str], solver: Optional = None): """Create contour using field name and surfaces list. Parameters diff --git a/src/ansys/fluent/visualization/graphics/graphics_objects.py b/src/ansys/fluent/visualization/graphics/graphics_objects.py index 9fc04472..1504d443 100644 --- a/src/ansys/fluent/visualization/graphics/graphics_objects.py +++ b/src/ansys/fluent/visualization/graphics/graphics_objects.py @@ -23,7 +23,6 @@ """Module providing visualization objects for PyVista.""" import sys -from typing import Optional from ansys.fluent.interface.post_objects.meta import Command from ansys.fluent.interface.post_objects.post_helper import PostAPIHelper @@ -80,7 +79,7 @@ class Mesh(MeshDefn): """ @Command - def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = False): + def display(self, window_id: str | None = None, overlay: bool | None = False): """Display mesh graphics. Parameters @@ -112,7 +111,7 @@ class Pathlines(PathlinesDefn): """ @Command - def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = False): + def display(self, window_id: str | None = None, overlay: bool | None = False): """Display mesh graphics. Parameters @@ -143,19 +142,21 @@ class Surface(SurfaceDefn): .. code-block:: python - from ansys.fluent.visualization import Graphics - - graphics_session = Graphics(session) - surface1 = graphics_session.Surfaces["surface-1"] - surface1.definition.type = "iso-surface" - surface1.definition.iso_surface.field= "velocity-magnitude" - surface1.definition.iso_surface.rendering= "contour" - surface1.definition.iso_surface.iso_value = 0.0 - surface1.display("window-0") + >>> from ansys.fluent.visualization import Graphics + >>> + >>> graphics_session = Graphics(session) + >>> surface1 = graphics_session.Surfaces["surface-1"] + >>> surface1.type = "iso-surface" + >>> surface1.iso_surface.field= "velocity-magnitude" + >>> surface1.iso_surface.rendering= "contour" + >>> surface1.iso_surface.iso_value = 0.0 + >>> surface1.display("window-0") """ @Command - def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = False): + def display( + self, window_id: str | None = None, overlay: bool | None = False + ) -> None: """Display surface graphics. Parameters @@ -196,7 +197,9 @@ class Contour(ContourDefn): """ @Command - def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = False): + def display( + self, window_id: str | None = None, overlay: bool | None = False + ) -> None: """Display contour graphics. Parameters @@ -238,7 +241,9 @@ class Vector(VectorDefn): """ @Command - def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = False): + def display( + self, window_id: str | None = None, overlay: bool | None = False + ) -> None: """Display vector graphics. Parameters diff --git a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py index bde31069..dcf66840 100644 --- a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py +++ b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py @@ -25,7 +25,6 @@ from enum import Enum import itertools import threading -from typing import Dict, List, Optional import numpy as np import pyvista as pv @@ -130,7 +129,7 @@ def _get_renderer(self, renderer_string=None): raise KeyError(error_message) from ex return renderer(self.id, in_jupyter(), not pyviz.config.interactive, self._grid) - def set_data(self, data_type: FieldDataType, data: Dict[int, Dict[str, np.array]]): + def set_data(self, data_type: FieldDataType, data: dict[int, dict[str, np.array]]): """Set data for graphics.""" self._data[data_type] = data @@ -653,10 +652,10 @@ class GraphicsWindowsManager(metaclass=AbstractSingletonMeta): def __init__(self): """Instantiate ``GraphicsWindow`` for Graphics.""" - self._post_windows: Dict[str:GraphicsWindow] = {} + self._post_windows: dict[str:GraphicsWindow] = {} self._plotter_thread: threading.Thread = None self._post_object: GraphicsDefn = None - self._window_id: Optional[str] = None + self._window_id: str | None = None self._exit_thread: bool = False self._app = None self._post_objects_list = [] @@ -753,9 +752,9 @@ def save_graphic( def refresh_windows( self, - session_id: Optional[str] = "", + session_id: str | None = "", windows_id=None, - overlay: Optional[bool] = False, + overlay: bool | None = False, ) -> None: """Refresh windows. @@ -784,7 +783,7 @@ def refresh_windows( def animate_windows( self, - session_id: Optional[str] = "", + session_id: str | None = "", windows_id=None, ) -> None: """Animate windows. @@ -816,7 +815,7 @@ def animate_windows( def close_windows( self, - session_id: Optional[str] = "", + session_id: str | None = "", windows_id=None, ) -> None: """Close windows. @@ -846,9 +845,9 @@ def close_windows( def _get_windows_id( self, - session_id: Optional[str] = "", + session_id: str | None = "", windows_id=None, - ) -> List[str]: + ) -> list[str]: if windows_id is None: windows_id = [] with self._condition: @@ -916,9 +915,9 @@ def open_window( def plot( self, graphics_object: GraphicsDefn, - window_id: Optional[str] = None, - fetch_data: Optional[bool] = False, - overlay: Optional[bool] = False, + window_id: str | None = None, + fetch_data: bool | None = False, + overlay: bool | None = False, ) -> None: """Draw a plot. @@ -1027,9 +1026,9 @@ def open_window( def plot( self, graphics_object: GraphicsDefn, - window_id: Optional[str] = None, - fetch_data: Optional[bool] = False, - overlay: Optional[bool] = False, + window_id: str | None = None, + fetch_data: bool | None = False, + overlay: bool | None = False, ) -> None: """Draw a plot. diff --git a/src/ansys/fluent/visualization/graphics/pyvista/renderer.py b/src/ansys/fluent/visualization/graphics/pyvista/renderer.py index 2cd0dc74..efc3021e 100644 --- a/src/ansys/fluent/visualization/graphics/pyvista/renderer.py +++ b/src/ansys/fluent/visualization/graphics/pyvista/renderer.py @@ -44,7 +44,7 @@ def __init__( win_id: str, in_jupyter: bool, non_interactive: bool, - grid: tuple | None = (1, 1), + grid: tuple[int, int] | None = (1, 1), ): self.plotter: BackgroundPlotter | pv.Plotter = ( pv.Plotter(title=f"PyFluent ({win_id})", shape=grid) diff --git a/src/ansys/fluent/visualization/plotter/matplotlib/renderer.py b/src/ansys/fluent/visualization/plotter/matplotlib/renderer.py index 2e227def..ef378abd 100644 --- a/src/ansys/fluent/visualization/plotter/matplotlib/renderer.py +++ b/src/ansys/fluent/visualization/plotter/matplotlib/renderer.py @@ -22,7 +22,6 @@ """Module providing matplotlib plotter functionality.""" -from typing import List, Optional import matplotlib.pyplot as plt import numpy as np @@ -36,12 +35,12 @@ class Plotter(AbstractRenderer): def __init__( self, window_id: str, - curves: List[str] | None = None, + curves: list[str] | None = None, title: str | None = "XY Plot", xlabel: str | None = "position", ylabel: str | None = "", - remote_process: Optional[bool] = False, - grid: tuple | None = (1, 1), + remote_process: bool | None = False, + grid: tuple[int, int] | None = (1, 1), ): """Instantiate a matplotlib plotter. @@ -63,6 +62,8 @@ def __init__( Subplot indices. remote_process: bool, optional Is remote process. + grid: tuple[int, int], optional + Grid layout for subplots. """ self._curves = [] if curves is None else curves self._title = title @@ -249,7 +250,7 @@ class ProcessPlotter(Plotter): def __init__( self, window_id, - curves_name: List[str] | None = None, + curves_name: list[str] | None = None, title: str | None = "XY Plot", xlabel: str | None = "position", ylabel: str | None = "", diff --git a/src/ansys/fluent/visualization/plotter/plotter_objects.py b/src/ansys/fluent/visualization/plotter/plotter_objects.py index 8a60917a..87bb9f06 100644 --- a/src/ansys/fluent/visualization/plotter/plotter_objects.py +++ b/src/ansys/fluent/visualization/plotter/plotter_objects.py @@ -23,12 +23,12 @@ """Module providing visualization objects for Matplotlib.""" import sys -from typing import Optional from ansys.fluent.interface.post_objects.meta import Command from ansys.fluent.interface.post_objects.post_helper import PostAPIHelper from ansys.fluent.interface.post_objects.post_object_definitions import ( MonitorDefn, + SurfaceDefn, XYPlotDefn, ) from ansys.fluent.interface.post_objects.post_objects_container import ( @@ -46,7 +46,10 @@ class Plots(PlotsContainer): """ def __init__( - self, session, post_api_helper=PostAPIHelper, local_surfaces_provider=None + self, + session, + post_api_helper=PostAPIHelper, + local_surfaces_provider: SurfaceDefn | None = None, ): super().__init__( session, sys.modules[__name__], post_api_helper, local_surfaces_provider @@ -77,7 +80,7 @@ class XYPlot(XYPlotDefn): """ @Command - def plot(self, window_id: Optional[str] = None): + def plot(self, window_id: str | None = None): """Draw XYPlot. Parameters @@ -112,7 +115,7 @@ class MonitorPlot(MonitorDefn): """ @Command - def plot(self, window_id: Optional[str] = None): + def plot(self, window_id: str | None = None): """Draw Monitor Plot. Parameters diff --git a/src/ansys/fluent/visualization/plotter/plotter_windows_manager.py b/src/ansys/fluent/visualization/plotter/plotter_windows_manager.py index 469d0552..2d066057 100644 --- a/src/ansys/fluent/visualization/plotter/plotter_windows_manager.py +++ b/src/ansys/fluent/visualization/plotter/plotter_windows_manager.py @@ -24,7 +24,7 @@ import itertools import multiprocessing as mp -from typing import Dict, List, Optional, Union +from typing import Union from ansys.fluent.core.fluent_connection import FluentConnection @@ -121,7 +121,7 @@ def __init__( self.id: str = id self.post_object = None self._grid = grid - self.plotter: Union[_ProcessPlotterHandle, "Plotter"] = self._get_plotter( + self.plotter: _ProcessPlotterHandle | Plotter = self._get_plotter( plotter_string=renderer ) self.close: bool = False @@ -226,7 +226,7 @@ def __init__( Plotter to plot the data. """ self.post_object: XYPlotDefn = post_object - self.plotter: Union[_ProcessPlotterHandle, "Plotter"] = plotter + self.plotter: _ProcessPlotterHandle | Plotter = plotter def __call__(self): """Draw an XY plot.""" @@ -258,7 +258,7 @@ def __init__( Plotter to plot the data. """ self.post_object: MonitorDefn = post_object - self.plotter: Union[_ProcessPlotterHandle, "Plotter"] = plotter + self.plotter: _ProcessPlotterHandle | Plotter = plotter def __call__(self): """Draw a monitor plot.""" @@ -300,7 +300,7 @@ class PlotterWindowsManager( def __init__(self): """Instantiate a windows manager for the plotter.""" - self._post_windows: Dict[str, PlotterWindow] = {} + self._post_windows: dict[str, PlotterWindow] = {} def open_window( self, @@ -354,7 +354,7 @@ def set_object_for_window(self, object: PlotDefn, window_id: str) -> None: def plot( self, object: PlotDefn, - window_id: Optional[str] = None, + window_id: str | None = None, grid=(1, 1), position=(0, 0), subplot_titles=None, @@ -424,9 +424,9 @@ def save_graphic( def refresh_windows( self, - session_id: Optional[str] = "", + session_id: str | None = "", windows_id=None, - overlay: Optional[bool] = None, + overlay: bool | None = None, ) -> None: """Refresh windows. @@ -451,7 +451,7 @@ def refresh_windows( def animate_windows( self, - session_id: Optional[str] = "", + session_id: str | None = "", windows_id=None, ) -> None: """Animate windows. @@ -476,7 +476,7 @@ def animate_windows( def close_windows( self, - session_id: Optional[str] = "", + session_id: str | None = "", windows_id=None, ) -> None: """Close windows. @@ -521,9 +521,9 @@ def _open_window( def _get_windows_id( self, - session_id: Optional[str] = "", + session_id: str | None = "", windows_id=None, - ) -> List[str]: + ) -> list[str]: if windows_id is None: windows_id = [] return [ diff --git a/src/ansys/fluent/visualization/plotter/pyvista/renderer.py b/src/ansys/fluent/visualization/plotter/pyvista/renderer.py index 19801c9f..33c4bb5a 100644 --- a/src/ansys/fluent/visualization/plotter/pyvista/renderer.py +++ b/src/ansys/fluent/visualization/plotter/pyvista/renderer.py @@ -20,7 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from typing import List, Optional import numpy as np import pyvista as pv @@ -34,11 +33,11 @@ class Plotter(AbstractRenderer): def __init__( self, window_id: str, - curves: Optional[List[str]] = None, - title: Optional[str] = "XY Plot", - xlabel: Optional[str] = "position", - ylabel: Optional[str] = "", - remote_process: Optional[bool] = False, + curves: list[str] | None = None, + title: str | None = "XY Plot", + xlabel: str | None = "position", + ylabel: str | None = "", + remote_process: bool | None = False, grid: tuple | None = (1, 1), ): """Instantiate a pyvista chart 2D plotter. diff --git a/src/ansys/fluent/visualization/post_data_extractor.py b/src/ansys/fluent/visualization/post_data_extractor.py index c6c306aa..e8fcd438 100644 --- a/src/ansys/fluent/visualization/post_data_extractor.py +++ b/src/ansys/fluent/visualization/post_data_extractor.py @@ -23,7 +23,6 @@ """Module providing data extractor APIs.""" import itertools -from typing import Dict from ansys.fluent.core.field_data_interfaces import ( PathlinesFieldDataRequest, @@ -267,7 +266,7 @@ def __init__(self, post_object: PlotDefn): """ self._post_object: PlotDefn = post_object - def fetch_data(self) -> Dict[str, Dict[str, np.array]]: + def fetch_data(self) -> dict[str, dict[str, np.array]]: """Fetch data for visualization object. Parameters diff --git a/src/ansys/fluent/visualization/visualization_windows_manager.py b/src/ansys/fluent/visualization/visualization_windows_manager.py index 873b7d27..91d8a899 100644 --- a/src/ansys/fluent/visualization/visualization_windows_manager.py +++ b/src/ansys/fluent/visualization/visualization_windows_manager.py @@ -26,7 +26,7 @@ """ from abc import ABCMeta, abstractmethod -from typing import List, Optional, Union +from collections.abc import Sequence from ansys.fluent.interface.post_objects.post_object_definitions import ( GraphicsDefn, @@ -47,7 +47,7 @@ class VisualizationWindowsManager(metaclass=ABCMeta): """Abstract class for visualization windows management.""" @abstractmethod - def open_window(self, window_id: Optional[str] = None) -> str: + def open_window(self, window_id: str | None = None) -> str: """Open new window. Parameters @@ -64,7 +64,7 @@ def open_window(self, window_id: Optional[str] = None) -> str: @abstractmethod def set_object_for_window( - self, object: Union[GraphicsDefn, PlotDefn], window_id: str + self, object: GraphicsDefn | PlotDefn, window_id: str ) -> None: """Associate visualization object with running window instance. @@ -86,8 +86,8 @@ def set_object_for_window( @abstractmethod def plot( self, - object: Union[GraphicsDefn, PlotDefn], - window_id: Optional[str] = None, + object: GraphicsDefn | PlotDefn, + window_id: str | None = None, ) -> None: """Draw plot. @@ -131,9 +131,9 @@ def save_graphic( @abstractmethod def refresh_windows( self, - session_id: Optional[str] = "", - windows_id: Optional[List[str]] = [], - overlay: Optional[bool] = False, + session_id: str | None = "", + windows_id: list[str] | None = [], + overlay: bool | None = False, ) -> None: """Refresh windows. @@ -156,8 +156,8 @@ def refresh_windows( @abstractmethod def animate_windows( self, - session_id: Optional[str] = "", - windows_id: Optional[List[str]] = [], + session_id: str | None = "", + windows_id: Sequence[str] | None = [], ) -> None: """Animate windows. @@ -182,8 +182,8 @@ def animate_windows( @abstractmethod def close_windows( self, - session_id: Optional[str] = "", - windows_id: Optional[List[str]] = [], + session_id: str | None = "", + windows_id: Sequence[str] | None = [], ) -> None: """Close windows. diff --git a/tests/test_post.py b/tests/test_post.py index c6068b49..a8cc8b3a 100644 --- a/tests/test_post.py +++ b/tests/test_post.py @@ -23,7 +23,6 @@ from pathlib import Path import pickle import sys -from typing import Dict, List, Optional, Union from ansys.fluent.core.field_data_interfaces import SurfaceDataType import numpy as np @@ -47,7 +46,7 @@ def __init__(self, session_data, field_request): def add_surfaces_request( self, - surface_ids: List[int], + surface_ids: list[int], overset_mesh: bool = False, provide_vertices=True, provide_faces=True, @@ -67,10 +66,10 @@ def add_surfaces_request( def add_scalar_fields_request( self, - surface_ids: List[int], + surface_ids: list[int], field_name: str, - node_value: Optional[bool] = True, - boundary_value: Optional[bool] = False, + node_value: bool | None = True, + boundary_value: bool | None = False, ) -> None: self.fields_request["scalar"].append( (surface_ids, field_name, node_value, boundary_value) @@ -78,12 +77,12 @@ def add_scalar_fields_request( def add_vector_fields_request( self, - surface_ids: List[int], + surface_ids: list[int], field_name: str, ) -> None: self.fields_request["vector"].append((surface_ids, field_name)) - def get_fields(self) -> Dict[int, Dict]: + def get_fields(self) -> dict[int, dict]: fields = {} for request_type, requests in self.fields_request.items(): for request in requests: @@ -119,9 +118,9 @@ def new_transaction(self): def get_surface_data( self, surface_name: str, - data_type: Union[SurfaceDataType, int], - overset_mesh: Optional[bool] = False, - ) -> Dict: + data_type: SurfaceDataType | int, + overset_mesh: bool | None = False, + ) -> dict: surfaces_info = self.surfaces() surface_ids = surfaces_info[surface_name]["surface_id"] self._request_to_serve["surf"].append(