Compare commits

..

No commits in common. "568fc449e818da3e06e799c9789dff7f8c2eaf36" and "e1f11f6d681e0529d7797247c9fd9ab2c5786197" have entirely different histories.

85 changed files with 512 additions and 464 deletions

14
TODO.md
View File

@ -504,13 +504,13 @@ Unreported:
- We found the translation callback! https://projects.blender.org/blender/blender/commit/8564e03cdf59fb2a71d545e81871411b82f561d9
- This can update the node center!!
- [x] Optimize the `DataChanged` invalidator.
- [ ] Optimize the `DataChanged` invalidator.
- [ ] Optimize unit stripping.
## Keyed Cache
- [x] Implement `bl_cache.KeyedCache` for, especially, abstracting the caches underlying the input and output sockets.
- [ ] Implement `bl_cache.KeyedCache` for, especially, abstracting the caches underlying the input and output sockets.
@ -546,12 +546,12 @@ We need support for arbitrary objects, but still backed by the persistance seman
- [ ] Similarly, a field method that gets the 'blfield__' prop data as a dictionary.
### Parallel Features
- [x] Move serialization work to a `utils`.
- [x] Also make ENCODER a function that can shortcut the easy cases.
- [x] For serializeability, let the encoder/decoder be able to make use of an optional `.msgspec_encodable()` and similar decoder respectively, and add support for these in the ENCODER/DECODER functions.
- [x] Define a superclass for `SocketDef` and make everyone inherit from it
- [ ] Move serialization work to a `utils`.
- [ ] Also make ENCODER a function that can shortcut the easy cases.
- [ ] For serializeability, let the encoder/decoder be able to make use of an optional `.msgspec_encodable()` and similar decoder respectively, and add support for these in the ENCODER/DECODER functions.
- [ ] Define a superclass for `SocketDef` and make everyone inherit from it
- [ ] Collect with a `BL_SOCKET_DEFS` object, instead of manually from `__init__.py`s
- [x] Add support for `.msgspec_*()` methods, so that we remove the dependency on sockets from the serialization module.
- [ ] Add support for `.msgspec_*()` methods, so that we remove the dependency on sockets from the serialization module.
### Sweeping Features
- [ ] Replace all raw Blender properties with `BLField`.

View File

