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 - We found the translation callback! https://projects.blender.org/blender/blender/commit/8564e03cdf59fb2a71d545e81871411b82f561d9
- This can update the node center!! - This can update the node center!!
- [ ] Optimize the `DataChanged` invalidator. - [x] Optimize the `DataChanged` invalidator.
- [ ] Optimize unit stripping. - [ ] Optimize unit stripping.
## Keyed Cache ## 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. - [ ] Similarly, a field method that gets the 'blfield__' prop data as a dictionary.
### Parallel Features ### Parallel Features
- [ ] Move serialization work to a `utils`. - [x] Move serialization work to a `utils`.
- [ ] Also make ENCODER a function that can shortcut the easy cases. - [x] 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. - [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.
- [ ] Define a superclass for `SocketDef` and make everyone inherit from it - [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 - [ ] 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 ### Sweeping Features
- [ ] Replace all raw Blender properties with `BLField`. - [ ] Replace all raw Blender properties with `BLField`.

View File

@ -5,14 +5,8 @@ import inspect
import typing as typ import typing as typ
import bpy 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, serialize
from ...utils import logger
from . import contracts as ct
from . import managed_objs, sockets
log = logger.get(__name__) log = logger.get(__name__)
@ -39,149 +33,12 @@ class BLInstance(typ.Protocol):
) -> None: ... ) -> None: ...
EncodableValue: typ.TypeAlias = typ.Any ## msgspec-compatible PropGetMethod: typ.TypeAlias = typ.Callable[
PropGetMethod: typ.TypeAlias = typ.Callable[[BLInstance], EncodableValue] [BLInstance], serialize.NaivelyEncodableType
PropSetMethod: typ.TypeAlias = typ.Callable[[BLInstance, EncodableValue], None] ]
PropSetMethod: typ.TypeAlias = typ.Callable[
#################### [BLInstance, serialize.NaivelyEncodableType], 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
#################### ####################
@ -210,7 +67,7 @@ class KeyedCache:
self, self,
func: typ.Callable, func: typ.Callable,
exclude: set[str], exclude: set[str],
serialize: set[str], encode: set[str],
): ):
# Function Information # Function Information
self.func: typ.Callable = func self.func: typ.Callable = func
@ -219,7 +76,7 @@ class KeyedCache:
# Arg -> Key Information # Arg -> Key Information
self.exclude: set[str] = exclude self.exclude: set[str] = exclude
self.include: set[str] = set(self.func_sig.parameters.keys()) - exclude self.include: set[str] = set(self.func_sig.parameters.keys()) - exclude
self.serialize: set[str] = serialize self.encode: set[str] = encode
# Cache Information # Cache Information
self.key_schema: tuple[str, ...] = tuple( self.key_schema: tuple[str, ...] = tuple(
@ -247,8 +104,8 @@ class KeyedCache:
[ [
( (
arg_value arg_value
if arg_name not in self.serialize if arg_name not in self.encode
else ENCODER.encode(arg_value) else serialize.encode(arg_value)
) )
for arg_name, arg_value in arguments.items() for arg_name, arg_value in arguments.items()
if arg_name in self.include if arg_name in self.include
@ -298,8 +155,8 @@ class KeyedCache:
# Compute Keys to Invalidate # Compute Keys to Invalidate
arguments_hashable = { arguments_hashable = {
arg_name: ENCODER.encode(arg_value) arg_name: serialize.encode(arg_value)
if arg_name in self.serialize and arg_name not in wildcard_arguments if arg_name in self.encode and arg_name not in wildcard_arguments
else arg_value else arg_value
for arg_name, arg_value in arguments.items() for arg_name, arg_value in arguments.items()
} }
@ -313,12 +170,12 @@ class KeyedCache:
cache.pop(key) 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: def decorator(func: typ.Callable) -> typ.Callable:
return KeyedCache( return KeyedCache(
func, func,
exclude=exclude, exclude=exclude,
serialize=serialize, encode=encode,
) )
return decorator return decorator
@ -364,9 +221,6 @@ class CachedBLProperty:
inspect.signature(getter_method).return_annotation if persist else None 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 # Check Non-Empty Type Annotation
## For now, just presume that all types can be encoded/decoded. ## For now, just presume that all types can be encoded/decoded.
if self._type is not None and self._type is inspect.Signature.empty: if self._type is not None and self._type is inspect.Signature.empty:
@ -428,7 +282,7 @@ class CachedBLProperty:
self._persist self._persist
and (encoded_value := getattr(bl_instance, self._bl_prop_name)) != '' 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 cache_nopersist[self._bl_prop_name] = value
return value return value
@ -439,7 +293,7 @@ class CachedBLProperty:
cache_nopersist[self._bl_prop_name] = value cache_nopersist[self._bl_prop_name] = value
if self._persist: if self._persist:
setattr( 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 return value
@ -611,7 +465,7 @@ class BLField:
raise TypeError(msg) raise TypeError(msg)
# Define Blender Property (w/Update Sync) # 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( log.debug(
'%s set to StringProperty w/default "%s" and no_update="%s"', '%s set to StringProperty w/default "%s" and no_update="%s"',
bl_attr_name, bl_attr_name,
@ -632,14 +486,14 @@ class BLField:
## 2. Retrieve bpy.props.StringProperty string. ## 2. Retrieve bpy.props.StringProperty string.
## 3. Decode using annotated type. ## 3. Decode using annotated type.
def getter(_self: BLInstance) -> AttrType: def getter(_self: BLInstance) -> AttrType:
return decode_any(AttrType, getattr(_self, bl_attr_name)) return serialize.decode(AttrType, getattr(_self, bl_attr_name))
## Setter: ## Setter:
## 1. Initialize bpy.props.StringProperty to Default (if undefined). ## 1. Initialize bpy.props.StringProperty to Default (if undefined).
## 3. Encode value (implicitly using the annotated type). ## 3. Encode value (implicitly using the annotated type).
## 2. Set bpy.props.StringProperty string. ## 2. Set bpy.props.StringProperty string.
def setter(_self: BLInstance, value: AttrType) -> None: 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( log.debug(
'Writing BLField attr "%s" w/encoded value: %s', 'Writing BLField attr "%s" w/encoded value: %s',
bl_attr_name, bl_attr_name,

View File

@ -132,7 +132,7 @@ def _socket_def_from_bl_socket(
def socket_def_from_bl_socket( def socket_def_from_bl_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket, 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.""" """Computes an appropriate (no-arg) SocketDef from the given `bl_interface_socket`, by parsing it."""
return _socket_def_from_bl_socket( return _socket_def_from_bl_socket(
bl_interface_socket.description, bl_interface_socket.bl_socket_idname 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 from .data_flow_actions import DataFlowAction
####################
# - Schemas
####################
from . import schemas
#################### ####################
# - Export # - Export
#################### ####################
@ -103,5 +98,4 @@ __all__ = [
'LazyDataValueRange', 'LazyDataValueRange',
'LazyDataValueSpectrum', 'LazyDataValueSpectrum',
'DataFlowAction', 'DataFlowAction',
'schemas',
] ]

View File

@ -1,4 +1,3 @@
import enum
import pydantic as pyd import pydantic as pyd
import typing_extensions as pytypes_ext 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_empty import ManagedBLEmpty
from .managed_bl_image import ManagedBLImage 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_volume import ManagedBLVolume
from .managed_bl_modifier import ManagedBLModifier from .managed_bl_modifier import ManagedBLModifier
ManagedObj: typ.TypeAlias = ManagedBLImage | ManagedBLMesh | ManagedBLModifier
__all__ = [ __all__ = [
'ManagedObj',
#'ManagedBLEmpty', #'ManagedBLEmpty',
'ManagedBLImage', 'ManagedBLImage',
#'ManagedBLCollection', #'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 import bpy

View File

@ -1,5 +1,4 @@
import io import io
import time
import typing as typ import typing as typ
import bpy import bpy
@ -12,6 +11,7 @@ import typing_extensions as typx
from ....utils import logger from ....utils import logger
from .. import contracts as ct from .. import contracts as ct
from . import base
log = logger.get(__name__) 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) return rgba_image_from_xyzf__grayscale(xyz_freq)
class ManagedBLImage(ct.schemas.ManagedObj): class ManagedBLImage(base.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLImage managed_obj_type = ct.ManagedObjType.ManagedBLImage
_bl_image_name: str _bl_image_name: str
@ -181,6 +181,9 @@ class ManagedBLImage(ct.schemas.ManagedObj):
if bl_image := bpy.data.images.get(self.name): if bl_image := bpy.data.images.get(self.name):
self.preview_space.image = bl_image self.preview_space.image = bl_image
def hide_preview(self) -> None:
self.preview_space.image = None
#################### ####################
# - Image Geometry # - Image Geometry
#################### ####################
@ -269,12 +272,12 @@ class ManagedBLImage(ct.schemas.ManagedObj):
) )
# log.debug('Computed MPL Geometry (%f)', time.perf_counter() - time_start) # log.debug('Computed MPL Geometry (%f)', time.perf_counter() - time_start)
#log.debug( # log.debug(
# 'Creating MPL Axes (aspect=%f, width=%f, height=%f)', # 'Creating MPL Axes (aspect=%f, width=%f, height=%f)',
# aspect_ratio, # aspect_ratio,
# _width_inches, # _width_inches,
# _height_inches, # _height_inches,
#) # )
# Create MPL Figure, Axes, and Compute Figure Geometry # Create MPL Figure, Axes, and Compute Figure Geometry
fig, ax = plt.subplots( fig, ax = plt.subplots(
figsize=[_width_inches, _height_inches], figsize=[_width_inches, _height_inches],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,10 +2,10 @@ import typing as typ
import pydantic as pyd import pydantic as pyd
from ..bl import PresetName, SocketName from .. import contracts as ct
class PresetDef(pyd.BaseModel): class PresetDef(pyd.BaseModel):
label: PresetName label: ct.PresetName
description: str 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(), 'Domain': sockets.MaxwellSimDomainSocketDef(),
} }
managed_obj_defs: typ.ClassVar = { managed_obj_types: typ.ClassVar = {
'mesh': ct.schemas.ManagedObjDef( 'mesh': managed_objs.ManagedBLMesh,
mk=lambda name: managed_objs.ManagedBLMesh(name), 'modifier': managed_objs.ManagedBLModifier,
name_prefix='',
),
'modifier': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLModifier(name),
),
} }
#################### ####################
@ -75,7 +70,7 @@ class SimDomainNode(base.MaxwellSimNode):
def on_input_changed( def on_input_changed(
self, self,
props: dict, props: dict,
managed_objs: dict[str, ct.schemas.ManagedObj], managed_objs: dict,
input_sockets: dict, input_sockets: dict,
unit_systems: dict, unit_systems: dict,
): ):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,46 @@
import abc
import functools import functools
import typing as typ import typing as typ
import bpy import bpy
import pydantic as pyd
import sympy as sp import sympy as sp
import sympy.physics.units as spu
import typing_extensions as typx import typing_extensions as typx
from ....utils import logger from ....utils import logger, serialize
from .. import contracts as ct from .. import contracts as ct
log = logger.get(__name__) 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): class MaxwellSimSocket(bpy.types.NodeSocket):
# Fundamentals # Fundamentals
socket_type: ct.SocketType socket_type: ct.SocketType

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,10 @@ import types
from .. import contracts as ct 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( def scan_for_socket_defs(
sockets_module: types.ModuleType, sockets_module: types.ModuleType,
) -> dict[ct.SocketType, type[ct.schemas.SocketDef]]: ) -> dict:
return { return {
socket_type: getattr( socket_type: getattr(
sockets_module, sockets_module,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import bpy import bpy
import pydantic as pyd
import sympy as sp import sympy as sp
from .....utils.pydantic_sympy import ConstrSympyExpr from .....utils.pydantic_sympy import ConstrSympyExpr
@ -55,7 +54,7 @@ class Real3DVectorBLSocket(base.MaxwellSimSocket):
#################### ####################
# - Socket Configuration # - Socket Configuration
#################### ####################
class Real3DVectorSocketDef(pyd.BaseModel): class Real3DVectorSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.Real3DVector socket_type: ct.SocketType = ct.SocketType.Real3DVector
default_value: Real3DVector = sp.Matrix([0.0, 0.0, 0.0]) 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 as sp
import sympy.physics.units as spu import sympy.physics.units as spu
SympyType = sp.Basic | sp.Expr | sp.MatrixBase | spu.Quantity
#################### ####################
# - Useful Methods # - 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) PATH_SCRIPT = str(Path(__file__).resolve().parent)
sys.path.insert(0, str(PATH_SCRIPT)) sys.path.insert(0, str(PATH_SCRIPT))
import info # noqa: E402 import info # noqa: E402
import pack # noqa: E402
sys.path.remove(str(PATH_SCRIPT)) sys.path.remove(str(PATH_SCRIPT))