from __future__ import annotations
import contextlib
import ctypes
from typing import Any
import numpy as np
from System.Runtime.InteropServices import GCHandle, GCHandleType
from skZemax.skZemax_subfunctions._c_print import c_print as cp
[docs]
@staticmethod
def __LowLevelZemaxStringCheck__(
self,
in_obj,
extra_include_filter: str | list | None = None,
extra_exclude_filter: str | list | None = None,
check_if_upper: bool = False,
) -> list:
"""
A low level function which produces a list of values given by python's dir() call - after some additional filtering.
:param in_obj: The object for which the contents will be listed.
:type in_obj: _type_
:param extra_include_filter: A string which (or list of strings), if provided, will keep only the elements of the dir() call that have this sequence within it, defaults to None
:type extra_include_filter: str, optional
:param extra_exclude_filter: A string which (or list of strings), if provided, will exclude all the elements of the dir() call that have this sequence within it, defaults to None
:type extra_exclude_filter: str, optional
:param check_if_upper: If True, will only keep elements which are all upper cased, defaults to False
:type check_if_upper: bool, optional
:return: A list of the objects attributes (after any filtering)
:rtype: list
"""
if extra_include_filter is not None and isinstance(extra_include_filter, str):
extra_include_filter = [extra_include_filter]
if extra_exclude_filter is not None and isinstance(extra_exclude_filter, str):
extra_exclude_filter = [extra_exclude_filter]
all_names = [x for x in dir(in_obj) if "__" not in x]
if extra_include_filter is not None:
all_names = [x for x in all_names if any(y in x for y in extra_include_filter)]
if extra_exclude_filter is not None:
all_names = [
x for x in all_names if not any(y in x for y in extra_exclude_filter)
]
if check_if_upper:
all_names = [x for x in all_names if x.isupper()]
# Filter out calls one shouldn't use through this function
for x in [
"Format",
"Equals",
"CompareTo",
"Finalize",
"GetHashCode",
"GetName",
"GetNames",
"GetType",
"GetTypeCode",
"GetUnderlyingType",
"GetValues",
"HasFlag",
"MemberwiseClone",
"Overloads",
"Parse",
"ReferenceEquals",
"ToObject",
"ToString",
"TryParse",
"IsDefined",
]:
with contextlib.suppress(BaseException):
all_names.remove(x)
return sorted(all_names, key=lambda item: (len(item), item))
[docs]
def _CheckIfStringValidInDir_(
self,
in_obj: Any,
in_string: str,
extra_include_filter: str | list | None = None,
extra_exclude_filter: str | list | None = None,
check_if_upper: bool = False,
) -> Any:
"""
Looks at a Zemax class/module/etc and sees if the given string is an attribute/call within the Zemax code (via a dir() call).
This function *returns* the value of the attribute in the Zemax code. Returns None if string does not match anything.
If the given string matches more than one attribute, the first attribute within the dir() call will be given.
:param in_obj: The Zemax object to inspect.
:type in_obj: Any
:param in_string: The string to find within the object. This is not case sensitive.
:type in_string: str
:param extra_include_filter: A string which (or list of strings), if provided, will keep only the elements of the dir() call that have this sequence within it, defaults to None
:type extra_include_filter: str, optional
:param extra_exclude_filter: A string which (or list of strings), if provided, will exclude all the elements of the dir() call that have this sequence within it, defaults to None
:type extra_exclude_filter: str, optional
:param check_if_upper: If True, will only keep elements - of the object dir() - which are all upper cased, defaults to False
:type check_if_upper: bool, optional
:return: he value of the Zemax object indicated by the string. None is returned if the string is not found.
:rtype: Any
"""
all_names = __LowLevelZemaxStringCheck__(
self,
in_obj=in_obj,
extra_include_filter=extra_include_filter,
extra_exclude_filter=extra_exclude_filter,
check_if_upper=check_if_upper,
)
# Check if input is known and return.
bool_mask = [in_string.lower() in x.lower() for x in all_names]
if np.any(bool_mask):
return getattr(in_obj, all_names[int(np.where(bool_mask)[0][0])])
if self._verbose:
cp(
f"!@ly!@_CheckIfStringValidInDir_ :: [!@lm!@{in_string}!@ly!@] not found in object [!@lm!@{in_obj!s}!@ly!@]."
)
return None
[docs]
def _SetAttrByStringIfValid_(
self,
in_obj: Any,
in_string: str,
in_value: Any,
extra_include_filter: str | list | None = None,
extra_exclude_filter: str | list | None = None,
check_if_upper: bool = False,
):
"""
Looks at a Zemax class/module/etc and sees if the given string is an attribute/call within the Zemax code (via a dir() call).
This function *sets* the value of the attribute in the Zemax code.
If the given string matches more than one attribute, the first attribute within the dir() call will be given.
:param in_obj: The Zemax object to inspect.
:type in_obj: Any
:param in_string: The string to find within the object. This is not case sensitive.
:type in_string: str
:param in_value: The value one would like to set the attribute/call identified by the in_string.
:type in_value: Any
:param extra_include_filter: A string which (or list of strings), if provided, will keep only the elements of the dir() call that have this sequence within it, defaults to None
:type extra_include_filter: Union[str, list], optional
:param extra_exclude_filter: A string which (or list of strings), if provided, will exclude all the elements of the dir() call that have this sequence within it, defaults to None
:type extra_exclude_filter: Union[str, list], optional
:param check_if_upper: If True, will only keep elements - of the object dir() - which are all upper cased, defaults to False
:type check_if_upper: bool, optional
"""
all_names = __LowLevelZemaxStringCheck__(
self,
in_obj=in_obj,
extra_include_filter=extra_include_filter,
extra_exclude_filter=extra_exclude_filter,
check_if_upper=check_if_upper,
)
# Check if input is known and return.
bool_mask = [in_string.lower() in x.lower() for x in all_names]
if np.any(bool_mask):
try:
in_obj.__setattr__(all_names[int(np.where(bool_mask)[0][0])], in_value)
except Exception as e:
cp(
f"!@lr!@_SetAttrByStringIfValid_ :: Error [!@lm!@{e}!@lr!@] when trying to set [!@lm!@{all_names[int(np.where(bool_mask)[0][0])]!s}!@lr!@] with [!@lm!@{in_value!s}!@lr!@]."
)
else:
cp(
f"!@ly!@_SetAttrByStringIfValid_ :: [!@lm!@{in_string}!@ly!@] not found in object [!@lm!@{in_obj!s}!@ly!@]."
)
return
[docs]
@staticmethod
def _ctype_to_numpy_(
self, data: Any, data_length: int, data_type: Any = np.int64
) -> Any:
"""
This method is a port from the example code attached to the `ZemaxRaytraceSupplement/RayTrace.dll`.
This conversion helps interface python with the C# code.
:param data: A pointer to a double reported by the C# code.
:type data: Any
:param data_length: The pointer array length to read in.
:type data_length: int, optional
:param data_type: The type of data to expect from the C# code, defaults to np.int64
:type data_type: Any, optional
:return: The value(s)
:rtype: Any
"""
src_hndl = GCHandle.Alloc(data, GCHandleType.Pinned)
try:
size_factor = ctypes.sizeof(ctypes.c_int32) / np.dtype(data_type).itemsize
src_ptr = src_hndl.AddrOfPinnedObject().ToInt64()
if size_factor >= 1:
cbuf = (ctypes.c_int32 * int(data_length * size_factor)).from_address(
src_ptr
)
else:
cbuf = (ctypes.c_int32 * int(data_length / size_factor)).from_address(
src_ptr
)
npData = np.frombuffer(cbuf, dtype=data_type)
finally:
if src_hndl.IsAllocated:
src_hndl.Free()
return npData