@ -5,8 +5,14 @@ import inspect
import typing as typ
import bpy
import msgspec
import sympy as sp
import sympy.physics.units as spu
from ...utils import logger, serialize
from ...utils import extra_sympy_units as spux
from ...utils import logger
from . import contracts as ct
from . import managed_objs, sockets
log = logger.get(__name__)
@ -33,12 +39,149 @@ class BLInstance(typ.Protocol):
) -> None: ...
PropGetMethod: typ.TypeAlias = typ.Callable[
[BLInstance], serialize.NaivelyEncodableType
]
PropSetMethod: typ.TypeAlias = typ.Callable[
[BLInstance, serialize.NaivelyEncodableType], None
]
EncodableValue: typ.TypeAlias = typ.Any ## msgspec-compatible
PropGetMethod: typ.TypeAlias = typ.Callable[[BLInstance], EncodableValue]
PropSetMethod: typ.TypeAlias = typ.Callable[[BLInstance, EncodableValue], None]
####################
# - (De)Serialization
####################
EncodedComplex: typ.TypeAlias = tuple[float, float] | list[float, float]
EncodedSympy: typ.TypeAlias = str
EncodedManagedObj: typ.TypeAlias = tuple[str, str] | list[str, str]
EncodedPydanticModel: typ.TypeAlias = tuple[str, str] | list[str, str]
def _enc_hook(obj: typ.Any) -> EncodableValue:
"""Translates types not natively supported by `msgspec`, to an encodable form supported by `msgspec`.
Parameters:
obj: The object of arbitrary type to transform into an encodable value.
Returns:
A value encodable by `msgspec`.
Raises:
NotImplementedError: When the type transformation hasn't been implemented.
"""
if isinstance(obj, complex):
return (obj.real, obj.imag)
if isinstance(obj, sp.Basic | sp.MatrixBase | sp.Expr | spu.Quantity):
return sp.srepr(obj)
if isinstance(obj, managed_objs.ManagedObj):
return (obj.name, obj.__class__.__name__)
if isinstance(obj, ct.schemas.SocketDef):
return (obj.model_dump(), obj.__class__.__name__)
msg = f'Can\'t encode "{obj}" of type {type(obj)}'
raise NotImplementedError(msg)
def _dec_hook(_type: type, obj: EncodableValue) -> typ.Any:
"""Translates the `msgspec`-encoded form of an object back to its true form.
Parameters:
_type: The type to transform the `msgspec`-encoded object back into.
obj: The encoded object of to transform back into an encodable value.
Returns:
A value encodable by `msgspec`.
Raises:
NotImplementedError: When the type transformation hasn't been implemented.
"""
if _type is complex and isinstance(obj, EncodedComplex):
return complex(obj[0], obj[1])
if (
_type is sp.Basic
and isinstance(obj, EncodedSympy)
or _type is sp.Expr
and isinstance(obj, EncodedSympy)
or _type is sp.MatrixBase
and isinstance(obj, EncodedSympy)
or _type is spu.Quantity
and isinstance(obj, EncodedSympy)
):
return sp.sympify(obj).subs(spux.ALL_UNIT_SYMBOLS)
if (
_type is managed_objs.ManagedBLMesh
and isinstance(obj, EncodedManagedObj)
or _type is managed_objs.ManagedBLImage
and isinstance(obj, EncodedManagedObj)
or _type is managed_objs.ManagedBLModifier
and isinstance(obj, EncodedManagedObj)
):
return {
'ManagedBLMesh': managed_objs.ManagedBLMesh,
'ManagedBLImage': managed_objs.ManagedBLImage,
'ManagedBLModifier': managed_objs.ManagedBLModifier,
}[obj[1]](obj[0])
if _type is ct.schemas.SocketDef:
return getattr(sockets, obj[1])(**obj[0])
msg = f'Can\'t decode "{obj}" to type {type(obj)}'
raise NotImplementedError(msg)
ENCODER = msgspec.json.Encoder(enc_hook=_enc_hook, order='deterministic')
_DECODERS: dict[type, msgspec.json.Decoder] = {
complex: msgspec.json.Decoder(type=complex, dec_hook=_dec_hook),
sp.Basic: msgspec.json.Decoder(type=sp.Basic, dec_hook=_dec_hook),
sp.Expr: msgspec.json.Decoder(type=sp.Expr, dec_hook=_dec_hook),
sp.MatrixBase: msgspec.json.Decoder(type=sp.MatrixBase, dec_hook=_dec_hook),
spu.Quantity: msgspec.json.Decoder(type=spu.Quantity, dec_hook=_dec_hook),
managed_objs.ManagedBLMesh: msgspec.json.Decoder(
type=managed_objs.ManagedBLMesh,
dec_hook=_dec_hook,
),
managed_objs.ManagedBLImage: msgspec.json.Decoder(
type=managed_objs.ManagedBLImage,
dec_hook=_dec_hook,
),
managed_objs.ManagedBLModifier: msgspec.json.Decoder(
type=managed_objs.ManagedBLModifier,
dec_hook=_dec_hook,
),
# managed_objs.ManagedObj: msgspec.json.Decoder(
# type=managed_objs.ManagedObj, dec_hook=_dec_hook
# ), ## Doesn't work b/c unions are not explicit
ct.schemas.SocketDef: msgspec.json.Decoder(
type=ct.schemas.SocketDef,
dec_hook=_dec_hook,
),
}
_DECODER_FALLBACK: msgspec.json.Decoder = msgspec.json.Decoder(dec_hook=_dec_hook)
@functools.cache
def DECODER(_type: type) -> msgspec.json.Decoder: # noqa: N802
"""Retrieve a suitable `msgspec.json.Decoder` by-type.
Parameters:
_type: The type to retrieve a decoder for.
Returns:
A suitable decoder.
"""
if (decoder := _DECODERS.get(_type)) is not None:
return decoder
return _DECODER_FALLBACK
def decode_any(_type: type, obj: str) -> typ.Any:
naive_decode = DECODER(_type).decode(obj)
if _type == dict[str, ct.schemas.SocketDef]:
return {
socket_name: getattr(sockets, socket_def_list[1])(**socket_def_list[0])
for socket_name, socket_def_list in naive_decode.items()
}
log.critical(
'Naive Decode of "%s" to "%s" (%s)', str(obj), str(naive_decode), str(_type)
)
return naive_decode
####################
@ -67,7 +210,7 @@ class KeyedCache:
self,
func: typ.Callable,
exclude: set[str],
encode: set[str],
serialize: set[str],
):
# Function Information
self.func: typ.Callable = func
@ -76,7 +219,7 @@ class KeyedCache:
# Arg -> Key Information
self.exclude: set[str] = exclude
self.include: set[str] = set(self.func_sig.parameters.keys()) - exclude
self.encode: set[str] = encode
self.serialize: set[str] = serialize
# Cache Information
self.key_schema: tuple[str, ...] = tuple(
@ -104,8 +247,8 @@ class KeyedCache:
[
(
arg_value
if arg_name not in self.encode
else serialize.encode(arg_value)
if arg_name not in self.serialize
else ENCODER.encode(arg_value)
)
for arg_name, arg_value in arguments.items()
if arg_name in self.include
@ -155,8 +298,8 @@ class KeyedCache:
# Compute Keys to Invalidate
arguments_hashable = {
arg_name: serialize.encode(arg_value)
if arg_name in self.encode and arg_name not in wildcard_arguments
arg_name: ENCODER.encode(arg_value)
if arg_name in self.serialize and arg_name not in wildcard_arguments
else arg_value
for arg_name, arg_value in arguments.items()
}
@ -170,12 +313,12 @@ class KeyedCache:
cache.pop(key)
def keyed_cache(exclude: set[str], encode: set[str] = frozenset()) -> typ.Callable:
def keyed_cache(exclude: set[str], serialize: set[str] = frozenset()) -> typ.Callable:
def decorator(func: typ.Callable) -> typ.Callable:
return KeyedCache(
func,
exclude=exclude,
encode=encode,
serialize=serialize,
)
return decorator
@ -221,6 +364,9 @@ class CachedBLProperty:
inspect.signature(getter_method).return_annotation if persist else None
)
# Check Non-Empty Type Annotation
## For now, just presume that all types can be encoded/decoded.
# Check Non-Empty Type Annotation
## For now, just presume that all types can be encoded/decoded.
if self._type is not None and self._type is inspect.Signature.empty:
@ -282,7 +428,7 @@ class CachedBLProperty:
self._persist
and (encoded_value := getattr(bl_instance, self._bl_prop_name)) != ''
):
value = serialize.decode(self._type, encoded_value)
value = decode_any(self._type, encoded_value)
cache_nopersist[self._bl_prop_name] = value
return value
@ -293,7 +439,7 @@ class CachedBLProperty:
cache_nopersist[self._bl_prop_name] = value
if self._persist:
setattr(
bl_instance, self._bl_prop_name, serialize.encode(value).decode('utf-8')
bl_instance, self._bl_prop_name, ENCODER.encode(value).decode('utf-8')
)
return value
@ -465,7 +611,7 @@ class BLField:
raise TypeError(msg)
# Define Blender Property (w/Update Sync)
encoded_default_value = serialize.encode(self._default_value).decode('utf-8')
encoded_default_value = ENCODER.encode(self._default_value).decode('utf-8')
log.debug(
'%s set to StringProperty w/default "%s" and no_update="%s"',
bl_attr_name,
@ -486,14 +632,14 @@ class BLField:
## 2. Retrieve bpy.props.StringProperty string.
## 3. Decode using annotated type.
def getter(_self: BLInstance) -> AttrType:
return serialize.decode(AttrType, getattr(_self, bl_attr_name))
return decode_any(AttrType, getattr(_self, bl_attr_name))
## Setter:
## 1. Initialize bpy.props.StringProperty to Default (if undefined).
## 3. Encode value (implicitly using the annotated type).
## 2. Set bpy.props.StringProperty string.
def setter(_self: BLInstance, value: AttrType) -> None:
encoded_value = serialize.encode(value).decode('utf-8')
encoded_value = ENCODER.encode(value).decode('utf-8')
log.debug(
'Writing BLField attr "%s" w/encoded value: %s',
bl_attr_name,

View File

@ -132,7 +132,7 @@ def _socket_def_from_bl_socket(
def socket_def_from_bl_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
) -> sockets.base.SocketDef:
) -> ct.schemas.SocketDef:
"""Computes an appropriate (no-arg) SocketDef from the given `bl_interface_socket`, by parsing it."""
return _socket_def_from_bl_socket(
bl_interface_socket.description, bl_interface_socket.bl_socket_idname

View File

@ -65,6 +65,11 @@ from .data_flows import (
)
from .data_flow_actions import DataFlowAction
####################
# - Schemas
####################
from . import schemas
####################
# - Export
####################
@ -98,4 +103,5 @@ __all__ = [
'LazyDataValueRange',
'LazyDataValueSpectrum',
'DataFlowAction',
'schemas',
]

View File

@ -1,3 +1,4 @@
import enum
import pydantic as pyd
import typing_extensions as pytypes_ext

View File

@ -0,0 +1,11 @@
from .managed_obj import ManagedObj
from .managed_obj_def import ManagedObjDef
from .preset_def import PresetDef
from .socket_def import SocketDef
__all__ = [
'SocketDef',
'ManagedObj',
'ManagedObjDef',
'PresetDef',
]

View File

@ -0,0 +1,22 @@
import typing as typ
from ..bl import ManagedObjName
from ..managed_obj_type import ManagedObjType
class ManagedObj(typ.Protocol):
managed_obj_type: ManagedObjType
def __init__(
self,
name: ManagedObjName,
): ...
@property
def name(self) -> str: ...
@name.setter
def name(self, value: str): ...
def free(self): ...
def bl_select(self): ...

View File

@ -0,0 +1,10 @@
import typing as typ
import pydantic as pyd
from .managed_obj import ManagedObj
class ManagedObjDef(pyd.BaseModel):
mk: typ.Callable[[str], ManagedObj]
name_prefix: str = ''

View File

@ -2,10 +2,10 @@ import typing as typ
import pydantic as pyd
from .. import contracts as ct
from ..bl import PresetName, SocketName
class PresetDef(pyd.BaseModel):
label: ct.PresetName
label: PresetName
description: str
values: dict[ct.SocketName, typ.Any]
values: dict[SocketName, typ.Any]

View File

@ -0,0 +1,12 @@
import typing as typ
import bpy
from ..socket_types import SocketType
@typ.runtime_checkable
class SocketDef(typ.Protocol):
socket_type: SocketType
def init(self, bl_socket: bpy.types.NodeSocket) -> None: ...

View File

@ -1,5 +1,4 @@
from .base import ManagedObj
import typing as typ
# from .managed_bl_empty import ManagedBLEmpty
from .managed_bl_image import ManagedBLImage
@ -11,8 +10,9 @@ from .managed_bl_mesh import ManagedBLMesh
# from .managed_bl_volume import ManagedBLVolume
from .managed_bl_modifier import ManagedBLModifier
ManagedObj: typ.TypeAlias = ManagedBLImage | ManagedBLMesh | ManagedBLModifier
__all__ = [
'ManagedObj',
#'ManagedBLEmpty',
'ManagedBLImage',
#'ManagedBLCollection',

View File

@ -1,65 +0,0 @@
import abc
import typing as typ
from blender_maxwell.utils import logger, serialize
from .. import contracts as ct
log = logger.get(__name__)
class ManagedObj(abc.ABC):
managed_obj_type: ct.ManagedObjType
@abc.abstractmethod
def __init__(
self,
name: ct.ManagedObjName,
):
"""Initializes the managed object with a unique name."""
####################
# - Properties
####################
@property
@abc.abstractmethod
def name(self) -> str:
"""Retrieve the name of the managed object."""
@name.setter
@abc.abstractmethod
def name(self, value: str) -> None:
"""Retrieve the name of the managed object."""
####################
# - Methods
####################
@abc.abstractmethod
def free(self) -> None:
"""Cleanup the resources managed by the managed object."""
@abc.abstractmethod
def bl_select(self) -> None:
"""Select the managed object in Blender, if such an operation makes sense."""
@abc.abstractmethod
def hide_preview(self) -> None:
"""Select the managed object in Blender, if such an operation makes sense."""
####################
# - Serialization
####################
def dump_as_msgspec(self) -> serialize.NaiveRepresentation:
return [
serialize.TypeID.ManagedObj,
self.__class__.__name__,
self.name,
]
@staticmethod
def parse_as_msgspec(obj: serialize.NaiveRepresentation) -> typ.Self:
return next(
subclass(obj[2])
for subclass in ManagedObj.__subclasses__()
if subclass.__name__ == obj[1]
)

View File

@ -1,3 +1,4 @@
import functools
import bpy

View File

@ -1,4 +1,5 @@
import io
import time
import typing as typ
import bpy
@ -11,7 +12,6 @@ import typing_extensions as typx
from ....utils import logger
from .. import contracts as ct
from . import base
log = logger.get(__name__)
@ -76,7 +76,7 @@ def rgba_image_from_xyzf(xyz_freq, colormap: str | None = None):
return rgba_image_from_xyzf__grayscale(xyz_freq)
class ManagedBLImage(base.ManagedObj):
class ManagedBLImage(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLImage
_bl_image_name: str
@ -181,9 +181,6 @@ class ManagedBLImage(base.ManagedObj):
if bl_image := bpy.data.images.get(self.name):
self.preview_space.image = bl_image
def hide_preview(self) -> None:
self.preview_space.image = None
####################
# - Image Geometry
####################

View File

@ -6,7 +6,6 @@ import numpy as np
from ....utils import logger
from .. import contracts as ct
from . import base
from .managed_bl_collection import managed_collection, preview_collection
log = logger.get(__name__)
@ -15,7 +14,7 @@ log = logger.get(__name__)
####################
# - BLMesh
####################
class ManagedBLMesh(base.ManagedObj):
class ManagedBLMesh(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLMesh
_bl_object_name: str | None = None

View File

@ -8,7 +8,6 @@ import typing_extensions as typx
from ....utils import analyze_geonodes, logger
from .. import bl_socket_map
from .. import contracts as ct
from . import base
log = logger.get(__name__)
@ -161,7 +160,7 @@ def write_modifier(
####################
# - ManagedObj
####################
class ManagedBLModifier(base.ManagedObj):
class ManagedBLModifier(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLModifier
_modifier_name: str | None = None
@ -186,9 +185,6 @@ class ManagedBLModifier(base.ManagedObj):
def __init__(self, name: str):
self.name = name
def bl_select(self) -> None: pass
def hide_preview(self) -> None: pass
####################
# - Deallocation
####################

View File

@ -5,6 +5,7 @@ import bpy
from ...utils import logger
from . import contracts as ct
from .managed_objs.managed_bl_collection import preview_collection
log = logger.get(__name__)

View File

@ -4,7 +4,7 @@ import bpy
from .....utils import logger
from ... import contracts as ct
from ... import sockets
from ... import managed_objs, sockets
from .. import base, events
log = logger.get(__name__)
@ -245,9 +245,9 @@ class ExtractDataNode(base.MaxwellSimNode):
field_data = self._compute_input('Field Data')
return getattr(field_data, props['field_data__component'])
elif self.active_socket_set == 'Flux Data':
elif self.active_socket_set == 'Flux Data': # noqa: RET505
flux_data = self._compute_input('Flux Data')
return flux_data.flux
return getattr(flux_data, 'flux')
msg = f'Tried to get data from unknown output socket in "{self.bl_label}"'
raise RuntimeError(msg)

View File

@ -24,12 +24,23 @@ class VizNode(base.MaxwellSimNode):
'Data': sockets.AnySocketDef(),
'Freq': sockets.PhysicalFreqSocketDef(),
}
# input_sockets_sets: typ.ClassVar = {
# '2D Freq': {
# 'Data': sockets.AnySocketDef(),
# 'Freq': sockets.PhysicalFreqSocketDef(),
# },
# }
output_sockets: typ.ClassVar = {
'Preview': sockets.AnySocketDef(),
}
managed_obj_types: typ.ClassVar = {
'plot': managed_objs.ManagedBLImage,
managed_obj_defs: typ.ClassVar = {
'plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
),
#'empty': ct.schemas.ManagedObjDef(
# mk=lambda name: managed_objs.ManagedBLEmpty(name),
# ),
}
#####################
@ -67,7 +78,7 @@ class VizNode(base.MaxwellSimNode):
)
def on_show_plot(
self,
managed_objs: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict,
props: dict,
unit_systems: dict,

View File

@ -12,13 +12,11 @@ import bpy
import sympy as sp
import typing_extensions as typx
from blender_maxwell.utils import logger
from .. import bl_cache, sockets
from ....utils import logger
from .. import bl_cache
from .. import contracts as ct
from .. import managed_objs as _managed_objs
from . import events
from . import presets as _presets
log = logger.get(__name__)
@ -44,27 +42,21 @@ class MaxwellSimNode(bpy.types.Node):
## TODO: bl_description from first line of __doc__?
# Sockets
input_sockets: typ.ClassVar[dict[str, sockets.base.SocketDef]] = MappingProxyType(
{}
)
output_sockets: typ.ClassVar[dict[str, sockets.base.SocketDef]] = MappingProxyType(
{}
)
input_socket_sets: typ.ClassVar[dict[str, dict[str, sockets.base.SocketDef]]] = (
input_sockets: typ.ClassVar[dict[str, ct.schemas.SocketDef]] = MappingProxyType({})
output_sockets: typ.ClassVar[dict[str, ct.schemas.SocketDef]] = MappingProxyType({})
input_socket_sets: typ.ClassVar[dict[str, dict[str, ct.schemas.SocketDef]]] = (
MappingProxyType({})
)
output_socket_sets: typ.ClassVar[dict[str, dict[str, sockets.base.SocketDef]]] = (
output_socket_sets: typ.ClassVar[dict[str, dict[str, ct.schemas.SocketDef]]] = (
MappingProxyType({})
)
# Presets
presets: typ.ClassVar[dict[str, dict[str, _presets.PresetDef]]] = MappingProxyType(
{}
)
presets: typ.ClassVar = MappingProxyType({})
# Managed Objects
managed_obj_types: typ.ClassVar[
dict[ct.ManagedObjName, type[_managed_objs.ManagedObj]]
managed_obj_defs: typ.ClassVar[
dict[ct.ManagedObjName, ct.schemas.ManagedObjDef]
] = MappingProxyType({})
####################
@ -230,7 +222,7 @@ class MaxwellSimNode(bpy.types.Node):
####################
@events.on_value_changed(
prop_name='sim_node_name',
props={'sim_node_name', 'managed_objs', 'managed_obj_types'},
props={'sim_node_name', 'managed_objs', 'managed_obj_defs'},
)
def _on_sim_node_name_changed(self, props: dict):
log.info(
@ -241,8 +233,9 @@ class MaxwellSimNode(bpy.types.Node):
)
# Set Name of Managed Objects
for mobj in props['managed_objs'].values():
mobj.name = props['sim_node_name']
for mobj_id, mobj in props['managed_objs'].items():
mobj_def = props['managed_obj_defs'][mobj_id]
mobj.name = mobj_def.name_prefix + props['sim_node_name']
@events.on_value_changed(prop_name='active_socket_set')
def _on_socket_set_changed(self):
@ -289,6 +282,8 @@ class MaxwellSimNode(bpy.types.Node):
def _on_preview_changed(self, props):
if not props['preview_active']:
for mobj in self.managed_objs.values():
if isinstance(mobj, _managed_objs.ManagedBLMesh):
## TODO: This is a Workaround
mobj.hide_preview()
@events.on_enable_lock()
@ -308,8 +303,8 @@ class MaxwellSimNode(bpy.types.Node):
####################
# - Loose Sockets w/Events
####################
loose_input_sockets: dict[str, sockets.base.SocketDef] = bl_cache.BLField({})
loose_output_sockets: dict[str, sockets.base.SocketDef] = bl_cache.BLField({})
loose_input_sockets: dict[str, ct.schemas.SocketDef] = bl_cache.BLField({})
loose_output_sockets: dict[str, ct.schemas.SocketDef] = bl_cache.BLField({})
@events.on_value_changed(prop_name={'loose_input_sockets', 'loose_output_sockets'})
def _on_loose_sockets_changed(self):
@ -339,7 +334,7 @@ class MaxwellSimNode(bpy.types.Node):
def _active_socket_set_socket_defs(
self,
direc: typx.Literal['input', 'output'],
) -> dict[ct.SocketName, sockets.base.SocketDef]:
) -> dict[ct.SocketName, ct.schemas.SocketDef]:
"""Retrieve all socket definitions for sockets that should be defined, according to the `self.active_socket_set`.
Notes:
@ -349,7 +344,7 @@ class MaxwellSimNode(bpy.types.Node):
direc: The direction to load Blender sockets from.
Returns:
Mapping from socket names to corresponding `sockets.base.SocketDef`s.
Mapping from socket names to corresponding `ct.schemas.SocketDef`s.
If `self.active_socket_set` is None, the empty dict is returned.
"""
@ -365,14 +360,14 @@ class MaxwellSimNode(bpy.types.Node):
def active_socket_defs(
self, direc: typx.Literal['input', 'output']
) -> dict[ct.SocketName, sockets.base.SocketDef]:
) -> dict[ct.SocketName, ct.schemas.SocketDef]:
"""Retrieve all socket definitions for sockets that should be defined.
Parameters:
direc: The direction to load Blender sockets from.
Returns:
Mapping from socket names to corresponding `sockets.base.SocketDef`s.
Mapping from socket names to corresponding `ct.schemas.SocketDef`s.
"""
static_sockets = self.input_sockets if direc == 'input' else self.output_sockets
loose_sockets = (
@ -465,17 +460,33 @@ class MaxwellSimNode(bpy.types.Node):
####################
# - Managed Objects
####################
@bl_cache.cached_bl_property(persist=True)
managed_bl_meshes: dict[str, _managed_objs.ManagedBLMesh] = bl_cache.BLField({})
managed_bl_images: dict[str, _managed_objs.ManagedBLImage] = bl_cache.BLField({})
managed_bl_modifiers: dict[str, _managed_objs.ManagedBLModifier] = bl_cache.BLField(
{}
)
@bl_cache.cached_bl_property(
persist=False
) ## Disable broken ManagedObj union DECODER
def managed_objs(self) -> dict[str, _managed_objs.ManagedObj]:
"""Access the managed objects defined on this node.
Persistent cache ensures that the managed objects are only created on first access, even across file reloads.
"""
if self.managed_obj_types:
if self.managed_obj_defs:
if not (
managed_objs := (
self.managed_bl_meshes
| self.managed_bl_images
| self.managed_bl_modifiers
)
):
return {
mobj_name: mobj_type(self.sim_node_name)
for mobj_name, mobj_type in self.managed_obj_types.items()
mobj_name: mobj_def.mk(mobj_def.name_prefix + self.sim_node_name)
for mobj_name, mobj_def in self.managed_obj_defs.items()
}
return managed_objs
return {}
@ -553,7 +564,7 @@ class MaxwellSimNode(bpy.types.Node):
####################
@bl_cache.keyed_cache(
exclude={'self', 'optional'},
encode={'unit_system'},
serialize={'unit_system'},
)
def _compute_input(
self,
@ -708,6 +719,13 @@ class MaxwellSimNode(bpy.types.Node):
stop_propagation |= event_method.stop_propagation
event_method(self)
# Stop Propagation (maybe)
if (
ct.DataFlowAction.stop_if_no_event_methods(action)
and len(triggered_event_methods) == 0
):
return
# Propagate Action to All Sockets in "Trigger Direction"
## The trigger chain goes node/socket/node/socket/...
if not stop_propagation:

View File

@ -47,8 +47,11 @@ class Tidy3DFileImporterNode(base.MaxwellSimNode):
input_sockets: typ.ClassVar = {
'File Path': sockets.FilePathSocketDef(),
}
managed_obj_types: typ.ClassVar = {
'plot': managed_objs.ManagedBLImage,
managed_obj_defs: typ.ClassVar = {
'plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix='',
),
}
####################

View File

@ -1,6 +1,8 @@
import typing as typ
from pathlib import Path
import tidy3d as td
from ...... import info
from ......services import tdcloud
from ......utils import logger

View File

@ -26,8 +26,11 @@ class LibraryMediumNode(base.MaxwellSimNode):
'Medium': sockets.MaxwellMediumSocketDef(),
}
managed_obj_types = {
'nk_plot': managed_objs.ManagedBLImage,
managed_obj_defs = {
'nk_plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix='',
)
}
####################
@ -122,8 +125,8 @@ class LibraryMediumNode(base.MaxwellSimNode):
)
def on_show_plot(
self,
managed_objs: dict,
props: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
props: dict[str, typ.Any],
):
medium = td.material_library[props['material']].medium
freq_range = [

View File

@ -1,6 +1,7 @@
import typing as typ
import sympy as sp
import sympy.physics.units as spu
import tidy3d as td
from .....assets.import_geonodes import GeoNodes, import_geonodes
@ -49,9 +50,13 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
'Time Domain': {'Time Monitor': sockets.MaxwellMonitorSocketDef()},
}
managed_obj_types: typ.ClassVar = {
'mesh': managed_objs.ManagedBLMesh,
'modifier': managed_objs.ManagedBLModifier,
managed_obj_defs: typ.ClassVar = {
'mesh': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLMesh(name),
),
'modifier': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLModifier(name),
),
}
####################
@ -113,7 +118,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
def on_inputs_changed(
self,
props: dict,
managed_objs: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict,
unit_systems: dict,
):

View File

@ -1,6 +1,8 @@
import typing as typ
import bpy
import sympy as sp
import sympy.physics.units as spu
import tidy3d as td
from .....assets.import_geonodes import GeoNodes, import_geonodes
@ -48,9 +50,13 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
'Time Domain': {'Time Monitor': sockets.MaxwellMonitorSocketDef()},
}
managed_obj_types: typ.ClassVar = {
'mesh': managed_objs.ManagedBLMesh,
'modifier': managed_objs.ManagedBLModifier,
managed_obj_defs: typ.ClassVar = {
'mesh': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLMesh(name),
),
'modifier': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLModifier(name),
),
}
####################
@ -114,7 +120,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
def on_inputs_changed(
self,
props: dict,
managed_objs: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict,
unit_systems: dict,
):

View File

@ -6,6 +6,7 @@ import sympy as sp
from .....utils import logger
from ... import contracts as ct
from ... import sockets
from ...managed_objs.managed_bl_collection import preview_collection
from .. import base, events
log = logger.get(__name__)

View File

@ -28,9 +28,14 @@ class SimDomainNode(base.MaxwellSimNode):
'Domain': sockets.MaxwellSimDomainSocketDef(),
}
managed_obj_types: typ.ClassVar = {
'mesh': managed_objs.ManagedBLMesh,
'modifier': managed_objs.ManagedBLModifier,
managed_obj_defs: typ.ClassVar = {
'mesh': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLMesh(name),
name_prefix='',
),
'modifier': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLModifier(name),
),
}
####################
@ -70,7 +75,7 @@ class SimDomainNode(base.MaxwellSimNode):
def on_input_changed(
self,
props: dict,
managed_objs: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict,
unit_systems: dict,
):

View File

@ -56,8 +56,11 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
'Source': sockets.MaxwellSourceSocketDef(),
}
managed_obj_types: typ.ClassVar = {
'plane_wave_source': managed_objs.ManagedBLMesh,
managed_obj_defs: typ.ClassVar = {
'plane_wave_source': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix='',
)
}
####################

View File

@ -1,6 +1,7 @@
import typing as typ
import bpy
import sympy.physics.units as spu
import tidy3d as td
from ... import contracts as ct
@ -26,8 +27,11 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
'Source': sockets.MaxwellSourceSocketDef(),
}
managed_obj_types = {
'mesh': managed_objs.ManagedBLMesh,
managed_obj_defs = {
'sphere_empty': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix='',
)
}
####################

