Compare commits

..

5 Commits

Author SHA1 Message Date
Sofus Albert Høgsbro Rose 568fc449e8
fix: Caching now (seems to) work robustly.
The serialization routines are fast and effective.
Overall, the node graph feels snappy, and everything updates smoothly.
Logging on the action chain suggests that there aren't extraneous calls,
and that existing calls (ex. no-op previews) are fast.

There will likely be edge cases, and we'll see how it scales - but
for now, let's go with it!
2024-04-15 19:15:20 +02:00
Sofus Albert Høgsbro Rose 269af4ba32
refactor: Ran lint fix 2024-04-15 17:49:53 +02:00
Sofus Albert Høgsbro Rose b5a4b6b0a1
refactor: Common SocketDef owner in `sockets.base` 2024-04-15 17:48:29 +02:00
Sofus Albert Høgsbro Rose 4f6bd8e990
refactor: Revamped serialization (non-working) 2024-04-15 17:46:31 +02:00
Sofus Albert Høgsbro Rose 3def85e24f
refactor: Non-working first-move of serialization logic 2024-04-15 15:21:13 +02:00
85 changed files with 464 additions and 512 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!!
- [ ] Optimize the `DataChanged` invalidator.
- [x] Optimize the `DataChanged` invalidator.
- [ ] Optimize unit stripping.
## Keyed Cache
- [ ] Implement `bl_cache.KeyedCache` for, especially, abstracting the caches underlying the input and output sockets.
- [x] 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
- [ ] 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
- [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
- [ ] Collect with a `BL_SOCKET_DEFS` object, instead of manually from `__init__.py`s
- [ ] Add support for `.msgspec_*()` methods, so that we remove the dependency on sockets from the serialization module.
- [x] 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,14 +5,8 @@ import inspect
import typing as typ
import bpy
import msgspec
import sympy as sp
import sympy.physics.units as spu
from ...utils import extra_sympy_units as spux
from ...utils import logger
from . import contracts as ct
from . import managed_objs, sockets
from ...utils import logger, serialize
log = logger.get(__name__)
@ -39,149 +33,12 @@ class BLInstance(typ.Protocol):
) -> 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
PropGetMethod: typ.TypeAlias = typ.Callable[
[BLInstance], serialize.NaivelyEncodableType
]
PropSetMethod: typ.TypeAlias = typ.Callable[
[BLInstance, serialize.NaivelyEncodableType], None
]
####################
@ -210,7 +67,7 @@ class KeyedCache:
self,
func: typ.Callable,
exclude: set[str],
serialize: set[str],
encode: set[str],
):
# Function Information
self.func: typ.Callable = func
@ -219,7 +76,7 @@ class KeyedCache:
# Arg -> Key Information
self.exclude: set[str] = exclude
self.include: set[str] = set(self.func_sig.parameters.keys()) - exclude
self.serialize: set[str] = serialize
self.encode: set[str] = encode
# Cache Information
self.key_schema: tuple[str, ...] = tuple(
@ -247,8 +104,8 @@ class KeyedCache:
[
(
arg_value
if arg_name not in self.serialize
else ENCODER.encode(arg_value)
if arg_name not in self.encode
else serialize.encode(arg_value)
)
for arg_name, arg_value in arguments.items()
if arg_name in self.include
@ -298,8 +155,8 @@ class KeyedCache:
# Compute Keys to Invalidate
arguments_hashable = {
arg_name: ENCODER.encode(arg_value)
if arg_name in self.serialize and arg_name not in wildcard_arguments
arg_name: serialize.encode(arg_value)
if arg_name in self.encode and arg_name not in wildcard_arguments
else arg_value
for arg_name, arg_value in arguments.items()
}
@ -313,12 +170,12 @@ class KeyedCache:
cache.pop(key)
def keyed_cache(exclude: set[str], serialize: set[str] = frozenset()) -> typ.Callable:
def keyed_cache(exclude: set[str], encode: set[str] = frozenset()) -> typ.Callable:
def decorator(func: typ.Callable) -> typ.Callable:
return KeyedCache(
func,
exclude=exclude,
serialize=serialize,
encode=encode,
)
return decorator
@ -364,9 +221,6 @@ 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:
@ -428,7 +282,7 @@ class CachedBLProperty:
self._persist
and (encoded_value := getattr(bl_instance, self._bl_prop_name)) != ''
):
value = decode_any(self._type, encoded_value)
value = serialize.decode(self._type, encoded_value)
cache_nopersist[self._bl_prop_name] = value
return value
@ -439,7 +293,7 @@ class CachedBLProperty:
cache_nopersist[self._bl_prop_name] = value
if self._persist:
setattr(
bl_instance, self._bl_prop_name, ENCODER.encode(value).decode('utf-8')
bl_instance, self._bl_prop_name, serialize.encode(value).decode('utf-8')
)
return value
@ -611,7 +465,7 @@ class BLField:
raise TypeError(msg)
# Define Blender Property (w/Update Sync)
encoded_default_value = ENCODER.encode(self._default_value).decode('utf-8')
encoded_default_value = serialize.encode(self._default_value).decode('utf-8')
log.debug(
'%s set to StringProperty w/default "%s" and no_update="%s"',
bl_attr_name,
@ -632,14 +486,14 @@ class BLField:
## 2. Retrieve bpy.props.StringProperty string.
## 3. Decode using annotated type.
def getter(_self: BLInstance) -> AttrType:
return decode_any(AttrType, getattr(_self, bl_attr_name))
return serialize.decode(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 = ENCODER.encode(value).decode('utf-8')
encoded_value = serialize.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,
) -> ct.schemas.SocketDef:
) -> sockets.base.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,11 +65,6 @@ from .data_flows import (
)
from .data_flow_actions import DataFlowAction
####################
# - Schemas
####################
from . import schemas
####################
# - Export
####################
@ -103,5 +98,4 @@ __all__ = [
'LazyDataValueRange',
'LazyDataValueSpectrum',
'DataFlowAction',
'schemas',
]

View File

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

View File

@ -1,11 +0,0 @@
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

@ -1,22 +0,0 @@
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

@ -1,10 +0,0 @@
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

@ -1,12 +0,0 @@
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,4 +1,5 @@
import typing as typ
from .base import ManagedObj
# from .managed_bl_empty import ManagedBLEmpty
from .managed_bl_image import ManagedBLImage
@ -10,9 +11,8 @@ 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

@ -0,0 +1,65 @@
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,4 +1,3 @@
import functools
import bpy

View File

@ -1,5 +1,4 @@
import io
import time
import typing as typ
import bpy
@ -12,6 +11,7 @@ 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(ct.schemas.ManagedObj):
class ManagedBLImage(base.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLImage
_bl_image_name: str
@ -181,6 +181,9 @@ class ManagedBLImage(ct.schemas.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,6 +6,7 @@ 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__)
@ -14,7 +15,7 @@ log = logger.get(__name__)
####################
# - BLMesh
####################
class ManagedBLMesh(ct.schemas.ManagedObj):
class ManagedBLMesh(base.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLMesh
_bl_object_name: str | None = None

View File

@ -8,6 +8,7 @@ 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__)
@ -160,7 +161,7 @@ def write_modifier(
####################
# - ManagedObj
####################
class ManagedBLModifier(ct.schemas.ManagedObj):
class ManagedBLModifier(base.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLModifier
_modifier_name: str | None = None
@ -185,6 +186,9 @@ class ManagedBLModifier(ct.schemas.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,7 +5,6 @@ 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 managed_objs, sockets
from ... import 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': # noqa: RET505
elif self.active_socket_set == 'Flux Data':
flux_data = self._compute_input('Flux Data')
return getattr(flux_data, 'flux')
return flux_data.flux
msg = f'Tried to get data from unknown output socket in "{self.bl_label}"'
raise RuntimeError(msg)

View File

@ -24,23 +24,12 @@ 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_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),
# ),
managed_obj_types: typ.ClassVar = {
'plot': managed_objs.ManagedBLImage,
}
#####################
@ -78,7 +67,7 @@ class VizNode(base.MaxwellSimNode):
)
def on_show_plot(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
managed_objs: dict,
input_sockets: dict,
props: dict,
unit_systems: dict,

View File

@ -12,11 +12,13 @@ import bpy
import sympy as sp
import typing_extensions as typx
from ....utils import logger
from .. import bl_cache
from blender_maxwell.utils import logger
from .. import bl_cache, sockets
from .. import contracts as ct
from .. import managed_objs as _managed_objs
from . import events
from . import presets as _presets
log = logger.get(__name__)
@ -42,21 +44,27 @@ class MaxwellSimNode(bpy.types.Node):
## TODO: bl_description from first line of __doc__?
# Sockets
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]]] = (
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]]] = (
MappingProxyType({})
)
output_socket_sets: typ.ClassVar[dict[str, dict[str, ct.schemas.SocketDef]]] = (
output_socket_sets: typ.ClassVar[dict[str, dict[str, sockets.base.SocketDef]]] = (
MappingProxyType({})
)
# Presets
presets: typ.ClassVar = MappingProxyType({})
presets: typ.ClassVar[dict[str, dict[str, _presets.PresetDef]]] = MappingProxyType(
{}
)
# Managed Objects
managed_obj_defs: typ.ClassVar[
dict[ct.ManagedObjName, ct.schemas.ManagedObjDef]
managed_obj_types: typ.ClassVar[
dict[ct.ManagedObjName, type[_managed_objs.ManagedObj]]
] = MappingProxyType({})
####################
@ -222,7 +230,7 @@ class MaxwellSimNode(bpy.types.Node):
####################
@events.on_value_changed(
prop_name='sim_node_name',
props={'sim_node_name', 'managed_objs', 'managed_obj_defs'},
props={'sim_node_name', 'managed_objs', 'managed_obj_types'},
)
def _on_sim_node_name_changed(self, props: dict):
log.info(
@ -233,9 +241,8 @@ class MaxwellSimNode(bpy.types.Node):
)
# Set Name of Managed Objects
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']
for mobj in props['managed_objs'].values():
mobj.name = props['sim_node_name']
@events.on_value_changed(prop_name='active_socket_set')
def _on_socket_set_changed(self):
@ -282,8 +289,6 @@ 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()
@ -303,8 +308,8 @@ class MaxwellSimNode(bpy.types.Node):
####################
# - Loose Sockets w/Events
####################
loose_input_sockets: dict[str, ct.schemas.SocketDef] = bl_cache.BLField({})
loose_output_sockets: dict[str, ct.schemas.SocketDef] = bl_cache.BLField({})
loose_input_sockets: dict[str, sockets.base.SocketDef] = bl_cache.BLField({})
loose_output_sockets: dict[str, sockets.base.SocketDef] = bl_cache.BLField({})
@events.on_value_changed(prop_name={'loose_input_sockets', 'loose_output_sockets'})
def _on_loose_sockets_changed(self):
@ -334,7 +339,7 @@ class MaxwellSimNode(bpy.types.Node):
def _active_socket_set_socket_defs(
self,
direc: typx.Literal['input', 'output'],
) -> dict[ct.SocketName, ct.schemas.SocketDef]:
) -> dict[ct.SocketName, sockets.base.SocketDef]:
"""Retrieve all socket definitions for sockets that should be defined, according to the `self.active_socket_set`.
Notes:
@ -344,7 +349,7 @@ class MaxwellSimNode(bpy.types.Node):
direc: The direction to load Blender sockets from.
Returns:
Mapping from socket names to corresponding `ct.schemas.SocketDef`s.
Mapping from socket names to corresponding `sockets.base.SocketDef`s.
If `self.active_socket_set` is None, the empty dict is returned.
"""
@ -360,14 +365,14 @@ class MaxwellSimNode(bpy.types.Node):
def active_socket_defs(
self, direc: typx.Literal['input', 'output']
) -> dict[ct.SocketName, ct.schemas.SocketDef]:
) -> dict[ct.SocketName, sockets.base.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 `ct.schemas.SocketDef`s.
Mapping from socket names to corresponding `sockets.base.SocketDef`s.
"""
static_sockets = self.input_sockets if direc == 'input' else self.output_sockets
loose_sockets = (
@ -460,33 +465,17 @@ class MaxwellSimNode(bpy.types.Node):
####################
# - Managed Objects
####################
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
@bl_cache.cached_bl_property(persist=True)
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_defs:
if not (
managed_objs := (
self.managed_bl_meshes
| self.managed_bl_images
| self.managed_bl_modifiers
)
):
if self.managed_obj_types:
return {
mobj_name: mobj_def.mk(mobj_def.name_prefix + self.sim_node_name)
for mobj_name, mobj_def in self.managed_obj_defs.items()
mobj_name: mobj_type(self.sim_node_name)
for mobj_name, mobj_type in self.managed_obj_types.items()
}
return managed_objs
return {}
@ -564,7 +553,7 @@ class MaxwellSimNode(bpy.types.Node):
####################
@bl_cache.keyed_cache(
exclude={'self', 'optional'},
serialize={'unit_system'},
encode={'unit_system'},
)
def _compute_input(
self,
@ -719,13 +708,6 @@ 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,11 +47,8 @@ class Tidy3DFileImporterNode(base.MaxwellSimNode):
input_sockets: typ.ClassVar = {
'File Path': sockets.FilePathSocketDef(),
}
managed_obj_defs: typ.ClassVar = {
'plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix='',
),
managed_obj_types: typ.ClassVar = {
'plot': managed_objs.ManagedBLImage,
}
####################

View File

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

View File

@ -1,7 +1,6 @@
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
@ -50,13 +49,9 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
'Time Domain': {'Time Monitor': sockets.MaxwellMonitorSocketDef()},
}
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),
),
managed_obj_types: typ.ClassVar = {
'mesh': managed_objs.ManagedBLMesh,
'modifier': managed_objs.ManagedBLModifier,
}
####################
@ -118,7 +113,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
def on_inputs_changed(
self,
props: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
managed_objs: dict,
input_sockets: dict,
unit_systems: dict,
):

View File

@ -1,8 +1,6 @@
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
@ -50,13 +48,9 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
'Time Domain': {'Time Monitor': sockets.MaxwellMonitorSocketDef()},
}
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),
),
managed_obj_types: typ.ClassVar = {
'mesh': managed_objs.ManagedBLMesh,
'modifier': managed_objs.ManagedBLModifier,
}
####################
@ -120,7 +114,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
def on_inputs_changed(
self,
props: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
managed_objs: dict,
input_sockets: dict,
unit_systems: dict,
):

View File

@ -6,7 +6,6 @@ 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

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

View File

@ -28,14 +28,9 @@ class SimDomainNode(base.MaxwellSimNode):
'Domain': sockets.MaxwellSimDomainSocketDef(),
}
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),
),
managed_obj_types: typ.ClassVar = {
'mesh': managed_objs.ManagedBLMesh,
'modifier': managed_objs.ManagedBLModifier,
}
####################
@ -75,7 +70,7 @@ class SimDomainNode(base.MaxwellSimNode):
def on_input_changed(
self,
props: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
managed_objs: dict,
input_sockets: dict,
unit_systems: dict,
):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
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
@ -97,7 +95,7 @@ class PhysicalFreqBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class PhysicalFreqSocketDef(pyd.BaseModel):
class PhysicalFreqSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalFreq
is_array: bool = False

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,10 @@ 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[ct.SocketType, type[ct.schemas.SocketDef]]:
) -> dict:
return {
socket_type: getattr(
sockets_module,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,8 @@ 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

@ -0,0 +1,182 @@
"""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,7 +9,6 @@ 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))