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!
main
Sofus Albert Høgsbro Rose 2024-04-15 19:15:20 +02:00
parent 269af4ba32
commit 568fc449e8
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
27 changed files with 112 additions and 189 deletions

View File

@ -504,7 +504,7 @@ 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.
@ -551,7 +551,7 @@ We need support for arbitrary objects, but still backed by the persistance seman
- [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] 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 - [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

@ -33,9 +33,11 @@ class BLInstance(typ.Protocol):
) -> None: ... ) -> None: ...
PropGetMethod: typ.TypeAlias = typ.Callable[[BLInstance], serialize.EncodableValue] PropGetMethod: typ.TypeAlias = typ.Callable[
[BLInstance], serialize.NaivelyEncodableType
]
PropSetMethod: typ.TypeAlias = typ.Callable[ PropSetMethod: typ.TypeAlias = typ.Callable[
[BLInstance, serialize.EncodableValue], None [BLInstance, serialize.NaivelyEncodableType], None
] ]

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,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,26 +0,0 @@
import abc
import typing as typ
import bpy
import pydantic as pyd
from .....utils import serialize
from ..socket_types import SocketType
class SocketDef(pyd.BaseModel, abc.ABC):
socket_type: 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 SocketDef.__subclasses__[obj[1]](**obj[2])

View File

@ -1,9 +1,12 @@
import abc import abc
import typing as typ import typing as typ
from ....utils import serialize from blender_maxwell.utils import logger, serialize
from .. import contracts as ct from .. import contracts as ct
log = logger.get(__name__)
class ManagedObj(abc.ABC): class ManagedObj(abc.ABC):
managed_obj_type: ct.ManagedObjType managed_obj_type: ct.ManagedObjType
@ -50,9 +53,13 @@ class ManagedObj(abc.ABC):
return [ return [
serialize.TypeID.ManagedObj, serialize.TypeID.ManagedObj,
self.__class__.__name__, self.__class__.__name__,
(self.managed_obj_type, self.name), self.name,
] ]
@staticmethod @staticmethod
def parse_as_msgspec(obj: serialize.NaiveRepresentation) -> typ.Self: def parse_as_msgspec(obj: serialize.NaiveRepresentation) -> typ.Self:
return ManagedObj.__subclasses__[obj[1]](**obj[2]) return next(
subclass(obj[2])
for subclass in ManagedObj.__subclasses__()
if subclass.__name__ == obj[1]
)

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,17 +44,23 @@ 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_types: typ.ClassVar[ managed_obj_types: typ.ClassVar[
@ -300,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):
@ -331,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:
@ -341,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.
""" """
@ -357,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 = (
@ -700,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

@ -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

@ -49,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),
),
} }
#################### ####################
@ -117,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

@ -48,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),
),
} }
#################### ####################
@ -96,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 '-',
) )
@ -118,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

@ -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

@ -26,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

@ -7,10 +7,8 @@ import pydantic as pyd
import sympy as sp import sympy as sp
import typing_extensions as typx import typing_extensions as typx
from .....utils import serialize from ....utils import logger, serialize
from ....utils import logger
from .. import contracts as ct from .. import contracts as ct
from ..socket_types import SocketType
log = logger.get(__name__) log = logger.get(__name__)
@ -19,7 +17,7 @@ log = logger.get(__name__)
# - SocketDef # - SocketDef
#################### ####################
class SocketDef(pyd.BaseModel, abc.ABC): class SocketDef(pyd.BaseModel, abc.ABC):
socket_type: SocketType socket_type: ct.SocketType
@abc.abstractmethod @abc.abstractmethod
def init(self, bl_socket: bpy.types.NodeSocket) -> None: def init(self, bl_socket: bpy.types.NodeSocket) -> None:
@ -33,7 +31,11 @@ class SocketDef(pyd.BaseModel, abc.ABC):
@staticmethod @staticmethod
def parse_as_msgspec(obj: serialize.NaiveRepresentation) -> typ.Self: def parse_as_msgspec(obj: serialize.NaiveRepresentation) -> typ.Self:
return SocketDef.__subclasses__[obj[1]](**obj[2]) return next(
subclass(**obj[2])
for subclass in SocketDef.__subclasses__()
if subclass.__name__ == obj[1]
)
#################### ####################

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

@ -5,7 +5,7 @@ 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 | sp.Quantity SympyType = sp.Basic | sp.Expr | sp.MatrixBase | spu.Quantity
#################### ####################

View File

@ -1,6 +1,7 @@
"""Attributes: """Robust serialization tool for use in the addon.
NaiveEncodableType:
See <https://jcristharif.com/msgspec/supported-types.html> for details. Attributes:
NaiveEncodableType: See <https://jcristharif.com/msgspec/supported-types.html> for details.
""" """
import dataclasses import dataclasses
@ -61,19 +62,20 @@ NaivelyEncodableType: typ.TypeAlias = (
| enum.Flag | enum.Flag
| enum.IntFlag | enum.IntFlag
| dataclasses.dataclass | dataclasses.dataclass
| typ.Optional | typ.Optional[typ.Any] # noqa: UP007
| typ.Union | typ.Union[typ.Any, ...] # noqa: UP007
| typ.NewType | typ.NewType
| typ.TypeAlias | typ.TypeAlias
| typ.TypeAliasType ## SUPPORT:
| typ.Generic # | typ.Generic[typ.Any]
| typ.TypeVar # | typ.TypeVar
| typ.Final # | typ.Final
| msgspec.Raw | msgspec.Raw
## NO SUPPORT: ## NO SUPPORT:
# | msgspec.UNSET # | msgspec.UNSET
) )
_NaivelyEncodableTypeSet = frozenset(typ.get_args(NaivelyEncodableType)) _NaivelyEncodableTypeSet = frozenset(typ.get_args(NaivelyEncodableType))
## TODO: Use for runtime type check? Beartype?
class TypeID(enum.StrEnum): class TypeID(enum.StrEnum):
@ -87,7 +89,7 @@ NaiveRepresentation: typ.TypeAlias = list[TypeID, str | None, typ.Any]
def is_representation(obj: NaivelyEncodableType) -> bool: def is_representation(obj: NaivelyEncodableType) -> bool:
return isinstance(obj, list) and obj[0] in set(TypeID) and len(obj) == 3 # noqa: PLR2004 return isinstance(obj, list) and len(obj) == 3 and obj[0] in set(TypeID) # noqa: PLR2004
#################### ####################
@ -141,7 +143,9 @@ def _dec_hook(_type: type, obj: NaivelyEncodableType) -> typ.Any:
obj_value = obj[2] obj_value = obj[2]
return sp.sympify(obj_value).subs(spux.ALL_UNIT_SYMBOLS) return sp.sympify(obj_value).subs(spux.ALL_UNIT_SYMBOLS)
if hasattr(obj, 'parse_as_msgspec'): if hasattr(_type, 'parse_as_msgspec') and (
is_representation(obj) and obj[0] in [TypeID.SocketDef, TypeID.ManagedObj]
):
return _type.parse_as_msgspec(obj) return _type.parse_as_msgspec(obj)
msg = f'Can\'t decode "{obj}" to type {type(obj)}' msg = f'Can\'t decode "{obj}" to type {type(obj)}'