View File

@ -41,8 +41,11 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
}
managed_obj_types = {
'amp_time': managed_objs.ManagedBLImage,
managed_obj_defs = {
'amp_time': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix='amp_time_',
)
}
####################
@ -120,9 +123,9 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
)
def on_show_plot(
self,
managed_objs: dict,
output_sockets: dict,
props: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
output_sockets: dict[str, typ.Any],
props: dict[str, typ.Any],
):
temporal_shape = output_sockets['Temporal Shape']
plot_time_start = props['plot_time_start'] * 1e-15

View File

@ -27,9 +27,13 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
'Structure': sockets.MaxwellStructureSocketDef(),
}
managed_obj_types: typ.ClassVar = {
'mesh': managed_objs.ManagedBLMesh,
'modifier': managed_objs.ManagedBLModifier,
managed_obj_defs: typ.ClassVar = {
'mesh': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLMesh(name),
),
'modifier': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLModifier(name),
),
}
####################
@ -42,8 +46,8 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
)
def compute_structure(
self,
input_sockets: dict,
managed_objs: dict,
input_sockets: dict[str, typ.Any],
managed_objs: dict[str, ct.schemas.ManagedObj],
) -> td.Structure:
# Simulate Input Value Change
## This ensures that the mesh has been re-computed.
@ -64,7 +68,7 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
# - Event Methods
####################
@events.on_value_changed(
socket_name={'GeoNodes', 'Center'},
socket_name='GeoNodes',
prop_name='preview_active',
any_loose_input_socket=True,
run_on_init=True,
@ -79,7 +83,7 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
def on_input_changed(
self,
props: dict,
managed_objs: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict,
loose_input_sockets: dict,
unit_systems: dict,

View File

@ -29,9 +29,13 @@ class BoxStructureNode(base.MaxwellSimNode):
'Structure': sockets.MaxwellStructureSocketDef(),
}
managed_obj_types: typ.ClassVar = {
'mesh': managed_objs.ManagedBLMesh,
'modifier': managed_objs.ManagedBLModifier,
managed_obj_defs: typ.ClassVar = {
'mesh': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLMesh(name),
),
'modifier': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLModifier(name),
),
}
####################
@ -70,7 +74,7 @@ class BoxStructureNode(base.MaxwellSimNode):
def on_inputs_changed(
self,
props: dict,
managed_objs: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict,
unit_systems: dict,
):

