feat: Robust DataFlowKind w/lazy structures.
This is essential for: - Representing ranges as bounds - Arbitrary symbolic/numeric representation of spectral distributions - Parametric representation and JIT of critical-path procedures. Unfortunately this broke a lot of nodes in small ways. Next step is to finish the low-hanging fruit nodes + fix the ones we have.main
parent
54dc46290a
commit
a75f697acd
34
TODO.md
34
TODO.md
|
@ -1,20 +1,16 @@
|
|||
# Acute Tasks
|
||||
- [x] Implement Material Import for Maxim Data
|
||||
- Move preview GN trees to the asset library.
|
||||
- [x] Implement Robust DataFlowKind for list-like / spectral-like composite types
|
||||
- [ ] Finish the "Low-Hanging Fruit" Nodes
|
||||
- [ ] Move preview GN trees to the asset library.
|
||||
|
||||
|
||||
|
||||
# Nodes
|
||||
**LEGEND**:
|
||||
- [-] Exists but doesn't quite work good enough.
|
||||
- [x] Done to working degree (the standard is "good enough for the demo").
|
||||
- See check marks underneath
|
||||
- [?] Unsure whether we should do this.
|
||||
|
||||
## Inputs
|
||||
- [x] Wave Constant
|
||||
- [x] Implement export of frequency / wavelength array/range.
|
||||
- [-] Unit System
|
||||
- [x] Unit System
|
||||
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
|
||||
|
||||
- [ ] Constants / Scientific Constant
|
||||
|
@ -26,7 +22,7 @@
|
|||
- [ ] Pol: Poincare sphere viz as 3D GN.
|
||||
- [x] Constants / Blender Constant
|
||||
|
||||
- [-] Web / Tidy3D Web Importer
|
||||
- [ ] Web / Tidy3D Web Importer
|
||||
- [ ] Change to output only a `FilePath`, which can be plugged into a Tidy3D File Importer.
|
||||
- [ ] Implement caching, such that the file will only download if the file doesn't already exist.
|
||||
- [ ] Have a visual indicator for the current download status, with a manual re-download button.
|
||||
|
@ -79,7 +75,7 @@
|
|||
|
||||
- [x] Point Dipole Source
|
||||
- [ ] Use a viz mesh, not empty (empty doesn't play well with alpha hashing).
|
||||
- [-] Plane Wave Source
|
||||
- [ ] Plane Wave Source
|
||||
- [x] Implement an oriented vector input with 3D preview.
|
||||
- [ ] **IMPORTANT**: Fix the math so that an actually valid construction emerges!!
|
||||
- [ ] Uniform Current Source
|
||||
|
@ -120,7 +116,6 @@
|
|||
- [x] Rewrite to use unit systems properly.
|
||||
- [ ] Propertly map / implement Enum input sockets to the GN group.
|
||||
- [ ] Implement a panel system, either based on native GN panels, or description parsing, or something like that.
|
||||
- [?] When GeoNodes themselves declare panels, implement a grid-like tab system to select which sockets should be exposed in the node at a given point in time.
|
||||
|
||||
- [ ] Primitive Structures / Plane Structure
|
||||
- [x] Primitive Structures / Box Structure
|
||||
|
@ -197,7 +192,7 @@
|
|||
- [x] Structures / Arrays / Box
|
||||
- [x] Structures / Arrays / Sphere
|
||||
- [ ] Structures / Arrays / Cylinder
|
||||
- [-] Structures / Arrays / Ring
|
||||
- [x] Structures / Arrays / Ring
|
||||
- [ ] Structures / Arrays / Capsule
|
||||
- [ ] Structures / Arrays / Cone
|
||||
|
||||
|
@ -270,7 +265,7 @@
|
|||
|
||||
- [ ] FDTD Sim
|
||||
- [ ] Sim Domain
|
||||
- [?] Toggleable option to push-sync the simulation time duration to the scene end time (how to handle FPS vs time-step? Should we adjust the FPS such that there is one time step per frame, while keeping the definition of "second" aligned to the Blender unit system?)
|
||||
- [ ] Toggleable option to push-sync the simulation time duration to the scene end time (how to handle FPS vs time-step? Should we adjust the FPS such that there is one time step per frame, while keeping the definition of "second" aligned to the Blender unit system?)
|
||||
- [ ] Sim Grid
|
||||
- [ ] Sim Grid Axis
|
||||
|
||||
|
@ -376,7 +371,7 @@
|
|||
## Registration and Contracts
|
||||
- [ ] Refactor the node category code; it's ugly.
|
||||
- It's maybe not that easy. And it seems to work with surprising reliability. Leave it alone for now!
|
||||
- [?] Would be nice with some kind of indicator somewhere to help set good socket descriptions when making geonodes.
|
||||
- [ ] (?) Would be nice with some kind of indicator somewhere to help set good socket descriptions when making geonodes.
|
||||
|
||||
## Managed Objects
|
||||
- [ ] Implement ManagedEmpty
|
||||
|
@ -401,7 +396,8 @@
|
|||
- [ ] When presets are used, if a preset is selected and the user alters a preset setting, then dynamically switch the preset indicator back to "Custom" to indicate that there is no active preset
|
||||
|
||||
## Events
|
||||
- [-] Mechanism for selecting a blender object managed by a particular node.
|
||||
- [x] Mechanism for selecting a blender object managed by a particular node.
|
||||
- [ ] Standard way of triggering the selection
|
||||
- [ ] Mechanism for ex. specially coloring a node that is currently participating in the preview.
|
||||
- [ ] Custom callbacks when deleting a node (in `free()`), to ex. delete all previews with the viewer node.
|
||||
|
||||
|
@ -414,7 +410,7 @@
|
|||
|
||||
## Many Nodes
|
||||
- [ ] Implement "Steady-State" / "Time Domain" on all relevant Monitor nodes
|
||||
- [?] Dynamic `bl_label` where appropriate (ex. "Library Medium" becoming "Au Medium")
|
||||
- [ ] (?) Dynamic `bl_label` where appropriate (ex. "Library Medium" becoming "Au Medium")
|
||||
- [ ] Implement LazyValue, including LazyParamValue on a new class of constant-like input nodes that really just emit ex. sympy variables.
|
||||
- [ ] Medium Features
|
||||
- [ ] Accept spatial field. Else, spatial uniformity.
|
||||
|
@ -438,11 +434,11 @@
|
|||
## Version Churn
|
||||
- [ ] Migrate to StrEnum sockets (py3.11).
|
||||
- [ ] Implement drag-and-drop node-from-file via bl4.1 file handler API.
|
||||
- [-] Start thinking about ways around `__annotations__` hacking.
|
||||
- [-] Prepare for for multi-input sockets (bl4.2)
|
||||
- [ ] Start thinking about ways around `__annotations__` hacking.
|
||||
- [ ] Prepare for for multi-input sockets (bl4.2)
|
||||
- PR has been merged: <https://projects.blender.org/blender/blender/commit/14106150797a6ce35e006ffde18e78ea7ae67598> (for now, just use the "Combine" node and have seperate socket types for both).
|
||||
- The `Combine` node has its own benefits, including previewability of "only structures". Multi-input would mainly be a kind of shorthand in specific cases (like input to the `Combine` node?)
|
||||
- [-] Prepare for volume geonodes (bl4.2; July 16, 2024)
|
||||
- [ ] Prepare for volume geonodes (bl4.2; July 16, 2024)
|
||||
- Will allow for actual volume processing in GeoNodes.
|
||||
- We might still want/need the jax based stuff after; volume geonodes aren't finalized.
|
||||
|
||||
|
|
|
@ -53,7 +53,17 @@ from .managed_obj_type import ManagedObjType
|
|||
####################
|
||||
# - Data Flows
|
||||
####################
|
||||
from .data_flows import DataFlowKind
|
||||
from .data_flows import (
|
||||
DataFlowKind,
|
||||
DataCapabilities,
|
||||
DataValue,
|
||||
DataValueArray,
|
||||
DataValueSpectrum,
|
||||
LazyDataValue,
|
||||
LazyDataValueRange,
|
||||
LazyDataValueSpectrum,
|
||||
)
|
||||
from .data_flow_actions import DataFlowAction
|
||||
|
||||
####################
|
||||
# - Schemas
|
||||
|
@ -85,5 +95,13 @@ __all__ = [
|
|||
'NODE_CAT_LABELS',
|
||||
'ManagedObjType',
|
||||
'DataFlowKind',
|
||||
'DataCapabilities',
|
||||
'DataValue',
|
||||
'DataValueArray',
|
||||
'DataValueSpectrum',
|
||||
'LazyDataValue',
|
||||
'LazyDataValueRange',
|
||||
'LazyDataValueSpectrum',
|
||||
'DataFlowAction',
|
||||
'schemas',
|
||||
]
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import enum
|
||||
|
||||
|
||||
class DataFlowAction(enum.StrEnum):
|
||||
# Locking
|
||||
EnableLock = 'enable_lock'
|
||||
DisableLock = 'disable_lock'
|
||||
|
||||
# Value
|
||||
DataChanged = 'value_changed'
|
||||
|
||||
# Previewing
|
||||
ShowPreview = 'show_preview'
|
||||
ShowPlot = 'show_plot'
|
|
@ -1,20 +1,34 @@
|
|||
import dataclasses
|
||||
import enum
|
||||
import functools
|
||||
import typing as typ
|
||||
from types import MappingProxyType
|
||||
|
||||
from ....utils.blender_type_enum import BlenderTypeEnum
|
||||
# import colour ## TODO
|
||||
import numpy as np
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
import typing_extensions as typx
|
||||
|
||||
from ....utils import extra_sympy_units as spux
|
||||
from ....utils import sci_constants as constants
|
||||
from .socket_types import SocketType
|
||||
|
||||
|
||||
class DataFlowKind(BlenderTypeEnum):
|
||||
class DataFlowKind(enum.StrEnum):
|
||||
"""Defines a shape/kind of data that may flow through a node tree.
|
||||
|
||||
Since a node socket may define one of each, we can support several related kinds of data flow through the same node-graph infrastructure.
|
||||
|
||||
Attributes:
|
||||
Value: A value usable without new data.
|
||||
Value: A value without any unknown symbols.
|
||||
- Basic types aka. float, int, list, string, etc. .
|
||||
- Exotic (immutable-ish) types aka. numpy array, KDTree, etc. .
|
||||
- A usable constructed object, ex. a `tidy3d.Box`.
|
||||
- Expressions (`sp.Expr`) that don't have unknown variables.
|
||||
- Lazy sequences aka. generators, with all data bound.
|
||||
SpectralValue: A value defined along a spectral range.
|
||||
- {`np.array`
|
||||
|
||||
LazyValue: An object which, when given new data, can make many values.
|
||||
- An `sp.Expr`, which might need `simplify`ing, `jax` JIT'ing, unit cancellations, variable substitutions, etc. before use.
|
||||
|
@ -50,8 +64,215 @@ class DataFlowKind(BlenderTypeEnum):
|
|||
Implementation TBD - though, ostensibly, one would have a "parameter" node which both would only provide a LazyValue (aka. a symbolic variable), but would also be able to provide a LazyParamValue, which would be a particular value of some kind (probably via the `value` of some other node socket).
|
||||
"""
|
||||
|
||||
Value = enum.auto()
|
||||
LazyValue = enum.auto()
|
||||
Capabilities = enum.auto()
|
||||
|
||||
LazyParamValue = enum.auto()
|
||||
# Values
|
||||
Value = enum.auto()
|
||||
ValueArray = enum.auto()
|
||||
ValueSpectrum = enum.auto()
|
||||
|
||||
# Lazy
|
||||
LazyValue = enum.auto()
|
||||
LazyValueRange = enum.auto()
|
||||
LazyValueSpectrum = enum.auto()
|
||||
|
||||
|
||||
####################
|
||||
# - Data Structures: Capabilities
|
||||
####################
|
||||
@dataclasses.dataclass(frozen=True, kw_only=True)
|
||||
class DataCapabilities:
|
||||
socket_type: SocketType
|
||||
active_kind: DataFlowKind
|
||||
|
||||
is_universal: bool = False
|
||||
|
||||
def is_compatible_with(self, other: typ.Self) -> bool:
|
||||
return (
|
||||
self.socket_type == other.socket_type
|
||||
and self.active_kind == other.active_kind
|
||||
) or other.is_universal
|
||||
|
||||
|
||||
####################
|
||||
# - Data Structures: Non-Lazy
|
||||
####################
|
||||
DataValue: typ.TypeAlias = typ.Any
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True, kw_only=True)
|
||||
class DataValueArray:
|
||||
"""A simple, flat array of values with an optionally-attached unit.
|
||||
|
||||
Attributes:
|
||||
values: A 1D array-like object of arbitrary numerical type.
|
||||
unit: A `sympy` unit.
|
||||
None if unitless.
|
||||
"""
|
||||
|
||||
values: typ.Sequence[DataValue]
|
||||
unit: spu.Quantity | None
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True, kw_only=True)
|
||||
class DataValueSpectrum:
|
||||
"""A numerical representation of a spectral distribution.
|
||||
|
||||
Attributes:
|
||||
wls: A 1D `numpy` float array of wavelength values.
|
||||
wls_unit: The unit of wavelengths, as length dimension.
|
||||
values: A 1D `numpy` float array of values corresponding to wavelength values.
|
||||
values_unit: The unit of the value, as arbitrary dimension.
|
||||
freqs_unit: The unit of the value, as arbitrary dimension.
|
||||
"""
|
||||
|
||||
# Wavelength
|
||||
wls: np.array
|
||||
wls_unit: spu.Quantity
|
||||
|
||||
# Value
|
||||
values: np.array
|
||||
values_unit: spu.Quantity
|
||||
|
||||
# Frequency
|
||||
freqs_unit: spu.Quantity = spu.hertz
|
||||
|
||||
@functools.cached_property
|
||||
def freqs(self) -> np.array:
|
||||
"""The spectral frequencies, computed from the wavelengths.
|
||||
|
||||
Frequencies are NOT reversed, so as to preserve the by-index mapping to `DataValueSpectrum.values`.
|
||||
|
||||
Returns:
|
||||
Frequencies, as a unitless `numpy` array.
|
||||
Use `DataValueSpectrum.wls_unit` to interpret this return value.
|
||||
"""
|
||||
unitless_speed_of_light = spux.sympy_to_python(
|
||||
spux.scale_to_unit(
|
||||
constants.vac_speed_of_light, (self.wl_unit / self.freq_unit)
|
||||
)
|
||||
)
|
||||
return unitless_speed_of_light / self.wls
|
||||
|
||||
# TODO: Colour Library
|
||||
# def as_colour_sd(self) -> colour.SpectralDistribution:
|
||||
# """Returns the `colour` representation of this spectral distribution, ideal for plotting and colorimetric analysis."""
|
||||
# return colour.SpectralDistribution(data=self.values, domain=self.wls)
|
||||
|
||||
|
||||
####################
|
||||
# - Data Structures: Lazy
|
||||
####################
|
||||
@dataclasses.dataclass(frozen=True, kw_only=True)
|
||||
class LazyDataValue:
|
||||
callback: typ.Callable[[...], [DataValue]]
|
||||
|
||||
def realize(self, *args: list[DataValue]) -> DataValue:
|
||||
return self.callback(*args)
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True, kw_only=True)
|
||||
class LazyDataValueRange:
|
||||
symbols: set[sp.Symbol]
|
||||
|
||||
start: sp.Basic
|
||||
stop: sp.Basic
|
||||
steps: int
|
||||
scaling: typx.Literal['lin', 'geom', 'log'] = 'lin'
|
||||
|
||||
has_unit: bool = False
|
||||
|
||||
def rescale_to_unit(self, unit: spu.Quantity) -> typ.Self:
|
||||
if self.has_unit:
|
||||
return LazyDataValueRange(
|
||||
symbols=self.symbols,
|
||||
has_unit=self.has_unit,
|
||||
start=spu.convert_to(self.start, unit),
|
||||
stop=spu.convert_to(self.stop, unit),
|
||||
steps=self.steps,
|
||||
scaling=self.scaling,
|
||||
)
|
||||
|
||||
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
||||
raise ValueError(msg)
|
||||
|
||||
def rescale_bounds(
|
||||
self,
|
||||
bound_cb: typ.Callable[[sp.Expr], sp.Expr],
|
||||
reverse: bool = False,
|
||||
) -> typ.Self:
|
||||
"""Call a function on both bounds (start and stop), creating a new `LazyDataValueRange`."""
|
||||
return LazyDataValueRange(
|
||||
symbols=self.symbols,
|
||||
has_unit=self.has_unit,
|
||||
start=bound_cb(self.start if not reverse else self.stop),
|
||||
stop=bound_cb(self.stop if not reverse else self.start),
|
||||
steps=self.steps,
|
||||
scaling=self.scaling,
|
||||
)
|
||||
|
||||
def realize(
|
||||
self, symbol_values: dict[sp.Symbol, DataValue] = MappingProxyType({})
|
||||
) -> DataValueArray:
|
||||
# Realize Symbols
|
||||
if not self.has_unit:
|
||||
start = spux.sympy_to_python(self.start.subs(symbol_values))
|
||||
stop = spux.sympy_to_python(self.stop.subs(symbol_values))
|
||||
else:
|
||||
start = spux.sympy_to_python(
|
||||
spux.scale_to_unit(self.start.subs(symbol_values), self.unit)
|
||||
)
|
||||
stop = spux.sympy_to_python(
|
||||
spux.scale_to_unit(self.stop.subs(symbol_values), self.unit)
|
||||
)
|
||||
|
||||
# Return Linspace / Logspace
|
||||
if self.scaling == 'lin':
|
||||
return DataValueArray(
|
||||
values=np.linspace(start, stop, self.steps), unit=self.unit
|
||||
)
|
||||
if self.scaling == 'geom':
|
||||
return DataValueArray(np.geomspace(start, stop, self.steps), self.unit)
|
||||
if self.scaling == 'log':
|
||||
return DataValueArray(np.logspace(start, stop, self.steps), self.unit)
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True, kw_only=True)
|
||||
class LazyDataValueSpectrum:
|
||||
wl_unit: spu.Quantity
|
||||
value_unit: spu.Quantity
|
||||
value_expr: sp.Expr
|
||||
|
||||
symbols: tuple[sp.Symbol, ...] = ()
|
||||
freq_symbol: sp.Symbol = sp.Symbol('lamda') # noqa: RUF009
|
||||
|
||||
def rescale_to_unit(self, unit: spu.Quantity) -> typ.Self:
|
||||
raise NotImplementedError
|
||||
|
||||
@functools.cached_property
|
||||
def as_func(self) -> typ.Callable[[DataValue, ...], DataValue]:
|
||||
"""Generates an optimized function for numerical evaluation of the spectral expression."""
|
||||
return sp.lambdify([self.freq_symbol, *self.symbols], self.value_expr)
|
||||
|
||||
def realize(
|
||||
self, wl_range: DataValueArray, symbol_values: tuple[DataValue, ...]
|
||||
) -> DataValueSpectrum:
|
||||
r"""Realizes the parameterized spectral function as a numerical spectral distribution.
|
||||
|
||||
Parameters:
|
||||
wl_range: The lazy wavelength range to build the concrete spectral distribution with.
|
||||
symbol_values: Numerical values for each symbol, in the same order as defined in `LazyDataValueSpectrum.symbols`.
|
||||
The wavelength symbol ($\lambda$ by default) always goes first.
|
||||
_This is used to call the spectral function using the output of `.as_func()`._
|
||||
|
||||
Returns:
|
||||
The concrete, numerical spectral distribution.
|
||||
"""
|
||||
return DataValueSpectrum(
|
||||
wls=wl_range.values,
|
||||
wls_unit=self.wl_unit,
|
||||
values=self.as_func(*list(symbol_values.values())),
|
||||
values_unit=self.value_unit,
|
||||
)
|
||||
|
|
|
@ -37,9 +37,8 @@ class ManagedBLImage(ct.schemas.ManagedObj):
|
|||
return
|
||||
|
||||
# ...AND Desired Image Name is Taken
|
||||
else:
|
||||
msg = f'Desired name {value} for BL image is taken'
|
||||
raise ValueError(msg)
|
||||
msg = f'Desired name {value} for BL image is taken'
|
||||
raise ValueError(msg)
|
||||
|
||||
# Object DOES Exist
|
||||
bl_image.name = value
|
||||
|
@ -48,11 +47,8 @@ class ManagedBLImage(ct.schemas.ManagedObj):
|
|||
## - `set_name` is allowed to change the name; nodes account for this.
|
||||
|
||||
def free(self):
|
||||
if not (bl_image := bpy.data.images.get(self.name)):
|
||||
msg = "Can't free BL image that doesn't exist"
|
||||
raise ValueError(msg)
|
||||
|
||||
bpy.data.images.remove(bl_image)
|
||||
if bl_image := bpy.data.images.get(self.name):
|
||||
bpy.data.images.remove(bl_image)
|
||||
|
||||
####################
|
||||
# - Managed Object Management
|
||||
|
@ -166,7 +162,12 @@ class ManagedBLImage(ct.schemas.ManagedObj):
|
|||
# Compute Plot Dimensions
|
||||
aspect_ratio = _width_inches / _height_inches
|
||||
|
||||
log.debug('Create MPL Axes (aspect=%d, width=%d, height=%d)', aspect_ratio, _width_inches, _height_inches)
|
||||
log.debug(
|
||||
'Create MPL Axes (aspect=%d, width=%d, height=%d)',
|
||||
aspect_ratio,
|
||||
_width_inches,
|
||||
_height_inches,
|
||||
)
|
||||
# Create MPL Figure, Axes, and Compute Figure Geometry
|
||||
fig, ax = plt.subplots(
|
||||
figsize=[_width_inches, _height_inches],
|
||||
|
|
|
@ -352,11 +352,11 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
self,
|
||||
value: dict[str, ct.schemas.SocketDef],
|
||||
) -> None:
|
||||
log.info(
|
||||
'Setting Loose Input Sockets on "%s" to "%s"',
|
||||
self.bl_label,
|
||||
str(value),
|
||||
)
|
||||
# Prune Loose Sockets
|
||||
self.ser_loose_input_sockets = _DEFAULT_LOOSE_SOCKET_SER
|
||||
self.sync_sockets()
|
||||
|
||||
# Install New Sockets
|
||||
if not value:
|
||||
self.ser_loose_input_sockets = _DEFAULT_LOOSE_SOCKET_SER
|
||||
else:
|
||||
|
@ -364,13 +364,17 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
|
||||
# Synchronize Sockets
|
||||
self.sync_sockets()
|
||||
## TODO: Perhaps re-init() all loose sockets anyway?
|
||||
|
||||
@loose_output_sockets.setter
|
||||
def loose_output_sockets(
|
||||
self,
|
||||
value: dict[str, ct.schemas.SocketDef],
|
||||
) -> None:
|
||||
# Prune Loose Sockets
|
||||
self.ser_loose_output_sockets = _DEFAULT_LOOSE_SOCKET_SER
|
||||
self.sync_sockets()
|
||||
|
||||
# Install New Sockets
|
||||
if not value:
|
||||
self.ser_loose_output_sockets = _DEFAULT_LOOSE_SOCKET_SER
|
||||
else:
|
||||
|
@ -378,7 +382,6 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
|
||||
# Synchronize Sockets
|
||||
self.sync_sockets()
|
||||
## TODO: Perhaps re-init() all loose sockets anyway?
|
||||
|
||||
####################
|
||||
# - Socket Management
|
||||
|
@ -567,17 +570,11 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
msg = f'Property {prop_name} not defined on socket {self}'
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.trigger_action('value_changed', prop_name=prop_name)
|
||||
self.trigger_action(ct.DataFlowAction.DataChanged, prop_name=prop_name)
|
||||
|
||||
def trigger_action(
|
||||
self,
|
||||
action: typx.Literal[
|
||||
'enable_lock',
|
||||
'disable_lock',
|
||||
'value_changed',
|
||||
'show_preview',
|
||||
'show_plot',
|
||||
],
|
||||
action: ct.DataFlowAction,
|
||||
socket_name: ct.SocketName | None = None,
|
||||
prop_name: ct.SocketName | None = None,
|
||||
) -> None:
|
||||
|
@ -586,15 +583,15 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
Invalidates (recursively) the cache of any managed object or
|
||||
output socket method that implicitly depends on this input socket.
|
||||
"""
|
||||
#log.debug(
|
||||
# 'Action "%s" Triggered in "%s" (socket_name="%s", prop_name="%s")',
|
||||
# action,
|
||||
# self.name,
|
||||
# socket_name,
|
||||
# prop_name,
|
||||
#)
|
||||
# log.debug(
|
||||
# 'Action "%s" Triggered in "%s" (socket_name="%s", prop_name="%s")',
|
||||
# action,
|
||||
# self.name,
|
||||
# socket_name,
|
||||
# prop_name,
|
||||
# )
|
||||
# Forwards Chains
|
||||
if action == 'value_changed':
|
||||
if action == ct.DataFlowAction.DataChanged:
|
||||
# Run User Callbacks
|
||||
## Careful with these, they run BEFORE propagation...
|
||||
## ...because later-chain methods may rely on the results of this.
|
||||
|
@ -611,11 +608,11 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
and socket_name in self.loose_input_sockets
|
||||
)
|
||||
):
|
||||
#log.debug(
|
||||
# 'Running Value-Change Callback "%s" in "%s")',
|
||||
# method.__name__,
|
||||
# self.name,
|
||||
#)
|
||||
# log.debug(
|
||||
# 'Running Value-Change Callback "%s" in "%s")',
|
||||
# method.__name__,
|
||||
# self.name,
|
||||
# )
|
||||
method(self)
|
||||
|
||||
# Propagate via Output Sockets
|
||||
|
@ -623,21 +620,21 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
bl_socket.trigger_action(action)
|
||||
|
||||
# Backwards Chains
|
||||
elif action == 'enable_lock':
|
||||
elif action == ct.DataFlowAction.EnableLock:
|
||||
self.locked = True
|
||||
|
||||
## Propagate via Input Sockets
|
||||
for bl_socket in self.active_bl_sockets('input'):
|
||||
bl_socket.trigger_action(action)
|
||||
|
||||
elif action == 'disable_lock':
|
||||
elif action == ct.DataFlowAction.DisableLock:
|
||||
self.locked = False
|
||||
|
||||
## Propagate via Input Sockets
|
||||
for bl_socket in self.active_bl_sockets('input'):
|
||||
bl_socket.trigger_action(action)
|
||||
|
||||
elif action == 'show_preview':
|
||||
elif action == ct.DataFlowAction.ShowPreview:
|
||||
# Run User Callbacks
|
||||
## "On Show Preview" callbacks are 'on_value_changed' callbacks...
|
||||
## ...which simply hook into the 'preview_active' property.
|
||||
|
@ -653,7 +650,7 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
for bl_socket in self.active_bl_sockets('input'):
|
||||
bl_socket.trigger_action(action)
|
||||
|
||||
elif action == 'show_plot':
|
||||
elif action == ct.DataFlowAction.ShowPlot:
|
||||
# Run User Callbacks
|
||||
## These shouldn't change any data, BUT...
|
||||
## ...because they can stop propagation, they should go first.
|
||||
|
@ -717,7 +714,7 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
bl_socket.is_linked and bl_socket.locked
|
||||
for bl_socket in self.inputs.values()
|
||||
):
|
||||
self.trigger_action('disable_lock')
|
||||
self.trigger_action(ct.DataFlowAction.DisableLock)
|
||||
|
||||
# Free Managed Objects
|
||||
for managed_obj in self.managed_objs.values():
|
||||
|
|
|
@ -69,11 +69,12 @@ PropName: typ.TypeAlias = str
|
|||
def event_decorator(
|
||||
action_type: EventCallbackType,
|
||||
extra_data: EventCallbackData,
|
||||
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||
props: set[PropName] = frozenset(),
|
||||
managed_objs: set[ManagedObjName] = frozenset(),
|
||||
input_sockets: set[ct.SocketName] = frozenset(),
|
||||
input_socket_kinds: dict[ct.SocketName, ct.DataFlowKind] = MappingProxyType({}),
|
||||
output_sockets: set[ct.SocketName] = frozenset(),
|
||||
output_socket_kinds: dict[ct.SocketName, ct.DataFlowKind] = MappingProxyType({}),
|
||||
all_loose_input_sockets: bool = False,
|
||||
all_loose_output_sockets: bool = False,
|
||||
unit_systems: dict[UnitSystemID, UnitSystem] = MappingProxyType({}),
|
||||
|
@ -87,11 +88,11 @@ def event_decorator(
|
|||
Set to `return_method.action_type`
|
||||
extra_data: A dictionary that provides the caller with additional per-`action_type` information.
|
||||
This might include parameters to help select the most appropriate method(s) to respond to an event with, or actions to take after running the callback.
|
||||
kind: The `ct.DataFlowKind` used to compute all input and output socket data for methods with.
|
||||
Only affects data passed to the decorated method; namely `input_sockets`, `output_sockets`, and their loose variants.
|
||||
props: Set of `props` to compute, then pass to the decorated method.
|
||||
managed_objs: Set of `managed_objs` to retrieve, then pass to the decorated method.
|
||||
input_sockets: Set of `input_sockets` to compute, then pass to the decorated method.
|
||||
input_socket_kinds: The `ct.DataFlowKind` to compute per-input-socket.
|
||||
If an input socket isn't specified, it defaults to `ct.DataFlowKind.Value`.
|
||||
output_sockets: Set of `output_sockets` to compute, then pass to the decorated method.
|
||||
all_loose_input_sockets: Whether to compute all loose input sockets and pass them to the decorated method.
|
||||
Used when the names of the loose input sockets are unknown, but all of their values are needed.
|
||||
|
@ -154,7 +155,12 @@ def event_decorator(
|
|||
## Compute Requested Input Sockets
|
||||
if input_sockets:
|
||||
_input_sockets = {
|
||||
input_socket_name: node._compute_input(input_socket_name, kind)
|
||||
input_socket_name: node._compute_input(
|
||||
input_socket_name,
|
||||
kind=input_socket_kinds.get(
|
||||
input_socket_name, ct.DataFlowKind.Value
|
||||
),
|
||||
)
|
||||
for input_socket_name in input_sockets
|
||||
}
|
||||
|
||||
|
@ -163,19 +169,35 @@ def event_decorator(
|
|||
## Then, convert the symbol-less sympy scalar to a python type.
|
||||
for input_socket_name, unit_system_id in scale_input_sockets.items():
|
||||
unit_system = unit_systems[unit_system_id]
|
||||
_input_sockets[input_socket_name] = spux.sympy_to_python(
|
||||
spux.scale_to_unit(
|
||||
_input_sockets[input_socket_name],
|
||||
unit_system[node.inputs[input_socket_name].socket_type],
|
||||
)
|
||||
kind = input_socket_kinds.get(
|
||||
input_socket_name, ct.DataFlowKind.Value
|
||||
)
|
||||
|
||||
if kind == ct.DataFlowKind.Value:
|
||||
_input_sockets[input_socket_name] = spux.sympy_to_python(
|
||||
spux.scale_to_unit(
|
||||
_input_sockets[input_socket_name],
|
||||
unit_system[node.inputs[input_socket_name].socket_type],
|
||||
)
|
||||
)
|
||||
elif kind == ct.DataFlowKind.LazyValueRange:
|
||||
_input_sockets[input_socket_name] = _input_sockets[
|
||||
input_socket_name
|
||||
].rescale_to_unit(
|
||||
unit_system[node.inputs[input_socket_name].socket_type]
|
||||
)
|
||||
|
||||
method_kw_args |= {'input_sockets': _input_sockets}
|
||||
|
||||
## Compute Requested Output Sockets
|
||||
if output_sockets:
|
||||
_output_sockets = {
|
||||
output_socket_name: node.compute_output(output_socket_name, kind)
|
||||
output_socket_name: node.compute_output(
|
||||
output_socket_name,
|
||||
kind=input_socket_kinds.get(
|
||||
input_socket_name, ct.DataFlowKind.Value
|
||||
),
|
||||
)
|
||||
for output_socket_name in output_sockets
|
||||
}
|
||||
|
||||
|
@ -184,19 +206,32 @@ def event_decorator(
|
|||
## Then, convert the symbol-less sympy scalar to a python type.
|
||||
for output_socket_name, unit_system_id in scale_output_sockets.items():
|
||||
unit_system = unit_systems[unit_system_id]
|
||||
_output_sockets[output_socket_name] = spux.sympy_to_python(
|
||||
spux.scale_to_unit(
|
||||
_output_sockets[output_socket_name],
|
||||
unit_system[node.outputs[output_socket_name].socket_type],
|
||||
)
|
||||
kind = input_socket_kinds.get(
|
||||
input_socket_name, ct.DataFlowKind.Value
|
||||
)
|
||||
|
||||
if kind == ct.DataFlowKind.Value:
|
||||
_output_sockets[output_socket_name] = spux.sympy_to_python(
|
||||
spux.scale_to_unit(
|
||||
_output_sockets[output_socket_name],
|
||||
unit_system[
|
||||
node.outputs[output_socket_name].socket_type
|
||||
],
|
||||
)
|
||||
)
|
||||
elif kind == ct.DataFlowKind.LazyValueRange:
|
||||
_output_sockets[output_socket_name] = _output_sockets[
|
||||
output_socket_name
|
||||
].rescale_to_unit(
|
||||
unit_system[node.outputs[output_socket_name].socket_type]
|
||||
)
|
||||
method_kw_args |= {'output_sockets': _output_sockets}
|
||||
|
||||
# Loose Sockets
|
||||
## Compute All Loose Input Sockets
|
||||
if all_loose_input_sockets:
|
||||
_loose_input_sockets = {
|
||||
input_socket_name: node._compute_input(input_socket_name, kind)
|
||||
input_socket_name: node._compute_input(input_socket_name, kind=node.inputs[input_socket_name].active_kind)
|
||||
for input_socket_name in node.loose_input_sockets
|
||||
}
|
||||
method_kw_args |= {'loose_input_sockets': _loose_input_sockets}
|
||||
|
@ -204,7 +239,7 @@ def event_decorator(
|
|||
## Compute All Loose Output Sockets
|
||||
if all_loose_output_sockets:
|
||||
_loose_output_sockets = {
|
||||
output_socket_name: node.compute_output(output_socket_name, kind)
|
||||
output_socket_name: node.compute_output(output_socket_name, kind=node.outputs[output_socket_name].active_kind)
|
||||
for output_socket_name in node.loose_output_sockets
|
||||
}
|
||||
method_kw_args |= {'loose_output_sockets': _loose_output_sockets}
|
||||
|
@ -221,10 +256,10 @@ def event_decorator(
|
|||
|
||||
# Set Decorated Attributes and Return
|
||||
## Fix Introspection + Documentation
|
||||
#decorated.__name__ = method.__name__
|
||||
#decorated.__module__ = method.__module__
|
||||
#decorated.__qualname__ = method.__qualname__
|
||||
#decorated.__doc__ = method.__doc__
|
||||
# decorated.__name__ = method.__name__
|
||||
# decorated.__module__ = method.__module__
|
||||
# decorated.__qualname__ = method.__qualname__
|
||||
# decorated.__doc__ = method.__doc__
|
||||
|
||||
## Add Spice
|
||||
decorated.action_type = action_type
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
|
@ -15,106 +16,92 @@ class WaveConstantNode(base.MaxwellSimNode):
|
|||
bl_label = 'Wave Constant'
|
||||
|
||||
input_socket_sets: typ.ClassVar = {
|
||||
# Single
|
||||
'Vacuum WL': {
|
||||
'WL': sockets.PhysicalLengthSocketDef(
|
||||
default_value=500 * spu.nm,
|
||||
default_unit=spu.nm,
|
||||
),
|
||||
},
|
||||
'Frequency': {
|
||||
'Freq': sockets.PhysicalFreqSocketDef(
|
||||
default_value=500 * spux.THz,
|
||||
default_unit=spux.THz,
|
||||
),
|
||||
},
|
||||
# Listy
|
||||
'Vacuum WLs': {
|
||||
'WLs': sockets.PhysicalLengthSocketDef(
|
||||
is_list=True,
|
||||
),
|
||||
},
|
||||
'Frequencies': {
|
||||
'Freqs': sockets.PhysicalFreqSocketDef(
|
||||
is_list=True,
|
||||
),
|
||||
},
|
||||
'Wavelength': {},
|
||||
'Frequency': {},
|
||||
}
|
||||
|
||||
use_range: bpy.props.BoolProperty(
|
||||
name='Range',
|
||||
description='Whether to use the wavelength range',
|
||||
default=False,
|
||||
update=lambda self, context: self.sync_prop('use_range', context),
|
||||
)
|
||||
|
||||
def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout):
|
||||
col.prop(self, 'use_range', toggle=True)
|
||||
|
||||
####################
|
||||
# - Event Methods: Listy Output
|
||||
# - Event Methods: Wavelength Output
|
||||
####################
|
||||
@events.computes_output_socket(
|
||||
'WL',
|
||||
input_sockets={'WL'},
|
||||
all_loose_input_sockets=True,
|
||||
)
|
||||
def compute_vacwl_from_vacwl(self, input_sockets: dict) -> sp.Expr:
|
||||
return input_sockets['WL']
|
||||
def compute_wl(self, loose_input_sockets: dict) -> sp.Expr:
|
||||
if (wl := loose_input_sockets.get('WL')) is not None:
|
||||
return wl
|
||||
|
||||
freq = loose_input_sockets.get('Freq')
|
||||
|
||||
if isinstance(freq, ct.LazyDataValueRange):
|
||||
return freq.rescale_bounds(
|
||||
lambda bound: constants.vac_speed_of_light / bound, reverse=True
|
||||
)
|
||||
|
||||
return constants.vac_speed_of_light / freq
|
||||
|
||||
@events.computes_output_socket(
|
||||
'WL',
|
||||
input_sockets={'Freq'},
|
||||
'Freq',
|
||||
all_loose_input_sockets=True,
|
||||
)
|
||||
def compute_freq_from_vacwl(self, input_sockets: dict) -> sp.Expr:
|
||||
return constants.vac_speed_of_light / input_sockets['Freq']
|
||||
def compute_freq(self, loose_input_sockets: dict) -> sp.Expr:
|
||||
if (freq := loose_input_sockets.get('Freq')) is not None:
|
||||
return freq
|
||||
|
||||
####################
|
||||
# - Event Methods: Listy Output
|
||||
####################
|
||||
@events.computes_output_socket(
|
||||
'WLs',
|
||||
input_sockets={'WLs', 'Freqs'},
|
||||
)
|
||||
def compute_vac_wls(self, input_sockets: dict) -> sp.Expr:
|
||||
if (vac_wls := input_sockets['WLs']) is not None:
|
||||
return vac_wls
|
||||
if (freqs := input_sockets['Freqs']) is not None:
|
||||
return [constants.vac_speed_of_light / freq for freq in freqs][::-1]
|
||||
wl = loose_input_sockets.get('WL')
|
||||
|
||||
msg = 'Vac WL and Freq are both None'
|
||||
raise RuntimeError(msg)
|
||||
if isinstance(wl, ct.LazyDataValueRange):
|
||||
return wl.rescale_bounds(
|
||||
lambda bound: constants.vac_speed_of_light / bound, reverse=True
|
||||
)
|
||||
|
||||
@events.computes_output_socket(
|
||||
'Freqs',
|
||||
input_sockets={'WLs', 'Freqs'},
|
||||
)
|
||||
def compute_freqs(self, input_sockets: dict) -> sp.Expr:
|
||||
if (vac_wls := input_sockets['WLs']) is not None:
|
||||
return [constants.vac_speed_of_light / vac_wl for vac_wl in vac_wls][::-1]
|
||||
if (freqs := input_sockets['Freqs']) is not None:
|
||||
return freqs
|
||||
|
||||
msg = 'Vac WL and Freq are both None'
|
||||
raise RuntimeError(msg)
|
||||
return constants.vac_speed_of_light / wl
|
||||
|
||||
####################
|
||||
# - Event Methods
|
||||
####################
|
||||
@events.on_value_changed(prop_name='active_socket_set', props={'active_socket_set'})
|
||||
def on_active_socket_set_changed(self, props: dict):
|
||||
# Singular: Normal Output Sockets
|
||||
if props['active_socket_set'] in {'Vacuum WL', 'Frequency'}:
|
||||
self.loose_output_sockets = {}
|
||||
self.loose_output_sockets = {
|
||||
'Freq': sockets.PhysicalFreqSocketDef(),
|
||||
'WL': sockets.PhysicalLengthSocketDef(),
|
||||
@events.on_value_changed(
|
||||
prop_name={'active_socket_set', 'use_range'},
|
||||
props={'active_socket_set', 'use_range'},
|
||||
)
|
||||
def on_input_spec_change(self, props: dict):
|
||||
if props['active_socket_set'] == 'Wavelength':
|
||||
self.loose_input_sockets = {
|
||||
'WL': sockets.PhysicalLengthSocketDef(
|
||||
is_array=props['use_range'],
|
||||
default_value=500 * spu.nm,
|
||||
default_unit=spu.nm,
|
||||
)
|
||||
}
|
||||
|
||||
# Plural: Listy Output Sockets
|
||||
elif props['active_socket_set'] in {'Vacuum WLs', 'Frequencies'}:
|
||||
self.loose_output_sockets = {}
|
||||
self.loose_output_sockets = {
|
||||
'Freqs': sockets.PhysicalFreqSocketDef(is_list=True),
|
||||
'WLs': sockets.PhysicalLengthSocketDef(is_list=True),
|
||||
}
|
||||
|
||||
else:
|
||||
msg = f"Active socket set invalid for wave constant: {props['active_socket_set']}"
|
||||
raise RuntimeError(msg)
|
||||
self.loose_input_sockets = {
|
||||
'Freq': sockets.PhysicalFreqSocketDef(
|
||||
is_array=props['use_range'],
|
||||
default_value=600 * spux.THz,
|
||||
default_unit=spux.THz,
|
||||
)
|
||||
}
|
||||
|
||||
@events.on_init()
|
||||
def on_init(self):
|
||||
self.on_active_socket_set_changed()
|
||||
self.loose_output_sockets = {
|
||||
'WL': sockets.PhysicalLengthSocketDef(is_array=props['use_range']),
|
||||
'Freq': sockets.PhysicalFreqSocketDef(is_array=props['use_range']),
|
||||
}
|
||||
|
||||
@events.on_init(
|
||||
props={'active_socket_set', 'use_range'},
|
||||
)
|
||||
def on_init(self, props: dict):
|
||||
self.on_input_spec_change()
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -34,7 +34,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
|||
input_socket_sets: typ.ClassVar = {
|
||||
'Freq Domain': {
|
||||
'Freqs': sockets.PhysicalFreqSocketDef(
|
||||
is_list=True,
|
||||
is_array=True,
|
||||
),
|
||||
},
|
||||
'Time Domain': {
|
||||
|
|
|
@ -34,7 +34,7 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
|||
input_socket_sets: typ.ClassVar = {
|
||||
'Freq Domain': {
|
||||
'Freqs': sockets.PhysicalFreqSocketDef(
|
||||
is_list=True,
|
||||
is_array=True,
|
||||
),
|
||||
},
|
||||
'Time Domain': {
|
||||
|
@ -74,10 +74,14 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
|||
'Freqs',
|
||||
'Direction',
|
||||
},
|
||||
input_socket_kinds={
|
||||
'Freqs': ct.LazyDataValueRange,
|
||||
},
|
||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
||||
scale_input_sockets={
|
||||
'Center': 'Tidy3DUnits',
|
||||
'Size': 'Tidy3DUnits',
|
||||
'Freqs': 'Tidy3DUnits',
|
||||
'Samples/Space': 'Tidy3DUnits',
|
||||
'Rec Start': 'Tidy3DUnits',
|
||||
'Rec Stop': 'Tidy3DUnits',
|
||||
|
@ -88,8 +92,6 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
|||
direction = '+' if input_sockets['Direction'] else '-'
|
||||
|
||||
if props['active_socket_set'] == 'Freq Domain':
|
||||
freqs = input_sockets['Freqs']
|
||||
|
||||
log.info(
|
||||
'Computing FluxMonitor (name="%s") with center="%s", size="%s"',
|
||||
props['sim_node_name'],
|
||||
|
@ -101,9 +103,7 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
|||
size=input_sockets['Size'],
|
||||
name=props['sim_node_name'],
|
||||
interval_space=input_sockets['Samples/Space'],
|
||||
freqs=[
|
||||
float(spu.convert_to(freq, spu.hertz) / spu.hertz) for freq in freqs
|
||||
],
|
||||
freqs=input_sockets['Freqs'].realize().values,
|
||||
normal_dir=direction,
|
||||
)
|
||||
|
||||
|
|
|
@ -180,7 +180,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
|||
####################
|
||||
def sync_lock_tree(self, context):
|
||||
if self.lock_tree:
|
||||
self.trigger_action('enable_lock')
|
||||
self.trigger_action(ct.DataFlowAction.EnableLock)
|
||||
self.locked = False
|
||||
for bl_socket in self.inputs:
|
||||
if bl_socket.name == 'FDTD Sim':
|
||||
|
@ -188,7 +188,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
|||
bl_socket.locked = False
|
||||
|
||||
else:
|
||||
self.trigger_action('disable_lock')
|
||||
self.trigger_action(ct.DataFlowAction.DisableLock)
|
||||
|
||||
self.sync_prop('lock_tree', context)
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ class ViewerNode(base.MaxwellSimNode):
|
|||
def on_changed_plot_preview(self, props):
|
||||
if self.inputs['Data'].is_linked and props['auto_plot']:
|
||||
log.info('Enabling 2D Plot from "%s"', self.name)
|
||||
self.trigger_action('show_plot')
|
||||
self.trigger_action(ct.DataFlowAction.ShowPlot)
|
||||
|
||||
@events.on_value_changed(
|
||||
prop_name='auto_3d_preview',
|
||||
|
@ -145,7 +145,7 @@ class ViewerNode(base.MaxwellSimNode):
|
|||
# Trigger Preview Action
|
||||
if self.inputs['Data'].is_linked and props['auto_3d_preview']:
|
||||
log.info('Enabling 3D Previews from "%s"', self.name)
|
||||
self.trigger_action('show_preview')
|
||||
self.trigger_action(ct.DataFlowAction.ShowPreview)
|
||||
|
||||
@events.on_value_changed(
|
||||
socket_name='Data',
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
#import typing as typ
|
||||
#import bpy
|
||||
#
|
||||
#from .. import contracts as ct
|
||||
#
|
||||
#
|
||||
#
|
||||
#class MaxwellSimProp(bpy.types.PropertyGroup):
|
||||
# """A Blender property usable in nodes and sockets."""
|
||||
# name: str = ""
|
||||
# data_flow_kind: ct.DataFlowKind
|
||||
#
|
||||
# value: dict[str, tuple[bpy.types.Property, dict]] | None = None
|
||||
#
|
||||
# def __init_subclass__(cls, **kwargs: typ.Any):
|
||||
# log.debug('Initializing Prop: %s', cls.node_type)
|
||||
# super().__init_subclass__(**kwargs)
|
||||
#
|
||||
# # Setup Value
|
||||
# if cls.value:
|
||||
# cls.__annotations__['raw_value'] = value
|
||||
#
|
||||
#
|
||||
# @property
|
||||
# def value(self):
|
||||
# if self.data_flow_kind
|
|
@ -65,11 +65,11 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
cls.socket_shape = ct.SOCKET_SHAPES[cls.socket_type]
|
||||
|
||||
# Setup List
|
||||
cls.__annotations__['is_list'] = bpy.props.BoolProperty(
|
||||
name='Is List',
|
||||
description='Whether or not a particular socket is a list type socket',
|
||||
default=False,
|
||||
update=lambda self, context: self.sync_is_list(context),
|
||||
cls.__annotations__['active_kind'] = bpy.props.StringProperty(
|
||||
name='Active Kind',
|
||||
description='The active Data Flow Kind',
|
||||
default=str(ct.DataFlowKind.Value),
|
||||
update=lambda self, _: self.sync_active_kind(),
|
||||
)
|
||||
|
||||
# Configure Use of Units
|
||||
|
@ -90,7 +90,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
for unit_name, unit_value in socket_units['values'].items()
|
||||
],
|
||||
default=socket_units['default'],
|
||||
update=lambda self, context: self.sync_unit_change(),
|
||||
update=lambda self, _: self.sync_unit_change(),
|
||||
)
|
||||
|
||||
# Previous Unit (for conversion)
|
||||
|
@ -103,13 +103,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
####################
|
||||
def trigger_action(
|
||||
self,
|
||||
action: typx.Literal[
|
||||
'enable_lock',
|
||||
'disable_lock',
|
||||
'value_changed',
|
||||
'show_preview',
|
||||
'show_plot',
|
||||
],
|
||||
action: ct.DataFlowAction,
|
||||
) -> None:
|
||||
"""Called whenever the socket's output value has changed.
|
||||
|
||||
|
@ -157,24 +151,30 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
####################
|
||||
# - Action Chain: Event Handlers
|
||||
####################
|
||||
def sync_is_list(self, context: bpy.types.Context):
|
||||
"""Called when the "is_list_ property has been updated."""
|
||||
if self.is_list:
|
||||
if self.use_units:
|
||||
self.display_shape = 'SQUARE_DOT'
|
||||
else:
|
||||
self.display_shape = 'SQUARE'
|
||||
def sync_active_kind(self):
|
||||
"""Called when the active data flow kind of the socket changes.
|
||||
|
||||
self.trigger_action('value_changed')
|
||||
Alters the shape of the socket to match the active DataFlowKind, then triggers `ct.DataFlowAction.DataChanged` on the current socket.
|
||||
"""
|
||||
self.display_shape = {
|
||||
ct.DataFlowKind.Value: ct.SOCKET_SHAPES[self.socket_type],
|
||||
ct.DataFlowKind.ValueArray: 'SQUARE',
|
||||
ct.DataFlowKind.ValueSpectrum: 'SQUARE',
|
||||
ct.DataFlowKind.LazyValue: ct.SOCKET_SHAPES[self.socket_type],
|
||||
ct.DataFlowKind.LazyValueRange: 'SQUARE',
|
||||
ct.DataFlowKind.LazyValueSpectrum: 'SQUARE',
|
||||
}[self.active_kind] + ('_DOT' if self.use_units else '')
|
||||
|
||||
def sync_prop(self, prop_name: str, context: bpy.types.Context):
|
||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
||||
|
||||
def sync_prop(self, prop_name: str, _: bpy.types.Context):
|
||||
"""Called when a property has been updated."""
|
||||
if not hasattr(self, prop_name):
|
||||
if hasattr(self, prop_name):
|
||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
||||
else:
|
||||
msg = f'Property {prop_name} not defined on socket {self}'
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.trigger_action('value_changed')
|
||||
|
||||
def sync_link_added(self, link) -> bool:
|
||||
"""Called when a link has been added to this (input) socket.
|
||||
|
||||
|
@ -186,7 +186,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
msg = "Tried to sync 'link add' on output socket"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.trigger_action('value_changed')
|
||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -201,63 +201,78 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
msg = "Tried to sync 'link add' on output socket"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.trigger_action('value_changed')
|
||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
||||
|
||||
return True
|
||||
|
||||
####################
|
||||
# - Data Chain
|
||||
####################
|
||||
# Capabilities
|
||||
@property
|
||||
def value(self) -> typ.Any:
|
||||
def capabilities(self) -> None:
|
||||
return ct.DataCapabilities(
|
||||
socket_type=self.socket_type,
|
||||
active_kind=self.active_kind,
|
||||
)
|
||||
|
||||
# Value
|
||||
@property
|
||||
def value(self) -> ct.DataValue:
|
||||
raise NotImplementedError
|
||||
|
||||
@value.setter
|
||||
def value(self, value: typ.Any) -> None:
|
||||
def value(self, value: ct.DataValue) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
# ValueArray
|
||||
@property
|
||||
def value_list(self) -> typ.Any:
|
||||
return [self.value]
|
||||
|
||||
@value_list.setter
|
||||
def value_list(self, value: typ.Any) -> None:
|
||||
def value_array(self) -> ct.DataValueArray:
|
||||
raise NotImplementedError
|
||||
|
||||
def value_as_unit_system(
|
||||
self, unit_system: dict, dimensionless: bool = True
|
||||
) -> typ.Any:
|
||||
## TODO: Caching could speed this boi up quite a bit
|
||||
|
||||
unit_system_unit = unit_system[self.socket_type]
|
||||
return (
|
||||
spu.convert_to(
|
||||
self.value,
|
||||
unit_system_unit,
|
||||
)
|
||||
/ unit_system_unit
|
||||
)
|
||||
@value_array.setter
|
||||
def value_array(self, value: ct.DataValueArray) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
# ValueSpectrum
|
||||
@property
|
||||
def lazy_value(self) -> None:
|
||||
def value_spectrum(self) -> ct.DataValueSpectrum:
|
||||
raise NotImplementedError
|
||||
|
||||
@value_spectrum.setter
|
||||
def value_spectrum(self, value: ct.DataValueSpectrum) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
# LazyValue
|
||||
@property
|
||||
def lazy_value(self) -> ct.LazyDataValue:
|
||||
raise NotImplementedError
|
||||
|
||||
@lazy_value.setter
|
||||
def lazy_value(self, lazy_value: typ.Any) -> None:
|
||||
def lazy_value(self, lazy_value: ct.LazyDataValue) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
# LazyValueRange
|
||||
@property
|
||||
def lazy_value_list(self) -> typ.Any:
|
||||
return [self.lazy_value]
|
||||
|
||||
@lazy_value_list.setter
|
||||
def lazy_value_list(self, value: typ.Any) -> None:
|
||||
def lazy_value_range(self) -> ct.LazyDataValueRange:
|
||||
raise NotImplementedError
|
||||
|
||||
@lazy_value_range.setter
|
||||
def lazy_value_range(self, value: tuple[ct.DataValue, ct.DataValue, int]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
# LazyValueSpectrum
|
||||
@property
|
||||
def capabilities(self) -> None:
|
||||
def lazy_value_spectrum(self) -> ct.LazyDataValueSpectrum:
|
||||
raise NotImplementedError
|
||||
|
||||
@lazy_value_spectrum.setter
|
||||
def lazy_value_spectrum(self, value: ct.LazyDataValueSpectrum) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
####################
|
||||
# - Data Chain Computation
|
||||
####################
|
||||
def _compute_data(
|
||||
self,
|
||||
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||
|
@ -266,18 +281,17 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
|
||||
**NOTE**: Low-level method. Use `compute_data` instead.
|
||||
"""
|
||||
if kind == ct.DataFlowKind.Value:
|
||||
if self.is_list:
|
||||
return self.value_list
|
||||
return self.value
|
||||
if kind == ct.DataFlowKind.LazyValue:
|
||||
if self.is_list:
|
||||
return self.lazy_value_list
|
||||
return self.lazy_value
|
||||
if kind == ct.DataFlowKind.Capabilities:
|
||||
return self.capabilities
|
||||
return {
|
||||
ct.DataFlowKind.Value: lambda: self.value,
|
||||
ct.DataFlowKind.ValueArray: lambda: self.value_array,
|
||||
ct.DataFlowKind.ValueSpectrum: lambda: self.value_spectrum,
|
||||
ct.DataFlowKind.LazyValue: lambda: self.lazy_value,
|
||||
ct.DataFlowKind.LazyValueRange: lambda: self.lazy_value_range,
|
||||
ct.DataFlowKind.LazyValueSpectrum: lambda: self.lazy_value_spectrum,
|
||||
}[kind]()
|
||||
|
||||
return None
|
||||
msg = f'socket._compute_data was called with invalid kind "{kind}"'
|
||||
raise RuntimeError(msg)
|
||||
|
||||
def compute_data(
|
||||
self,
|
||||
|
@ -291,22 +305,25 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
- If output socket, ask node for data.
|
||||
"""
|
||||
# Compute Output Socket
|
||||
## List-like sockets guarantee that a list of a thing is passed.
|
||||
if self.is_output:
|
||||
res = self.node.compute_output(self.name, kind=kind)
|
||||
if self.is_list and not isinstance(res, list):
|
||||
return [res]
|
||||
return res
|
||||
return self.node.compute_output(self.name, kind=kind)
|
||||
|
||||
# Compute Input Socket
|
||||
## Unlinked: Retrieve Socket Value
|
||||
if not self.is_linked:
|
||||
return self._compute_data(kind)
|
||||
|
||||
## Linked: Compute Output of Linked Sockets
|
||||
## Linked: Check Capabilities
|
||||
for link in self.links:
|
||||
if not link.from_socket.capabilities.is_compatible_with(self.capabilities):
|
||||
msg = f'Output socket "{link.from_socket.bl_label}" is linked to input socket "{self.bl_label}" with incompatible capabilities (caps_out="{link.from_socket.capabilities}", caps_in="{self.capabilities}")'
|
||||
raise ValueError(msg)
|
||||
|
||||
## ...and Compute Data on Linked Socket
|
||||
linked_values = [link.from_socket.compute_data(kind) for link in self.links]
|
||||
|
||||
## Return Single Value / List of Values
|
||||
# Return Single Value / List of Values
|
||||
## Preparation for multi-input sockets.
|
||||
if len(linked_values) == 1:
|
||||
return linked_values[0]
|
||||
return linked_values
|
||||
|
@ -361,14 +378,16 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
|
||||
Can be overridden if more specific logic is required.
|
||||
"""
|
||||
prev_value = self.value / self.unit * self.prev_unit
|
||||
## After changing units, self.value is expressed in the wrong unit.
|
||||
## - Therefore, we removing the new unit, and re-add the prev unit.
|
||||
## - Using only self.value avoids implementation-specific details.
|
||||
if self.active_kind == ct.DataFlowKind.Value:
|
||||
self.value = self.value / self.unit * self.prev_unit
|
||||
|
||||
self.value = spu.convert_to(
|
||||
prev_value, self.unit
|
||||
) ## Now, the unit conversion can be done correctly.
|
||||
elif self.active_kind == ct.DataFlowKind.LazyValueRange:
|
||||
lazy_value_range = self.lazy_value_range
|
||||
self.lazy_value_range = (
|
||||
lazy_value_range.start / self.unit * self.prev_unit,
|
||||
lazy_value_range.stop / self.unit * self.prev_unit,
|
||||
lazy_value_range.steps,
|
||||
)
|
||||
|
||||
self.prev_active_unit = self.active_unit
|
||||
|
||||
|
@ -458,12 +477,16 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
elif self.locked:
|
||||
row.enabled = False
|
||||
|
||||
# Value Column(s)
|
||||
# Data Column(s)
|
||||
col = row.column(align=True)
|
||||
if self.is_list:
|
||||
self.draw_value_list(col)
|
||||
else:
|
||||
self.draw_value(col)
|
||||
{
|
||||
ct.DataFlowKind.Value: self.draw_value,
|
||||
ct.DataFlowKind.ValueArray: self.draw_value_array,
|
||||
ct.DataFlowKind.ValueSpectrum: self.draw_value_spectrum,
|
||||
ct.DataFlowKind.LazyValue: self.draw_lazy_value,
|
||||
ct.DataFlowKind.LazyValueRange: self.draw_lazy_value_range,
|
||||
ct.DataFlowKind.LazyValueSpectrum: self.draw_lazy_value_spectrum,
|
||||
}[self.active_kind](col)
|
||||
|
||||
def draw_output(
|
||||
self,
|
||||
|
@ -489,14 +512,23 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
"""
|
||||
row.label(text=text)
|
||||
|
||||
####################
|
||||
# - DataFlowKind draw() Methods
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
"""Called to draw the value column in unlinked input sockets.
|
||||
pass
|
||||
|
||||
Can be overridden.
|
||||
"""
|
||||
def draw_value_array(self, col: bpy.types.UILayout) -> None:
|
||||
pass
|
||||
|
||||
def draw_value_list(self, col: bpy.types.UILayout) -> None:
|
||||
"""Called to draw the value list column in unlinked input sockets.
|
||||
def draw_value_spectrum(self, col: bpy.types.UILayout) -> None:
|
||||
pass
|
||||
|
||||
Can be overridden.
|
||||
"""
|
||||
def draw_lazy_value(self, col: bpy.types.UILayout) -> None:
|
||||
pass
|
||||
|
||||
def draw_lazy_value_range(self, col: bpy.types.UILayout) -> None:
|
||||
pass
|
||||
|
||||
def draw_lazy_value_spectrum(self, col: bpy.types.UILayout) -> None:
|
||||
pass
|
||||
|
|
|
@ -10,6 +10,14 @@ from .. import base
|
|||
class AnyBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.Any
|
||||
bl_label = 'Any'
|
||||
|
||||
@property
|
||||
def capabilities(self):
|
||||
return ct.DataCapabilities(
|
||||
socket_type=self.socket_type,
|
||||
active_kind=self.active_kind,
|
||||
is_universal=True,
|
||||
)
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -18,7 +18,8 @@ class MaxwellMonitorSocketDef(pyd.BaseModel):
|
|||
is_list: bool = False
|
||||
|
||||
def init(self, bl_socket: MaxwellMonitorBLSocket) -> None:
|
||||
bl_socket.is_list = self.is_list
|
||||
if self.is_list:
|
||||
bl_socket.active_kind = ct.DataValueArray
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -18,7 +18,8 @@ class MaxwellSourceSocketDef(pyd.BaseModel):
|
|||
is_list: bool = False
|
||||
|
||||
def init(self, bl_socket: MaxwellSourceBLSocket) -> None:
|
||||
bl_socket.is_list = self.is_list
|
||||
if self.is_list:
|
||||
bl_socket.active_kind = ct.DataValueArray
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -18,7 +18,8 @@ class MaxwellStructureSocketDef(pyd.BaseModel):
|
|||
is_list: bool = False
|
||||
|
||||
def init(self, bl_socket: MaxwellStructureBLSocket) -> None:
|
||||
bl_socket.is_list = self.is_list
|
||||
if self.is_list:
|
||||
bl_socket.active_kind = ct.DataValueArray
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import bpy
|
||||
import numpy as np
|
||||
import pydantic as pyd
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
from .....utils import extra_sympy_units as spux
|
||||
from .....utils import logger
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from ... import contracts as ct
|
||||
from .. import base
|
||||
|
||||
log = logger.get(__name__)
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
|
@ -55,7 +58,7 @@ class PhysicalFreqBLSocket(base.MaxwellSimSocket):
|
|||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, 'raw_value', text='')
|
||||
|
||||
def draw_value_list(self, col: bpy.types.UILayout) -> None:
|
||||
def draw_lazy_value_range(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, 'min_freq', text='Min')
|
||||
col.prop(self, 'max_freq', text='Max')
|
||||
col.prop(self, 'steps', text='Steps')
|
||||
|
@ -69,32 +72,25 @@ class PhysicalFreqBLSocket(base.MaxwellSimSocket):
|
|||
|
||||
@value.setter
|
||||
def value(self, value: SympyExpr) -> None:
|
||||
self.raw_value = spu.convert_to(value, self.unit) / self.unit
|
||||
self.raw_value = spux.sympy_to_python(spux.scale_to_unit(value, self.unit))
|
||||
|
||||
@property
|
||||
def value_list(self) -> list[SympyExpr]:
|
||||
return [
|
||||
el * self.unit
|
||||
for el in np.linspace(self.min_freq, self.max_freq, self.steps)
|
||||
]
|
||||
def lazy_value_range(self) -> ct.LazyDataValueRange:
|
||||
return ct.LazyDataValueRange(
|
||||
symbols=set(),
|
||||
has_unit=True,
|
||||
start=sp.S(self.min_freq) * self.unit,
|
||||
stop=sp.S(self.max_freq) * self.unit,
|
||||
steps=self.steps,
|
||||
scaling='lin',
|
||||
)
|
||||
|
||||
@value_list.setter
|
||||
def value_list(self, value: tuple[SympyExpr, SympyExpr, int]):
|
||||
self.min_freq, self.max_freq, self.steps = [
|
||||
spu.convert_to(el, self.unit) / self.unit for el in value[:2]
|
||||
] + [value[2]]
|
||||
|
||||
def sync_unit_change(self) -> None:
|
||||
if self.is_list:
|
||||
self.value_list = (
|
||||
spu.convert_to(self.min_freq * self.prev_unit, self.unit),
|
||||
spu.convert_to(self.max_freq * self.prev_unit, self.unit),
|
||||
self.steps,
|
||||
)
|
||||
else:
|
||||
self.value = self.value / self.unit * self.prev_unit
|
||||
|
||||
self.prev_active_unit = self.active_unit
|
||||
@lazy_value_range.setter
|
||||
def lazy_value_range(self, value: tuple[sp.Expr, sp.Expr, int]) -> None:
|
||||
log.debug('Lazy Value Range: %s', str(value))
|
||||
self.min_freq = spux.sympy_to_python(spux.scale_to_unit(value[0], self.unit))
|
||||
self.max_freq = spux.sympy_to_python(spux.scale_to_unit(value[1], self.unit))
|
||||
self.steps = value[2]
|
||||
|
||||
|
||||
####################
|
||||
|
@ -102,24 +98,22 @@ class PhysicalFreqBLSocket(base.MaxwellSimSocket):
|
|||
####################
|
||||
class PhysicalFreqSocketDef(pyd.BaseModel):
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalFreq
|
||||
is_array: bool = False
|
||||
|
||||
default_value: SympyExpr = 500 * spux.terahertz
|
||||
default_unit: SympyExpr | None = None
|
||||
is_list: bool = False
|
||||
default_unit: SympyExpr = spux.terahertz
|
||||
|
||||
min_freq: SympyExpr = 400.0 * spux.terahertz
|
||||
max_freq: SympyExpr = 600.0 * spux.terahertz
|
||||
steps: SympyExpr = 50
|
||||
|
||||
def init(self, bl_socket: PhysicalFreqBLSocket) -> None:
|
||||
bl_socket.unit = self.default_unit
|
||||
|
||||
bl_socket.value = self.default_value
|
||||
bl_socket.is_list = self.is_list
|
||||
|
||||
if self.default_unit:
|
||||
bl_socket.unit = self.default_unit
|
||||
|
||||
if self.is_list:
|
||||
bl_socket.value_list = (self.min_freq, self.max_freq, self.steps)
|
||||
if self.is_array:
|
||||
bl_socket.active_kind = ct.DataFlowKind.LazyValueRange
|
||||
bl_socket.lazy_value_range = (self.min_freq, self.max_freq, self.steps)
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import bpy
|
||||
import numpy as np
|
||||
import pydantic as pyd
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
from .....utils import logger
|
||||
from .....utils import extra_sympy_units as spux
|
||||
from .....utils import logger
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from ... import contracts as ct
|
||||
from .. import base
|
||||
|
@ -58,7 +58,7 @@ class PhysicalLengthBLSocket(base.MaxwellSimSocket):
|
|||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, 'raw_value', text='')
|
||||
|
||||
def draw_value_list(self, col: bpy.types.UILayout) -> None:
|
||||
def draw_lazy_value_range(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, 'min_len', text='Min')
|
||||
col.prop(self, 'max_len', text='Max')
|
||||
col.prop(self, 'steps', text='Steps')
|
||||
|
@ -75,28 +75,21 @@ class PhysicalLengthBLSocket(base.MaxwellSimSocket):
|
|||
self.raw_value = spux.sympy_to_python(spux.scale_to_unit(value, self.unit))
|
||||
|
||||
@property
|
||||
def value_list(self) -> list[SympyExpr]:
|
||||
return [
|
||||
el * self.unit for el in np.linspace(self.min_len, self.max_len, self.steps)
|
||||
]
|
||||
def lazy_value_range(self) -> ct.LazyDataValueRange:
|
||||
return ct.LazyDataValueRange(
|
||||
symbols=set(),
|
||||
has_unit=True,
|
||||
start=sp.S(self.min_len) * self.unit,
|
||||
stop=sp.S(self.max_len) * self.unit,
|
||||
steps=self.steps,
|
||||
scaling='lin',
|
||||
)
|
||||
|
||||
@value_list.setter
|
||||
def value_list(self, value: tuple[SympyExpr, SympyExpr, int]):
|
||||
self.min_len, self.max_len, self.steps = [
|
||||
spu.convert_to(el, self.unit) / self.unit for el in value[:2]
|
||||
] + [value[2]]
|
||||
|
||||
def sync_unit_change(self) -> None:
|
||||
if self.is_list:
|
||||
self.value_list = (
|
||||
spu.convert_to(self.min_len * self.prev_unit, self.unit),
|
||||
spu.convert_to(self.max_len * self.prev_unit, self.unit),
|
||||
self.steps,
|
||||
)
|
||||
else:
|
||||
self.value = self.value / self.unit * self.prev_unit
|
||||
|
||||
self.prev_active_unit = self.active_unit
|
||||
@lazy_value_range.setter
|
||||
def lazy_value_range(self, value: tuple[sp.Expr, sp.Expr, int]) -> None:
|
||||
self.min_len = spux.sympy_to_python(spux.scale_to_unit(value[0], self.unit))
|
||||
self.max_len = spux.sympy_to_python(spux.scale_to_unit(value[1], self.unit))
|
||||
self.steps = value[2]
|
||||
|
||||
|
||||
####################
|
||||
|
@ -104,24 +97,24 @@ class PhysicalLengthBLSocket(base.MaxwellSimSocket):
|
|||
####################
|
||||
class PhysicalLengthSocketDef(pyd.BaseModel):
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalLength
|
||||
is_array: bool = False
|
||||
|
||||
default_value: SympyExpr = 1 * spu.um
|
||||
default_unit: SympyExpr | None = None
|
||||
is_list: bool = False
|
||||
|
||||
min_len: SympyExpr = 400.0 * spu.nm
|
||||
max_len: SympyExpr = 600.0 * spu.nm
|
||||
max_len: SympyExpr = 700.0 * spu.nm
|
||||
steps: SympyExpr = 50
|
||||
|
||||
def init(self, bl_socket: PhysicalLengthBLSocket) -> None:
|
||||
bl_socket.value = self.default_value
|
||||
bl_socket.is_list = self.is_list
|
||||
|
||||
if self.default_unit:
|
||||
bl_socket.unit = self.default_unit
|
||||
|
||||
if self.is_list:
|
||||
bl_socket.value_list = (self.min_len, self.max_len, self.steps)
|
||||
bl_socket.value = self.default_value
|
||||
if self.is_array:
|
||||
bl_socket.active_kind = ct.DataFlowKind.LazyValueRange
|
||||
bl_socket.lazy_value_range = (self.min_len, self.max_len, self.steps)
|
||||
|
||||
|
||||
|
||||
####################
|
||||
|
|
Loading…
Reference in New Issue