View File

@ -28,9 +28,13 @@ class SphereStructureNode(base.MaxwellSimNode):
'Structure': sockets.MaxwellStructureSocketDef(),
}
managed_obj_types: typ.ClassVar = {
'mesh': managed_objs.ManagedBLMesh,
'modifier': managed_objs.ManagedBLModifier,
managed_obj_defs: typ.ClassVar = {
'mesh': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLMesh(name),
),
'modifier': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLModifier(name),
),
}
####################
@ -73,7 +77,7 @@ class SphereStructureNode(base.MaxwellSimNode):
def on_inputs_changed(
self,
props: dict,
managed_objs: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict,
unit_systems: dict,
):

View File

@ -1,46 +1,17 @@
import abc
import functools
import typing as typ
import bpy
import pydantic as pyd
import sympy as sp
import sympy.physics.units as spu
import typing_extensions as typx
from ....utils import logger, serialize
from ....utils import logger
from .. import contracts as ct
log = logger.get(__name__)
####################
# - SocketDef
####################
class SocketDef(pyd.BaseModel, abc.ABC):
socket_type: ct.SocketType
@abc.abstractmethod
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
"""Initializes a real Blender node socket from this socket definition."""
####################
# - Serialization
####################
def dump_as_msgspec(self) -> serialize.NaiveRepresentation:
return [serialize.TypeID.SocketDef, self.__class__.__name__, self.model_dump()]
@staticmethod
def parse_as_msgspec(obj: serialize.NaiveRepresentation) -> typ.Self:
return next(
subclass(**obj[2])
for subclass in SocketDef.__subclasses__()
if subclass.__name__ == obj[1]
)
####################
# - SocketDef
####################
class MaxwellSimSocket(bpy.types.NodeSocket):
# Fundamentals
socket_type: ct.SocketType

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -22,7 +23,7 @@ class AnyBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class AnySocketDef(base.SocketDef):
class AnySocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.Any
def init(self, bl_socket: AnyBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -43,7 +44,7 @@ class BoolBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class BoolSocketDef(base.SocketDef):
class BoolSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.Bool
default_value: bool = False

View File

@ -1,6 +1,7 @@
from pathlib import Path
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -45,7 +46,7 @@ class FilePathBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class FilePathSocketDef(base.SocketDef):
class FilePathSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.FilePath
default_path: Path = Path()

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -42,7 +43,7 @@ class StringBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class StringSocketDef(base.SocketDef):
class StringSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.String
default_text: str = ''

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -42,7 +43,7 @@ class BlenderCollectionBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class BlenderCollectionSocketDef(base.SocketDef):
class BlenderCollectionSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.BlenderCollection
def init(self, bl_socket: BlenderCollectionBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -81,7 +82,7 @@ class BlenderGeoNodesBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class BlenderGeoNodesSocketDef(base.SocketDef):
class BlenderGeoNodesSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.BlenderGeoNodes
def init(self, bl_socket: BlenderGeoNodesBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -42,7 +43,7 @@ class BlenderImageBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class BlenderImageSocketDef(base.SocketDef):
class BlenderImageSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.BlenderImage
def init(self, bl_socket: BlenderImageBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -39,7 +40,7 @@ class BlenderMaterialBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class BlenderMaterialSocketDef(base.SocketDef):
class BlenderMaterialSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.BlenderMaterial
def init(self, bl_socket: BlenderMaterialBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -87,7 +88,7 @@ class BlenderObjectBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class BlenderObjectSocketDef(base.SocketDef):
class BlenderObjectSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.BlenderObject
def init(self, bl_socket: BlenderObjectBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -42,7 +43,7 @@ class BlenderTextBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class BlenderTextSocketDef(base.SocketDef):
class BlenderTextSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.BlenderText
def init(self, bl_socket: BlenderTextBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import tidy3d as td
import typing_extensions as typx
@ -52,7 +53,7 @@ class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellBoundCondSocketDef(base.SocketDef):
class MaxwellBoundCondSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundCond
default_choice: typx.Literal['PML', 'PEC', 'PMC', 'PERIODIC'] = 'PML'

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import tidy3d as td
from ... import contracts as ct
@ -123,7 +124,7 @@ class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellBoundCondsSocketDef(base.SocketDef):
class MaxwellBoundCondsSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundConds
def init(self, bl_socket: MaxwellBoundCondsBLSocket) -> None:

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -15,7 +16,7 @@ class MaxwellFDTDSimBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellFDTDSimSocketDef(base.SocketDef):
class MaxwellFDTDSimSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellFDTDSim
def init(self, bl_socket: MaxwellFDTDSimBLSocket) -> None:

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -15,7 +16,7 @@ class MaxwellFDTDSimDataBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellFDTDSimDataSocketDef(base.SocketDef):
class MaxwellFDTDSimDataSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellFDTDSimData
def init(self, bl_socket: MaxwellFDTDSimDataBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import scipy as sc
import sympy.physics.units as spu
import tidy3d as td
@ -97,7 +98,7 @@ class MaxwellMediumBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellMediumSocketDef(base.SocketDef):
class MaxwellMediumSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellMedium
default_permittivity_real: float = 1.0

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -11,7 +12,7 @@ class MaxwellMediumNonLinearityBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellMediumNonLinearitySocketDef(base.SocketDef):
class MaxwellMediumNonLinearitySocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellMediumNonLinearity
def init(self, bl_socket: MaxwellMediumNonLinearityBLSocket) -> None:

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -11,7 +12,7 @@ class MaxwellMonitorBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellMonitorSocketDef(base.SocketDef):
class MaxwellMonitorSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellMonitor
is_list: bool = False

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -11,7 +12,7 @@ class MaxwellSimDomainBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellSimDomainSocketDef(base.SocketDef):
class MaxwellSimDomainSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellSimDomain
def init(self, bl_socket: MaxwellSimDomainBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import tidy3d as td
from ... import contracts as ct
@ -47,7 +48,7 @@ class MaxwellSimGridBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellSimGridSocketDef(base.SocketDef):
class MaxwellSimGridSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellSimGrid
min_steps_per_wl: float = 10.0

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -11,7 +12,7 @@ class MaxwellSimGridAxisBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellSimGridAxisSocketDef(base.SocketDef):
class MaxwellSimGridAxisSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellSimGridAxis
def init(self, bl_socket: MaxwellSimGridAxisBLSocket) -> None:

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -11,7 +12,7 @@ class MaxwellSourceBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellSourceSocketDef(base.SocketDef):
class MaxwellSourceSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellSource
is_list: bool = False

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -11,7 +12,7 @@ class MaxwellStructureBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellStructureSocketDef(base.SocketDef):
class MaxwellStructureSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellStructure
is_list: bool = False

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -11,7 +12,7 @@ class MaxwellTemporalShapeBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellTemporalShapeSocketDef(base.SocketDef):
class MaxwellTemporalShapeSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellTemporalShape
def init(self, bl_socket: MaxwellTemporalShapeBLSocket) -> None:

View File

@ -1,6 +1,7 @@
import typing as typ
import bpy
import pydantic as pyd
import sympy as sp
from .....utils.pydantic_sympy import SympyExpr
@ -118,7 +119,7 @@ class ComplexNumberBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class ComplexNumberSocketDef(base.SocketDef):
class ComplexNumberSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.ComplexNumber
default_value: SympyExpr = sp.S(0 + 0j)

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -43,7 +44,7 @@ class IntegerNumberBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class IntegerNumberSocketDef(base.SocketDef):
class IntegerNumberSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.IntegerNumber
default_value: int = 0

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy as sp
from .....utils.pydantic_sympy import SympyExpr
@ -54,7 +55,7 @@ class RationalNumberBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class RationalNumberSocketDef(base.SocketDef):
class RationalNumberSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.RationalNumber
default_value: SympyExpr = sp.Rational(0, 1)

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
@ -48,7 +49,7 @@ class RealNumberBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class RealNumberSocketDef(base.SocketDef):
class RealNumberSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.RealNumber
default_value: float = 0.0

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy.physics.units as spu
from .....utils.pydantic_sympy import SympyExpr
@ -46,7 +47,7 @@ class PhysicalAccelScalarBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalAccelScalarSocketDef(base.SocketDef):
class PhysicalAccelScalarSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalAccelScalar
default_unit: SympyExpr | None = None

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy.physics.units as spu
from .....utils.pydantic_sympy import SympyExpr
@ -46,7 +47,7 @@ class PhysicalAngleBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalAngleSocketDef(base.SocketDef):
class PhysicalAngleSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalAngle
default_unit: SympyExpr | None = None

View File

@ -1,6 +1,7 @@
import typing as typ
import bpy
import pydantic as pyd
import sympy as sp
from ... import contracts as ct
@ -54,7 +55,7 @@ class PhysicalAreaBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalAreaSocketDef(base.SocketDef):
class PhysicalAreaSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalArea
default_unit: typ.Any | None = None

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy.physics.units as spu
from .....utils.pydantic_sympy import SympyExpr
@ -46,7 +47,7 @@ class PhysicalForceScalarBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalForceScalarSocketDef(base.SocketDef):
class PhysicalForceScalarSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalForceScalar
default_unit: SympyExpr | None = None

View File

@ -1,5 +1,7 @@
import bpy
import pydantic as pyd
import sympy as sp
import sympy.physics.units as spu
from .....utils import extra_sympy_units as spux
from .....utils import logger
@ -95,7 +97,7 @@ class PhysicalFreqBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalFreqSocketDef(base.SocketDef):
class PhysicalFreqSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalFreq
is_array: bool = False

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy as sp
import sympy.physics.units as spu
@ -95,7 +96,7 @@ class PhysicalLengthBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalLengthSocketDef(base.SocketDef):
class PhysicalLengthSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalLength
is_array: bool = False

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy.physics.units as spu
from .....utils.pydantic_sympy import SympyExpr
@ -46,7 +47,7 @@ class PhysicalMassBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalMassSocketDef(base.SocketDef):
class PhysicalMassSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalMass
default_unit: SympyExpr | None = None

View File

@ -1,6 +1,7 @@
import typing as typ
import bpy
import pydantic as pyd
import sympy as sp
import sympy.physics.units as spu
@ -47,7 +48,7 @@ class PhysicalPoint3DBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalPoint3DSocketDef(base.SocketDef):
class PhysicalPoint3DSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalPoint3D
default_unit: typ.Any | None = None

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy as sp
import sympy.physics.optics.polarization as spo_pol
import sympy.physics.units as spu
@ -236,7 +237,7 @@ class PhysicalPolBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalPolSocketDef(base.SocketDef):
class PhysicalPolSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalPol
def init(self, bl_socket: PhysicalPolBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy as sp
import sympy.physics.units as spu
@ -45,7 +46,7 @@ class PhysicalSize3DBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalSize3DSocketDef(base.SocketDef):
class PhysicalSize3DSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalSize3D
default_value: SympyExpr = sp.Matrix([1, 1, 1]) * spu.um

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy.physics.units as spu
from .....utils.pydantic_sympy import SympyExpr
@ -46,7 +47,7 @@ class PhysicalSpeedBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalSpeedSocketDef(base.SocketDef):
class PhysicalSpeedSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalSpeed
default_unit: SympyExpr | None = None

View File

@ -1,6 +1,7 @@
import typing as typ
import bpy
import pydantic as pyd
import sympy.physics.units as spu
from .....utils.pydantic_sympy import SympyExpr
@ -48,7 +49,7 @@ class PhysicalTimeBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalTimeSocketDef(base.SocketDef):
class PhysicalTimeSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalTime
default_value: SympyExpr | None = None

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
@ -272,7 +273,7 @@ class PhysicalUnitSystemBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalUnitSystemSocketDef(base.SocketDef):
class PhysicalUnitSystemSocketDef(pyd.BaseModel):
socket_type: ST = ST.PhysicalUnitSystem
show_by_default: bool = False

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy.physics.units as spu
from .....utils.pydantic_sympy import SympyExpr
@ -43,7 +44,7 @@ class PhysicalVolumeBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalVolumeSocketDef(base.SocketDef):
class PhysicalVolumeSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.PhysicalVolume
default_unit: SympyExpr | None = None

View File

@ -3,10 +3,9 @@ import types
from .. import contracts as ct
## TODO: Replace with BL_SOCKET_DEFS export from each module, a little like BL_NODES.
def scan_for_socket_defs(
sockets_module: types.ModuleType,
) -> dict:
) -> dict[ct.SocketType, type[ct.schemas.SocketDef]]:
return {
socket_type: getattr(
sockets_module,

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
from .....services import tdcloud
from ... import contracts as ct
@ -318,7 +319,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class Tidy3DCloudTaskSocketDef(base.SocketDef):
class Tidy3DCloudTaskSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.Tidy3DCloudTask
should_exist: bool

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -14,7 +15,7 @@ class Complex2DVectorBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class Complex2DVectorSocketDef(base.SocketDef):
class Complex2DVectorSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.Complex2DVector
def init(self, bl_socket: Complex2DVectorBLSocket) -> None:

View File

@ -1,3 +1,4 @@
import pydantic as pyd
from ... import contracts as ct
from .. import base
@ -14,7 +15,7 @@ class Complex3DVectorBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class Complex3DVectorSocketDef(base.SocketDef):
class Complex3DVectorSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.Complex3DVector
def init(self, bl_socket: Complex3DVectorBLSocket) -> None:

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy as sp
from .....utils.pydantic_sympy import ConstrSympyExpr
@ -53,7 +54,7 @@ class Integer3DVectorBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class Integer3DVectorSocketDef(base.SocketDef):
class Integer3DVectorSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.Integer3DVector
default_value: Integer3DVector = sp.Matrix([0, 0, 0])

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy as sp
from .....utils.pydantic_sympy import ConstrSympyExpr
@ -54,7 +55,7 @@ class Real2DVectorBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class Real2DVectorSocketDef(base.SocketDef):
class Real2DVectorSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.Real2DVector
default_value: Real2DVector = sp.Matrix([0.0, 0.0])

View File

@ -1,4 +1,5 @@
import bpy
import pydantic as pyd
import sympy as sp
from .....utils.pydantic_sympy import ConstrSympyExpr
@ -54,7 +55,7 @@ class Real3DVectorBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class Real3DVectorSocketDef(base.SocketDef):
class Real3DVectorSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.Real3DVector
default_value: Real3DVector = sp.Matrix([0.0, 0.0, 0.0])

View File

@ -5,8 +5,6 @@ import typing as typ
import sympy as sp
import sympy.physics.units as spu
SympyType = sp.Basic | sp.Expr | sp.MatrixBase | spu.Quantity
####################
# - Useful Methods

View File

@ -1,182 +0,0 @@
"""Robust serialization tool for use in the addon.
Attributes:
NaiveEncodableType: See <https://jcristharif.com/msgspec/supported-types.html> for details.
"""
import dataclasses
import datetime as dt
import decimal
import enum
import functools
import typing as typ
import uuid
import msgspec
import sympy as sp
from . import extra_sympy_units as spux
from . import logger
log = logger.get(__name__)
####################
# - Serialization Types
####################
NaivelyEncodableType: typ.TypeAlias = (
None
| bool
| int
| float
| str
| bytes
| bytearray
## NO SUPPORT:
# | memoryview
| tuple
| list
| dict
| set
| frozenset
## NO SUPPORT:
# | typ.Literal
| typ.Collection
## NO SUPPORT:
# | typ.Sequence ## -> list
# | typ.MutableSequence ## -> list
# | typ.AbstractSet ## -> set
# | typ.MutableSet ## -> set
# | typ.Mapping ## -> dict
# | typ.MutableMapping ## -> dict
| typ.TypedDict
| typ.NamedTuple
| dt.datetime
| dt.date
| dt.time
| dt.timedelta
| uuid.UUID
| decimal.Decimal
## NO SUPPORT:
# | enum.Enum
| enum.IntEnum
| enum.Flag
| enum.IntFlag
| dataclasses.dataclass
| typ.Optional[typ.Any] # noqa: UP007
| typ.Union[typ.Any, ...] # noqa: UP007
| typ.NewType
| typ.TypeAlias
## SUPPORT:
# | typ.Generic[typ.Any]
# | typ.TypeVar
# | typ.Final
| msgspec.Raw
## NO SUPPORT:
# | msgspec.UNSET
)
_NaivelyEncodableTypeSet = frozenset(typ.get_args(NaivelyEncodableType))
## TODO: Use for runtime type check? Beartype?
class TypeID(enum.StrEnum):
Complex: str = '!type=complex'
SympyType: str = '!type=sympytype'
SocketDef: str = '!type=socketdef'
ManagedObj: str = '!type=managedobj'
NaiveRepresentation: typ.TypeAlias = list[TypeID, str | None, typ.Any]
def is_representation(obj: NaivelyEncodableType) -> bool:
return isinstance(obj, list) and len(obj) == 3 and obj[0] in set(TypeID) # noqa: PLR2004
####################
# - Serialization Hooks
####################
def _enc_hook(obj: typ.Any) -> NaivelyEncodableType:
"""Translates types not natively supported by `msgspec`, to an encodable form supported by `msgspec`.
Parameters:
obj: The object of arbitrary type to transform into an encodable value.
Returns:
A value encodable by `msgspec`.
Raises:
NotImplementedError: When the type transformation hasn't been implemented.
"""
if isinstance(obj, complex):
return ['!type=complex', None, (obj.real, obj.imag)]
if isinstance(obj, spux.SympyType):
return ['!type=sympytype', None, sp.srepr(obj)]
if hasattr(obj, 'dump_as_msgspec'):
return obj.dump_as_msgspec()
msg = f'Can\'t encode "{obj}" of type {type(obj)}'
raise NotImplementedError(msg)
def _dec_hook(_type: type, obj: NaivelyEncodableType) -> typ.Any:
"""Translates the `msgspec`-encoded form of an object back to its true form.
Parameters:
_type: The type to transform the `msgspec`-encoded object back into.
obj: The naively decoded object to transform back into its actual type.
Returns:
A value encodable by `msgspec`.
Raises:
NotImplementedError: When the type transformation hasn't been implemented.
"""
if _type is complex or (is_representation(obj) and obj[0] == TypeID.Complex):
obj_value = obj[2]
return complex(obj_value[0], obj_value[1])
if _type in typ.get_args(spux.SympyType) or (
is_representation(obj) and obj[0] == TypeID.SympyType
):
obj_value = obj[2]
return sp.sympify(obj_value).subs(spux.ALL_UNIT_SYMBOLS)
if hasattr(_type, 'parse_as_msgspec') and (
is_representation(obj) and obj[0] in [TypeID.SocketDef, TypeID.ManagedObj]
):
return _type.parse_as_msgspec(obj)
msg = f'Can\'t decode "{obj}" to type {type(obj)}'
raise NotImplementedError(msg)
####################
# - Global Encoders / Decoders
####################
_ENCODER = msgspec.json.Encoder(enc_hook=_enc_hook, order='deterministic')
@functools.cache
def _DECODER(_type: type) -> msgspec.json.Decoder: # noqa: N802
"""Retrieve a suitable `msgspec.json.Decoder` by-type.
Parameters:
_type: The type to retrieve a decoder for.
Returns:
A suitable decoder.
"""
return msgspec.json.Decoder(type=_type, dec_hook=_dec_hook)
####################
# - Encoder / Decoder Functions
####################
def encode(obj: typ.Any) -> bytes:
return _ENCODER.encode(obj)
def decode(_type: type, obj: str | bytes) -> typ.Any:
return _DECODER(_type).decode(obj)

View File

@ -9,6 +9,7 @@ import bpy
PATH_SCRIPT = str(Path(__file__).resolve().parent)
sys.path.insert(0, str(PATH_SCRIPT))
import info # noqa: E402
import pack # noqa: E402
sys.path.remove(str(PATH_SCRIPT))