Compare commits
4 Commits
e330b9a451
...
339ee0226d
Author | SHA1 | Date |
---|---|---|
Sofus Albert Høgsbro Rose | 339ee0226d | |
Sofus Albert Høgsbro Rose | 2f42c9d91b | |
Sofus Albert Høgsbro Rose | f60b736584 | |
Sofus Albert Høgsbro Rose | 7263d585e5 |
47
TODO.md
47
TODO.md
|
@ -1,12 +1,5 @@
|
||||||
# Working TODO
|
# Working TODO
|
||||||
- [ ] Wave Constant
|
- [x] Wave Constant
|
||||||
- Bounds
|
|
||||||
- [ ] Boundary Conds
|
|
||||||
- [ ] PML
|
|
||||||
- [ ] PEC
|
|
||||||
- [ ] PMC
|
|
||||||
- [ ] Bloch
|
|
||||||
- [ ] Absorbing
|
|
||||||
- Sources
|
- Sources
|
||||||
- [ ] Temporal Shapes / Continuous Wave Temporal Shape
|
- [ ] Temporal Shapes / Continuous Wave Temporal Shape
|
||||||
- [ ] Temporal Shapes / Symbolic Temporal Shape
|
- [ ] Temporal Shapes / Symbolic Temporal Shape
|
||||||
|
@ -18,8 +11,8 @@
|
||||||
- [ ] Data File Import
|
- [ ] Data File Import
|
||||||
- [ ] DataFit Medium
|
- [ ] DataFit Medium
|
||||||
- Monitors
|
- Monitors
|
||||||
- [ ] EH Field
|
- [x] EH Field
|
||||||
- [ ] Power Flux
|
- [x] Power Flux
|
||||||
- [ ] Permittivity
|
- [ ] Permittivity
|
||||||
- [ ] Diffraction
|
- [ ] Diffraction
|
||||||
- Structures
|
- Structures
|
||||||
|
@ -49,9 +42,9 @@
|
||||||
- Integration
|
- Integration
|
||||||
- [ ] Simulation and Analysis of Maxim's Cavity
|
- [ ] Simulation and Analysis of Maxim's Cavity
|
||||||
- Constants
|
- Constants
|
||||||
- [ ] Number Constant
|
- [x] Number Constant
|
||||||
- [ ] Vector Constant
|
- [x] Vector Constant
|
||||||
- [ ] Physical Constant
|
- [x] Physical Constant
|
||||||
|
|
||||||
- [ ] Fix many problems by persisting `_enum_cb_cache` and `_str_cb_cache`.
|
- [ ] Fix many problems by persisting `_enum_cb_cache` and `_str_cb_cache`.
|
||||||
|
|
||||||
|
@ -70,7 +63,7 @@
|
||||||
- [ ] Pol SocketType: 3D Poincare sphere visualization of Stokes vectors.
|
- [ ] Pol SocketType: 3D Poincare sphere visualization of Stokes vectors.
|
||||||
|
|
||||||
- [x] Math / Map Math
|
- [x] Math / Map Math
|
||||||
- [ ] Remove "By x" socket set let socket sets only be "Function"/"Expr"; then add a dynamic enum underneath to select "By x" based on data support.
|
- [x] Remove "By x" socket set let socket sets only be "Function"/"Expr"; then add a dynamic enum underneath to select "By x" based on data support.
|
||||||
- [ ] Filter the operations based on data support, ex. use positive-definiteness to guide cholesky.
|
- [ ] Filter the operations based on data support, ex. use positive-definiteness to guide cholesky.
|
||||||
- [ ] Implement support for additional symbols via `Expr`.
|
- [ ] Implement support for additional symbols via `Expr`.
|
||||||
- [x] Math / Filter Math
|
- [x] Math / Filter Math
|
||||||
|
@ -81,8 +74,6 @@
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
- [x] Wave Constant
|
- [x] Wave Constant
|
||||||
- [ ] Fix the LazyValueRange (again!)
|
|
||||||
- [ ] Document
|
|
||||||
- [x] Scene
|
- [x] Scene
|
||||||
- [ ] Implement export of scene time via. Blender unit system.
|
- [ ] Implement export of scene time via. Blender unit system.
|
||||||
- [ ] Implement optional scene-synced time exporting, so that the simulation definition and scene definition match for analysis needs.
|
- [ ] Implement optional scene-synced time exporting, so that the simulation definition and scene definition match for analysis needs.
|
||||||
|
@ -90,14 +81,14 @@
|
||||||
- [x] Constants / Expr Constant
|
- [x] Constants / Expr Constant
|
||||||
- See IDEAS.
|
- See IDEAS.
|
||||||
- [x] Constants / Number Constant
|
- [x] Constants / Number Constant
|
||||||
- [ ] Fix non-integer sockets
|
- [x] Constants / Vector Constant
|
||||||
- [ ] Constants / Vector Constant
|
- [x] Constants / Physical Constant
|
||||||
- [ ] Constants / Physical Constant
|
|
||||||
- [x] Constants / Scientific Constant
|
- [x] Constants / Scientific Constant
|
||||||
- [ ] Nicer (boxed?) node information, maybe centered headers, in a box, etc. .
|
- [ ] Nicer (boxed?) node information, maybe centered headers, in a box, etc. .
|
||||||
- [x] Constants / Unit System Constant
|
- [ ] Constants / Unit System Constant
|
||||||
|
- [ ] Re-implement with `PhysicalType`.
|
||||||
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
|
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
|
||||||
- [x] Constants / Blender Constant
|
- [ ] Constants / Blender Constant
|
||||||
- [ ] Fix it!
|
- [ ] Fix it!
|
||||||
|
|
||||||
- [ ] Web / Tidy3D Web Importer
|
- [ ] Web / Tidy3D Web Importer
|
||||||
|
@ -200,13 +191,13 @@
|
||||||
|
|
||||||
## Bounds
|
## Bounds
|
||||||
- [x] Boundary Conds
|
- [x] Boundary Conds
|
||||||
- [ ] Boundary Cond / PML Bound Face
|
- [x] Boundary Cond / PML Bound Cond
|
||||||
- [ ] Dropdown for "Normal" and "Stable"
|
- [ ] 1D plot visualizing the effect of parameters on a 1D wave function
|
||||||
- [ ] Boundary Cond / PEC Bound Face
|
- [x] Boundary Cond / Bloch Bound Cond
|
||||||
- [ ] Boundary Cond / PMC Bound Face
|
- [x] Implement "simple" mode aka "periodic" mode in Tidy3D
|
||||||
- [ ] Boundary Cond / Bloch Bound Face
|
- [ ] 1D plot visualizing the effect of parameters on a 1D wave function
|
||||||
- [ ] Implement "simple" mode aka "periodic" mode in Tidy3D
|
- [x] Boundary Cond / Absorbing Bound Cond
|
||||||
- [ ] Boundary Cond / Absorbing Bound Face
|
- [ ] 1D plot visualizing the effect of parameters on a 1D wave function
|
||||||
|
|
||||||
## Monitors
|
## Monitors
|
||||||
- [x] EH Field Monitor
|
- [x] EH Field Monitor
|
||||||
|
|
|
@ -168,13 +168,13 @@ class GeoNodes(enum.StrEnum):
|
||||||
GN.StructurePrimitiveCapsule: GN_INTERNAL_STRUCTURES_PATH,
|
GN.StructurePrimitiveCapsule: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
GN.StructurePrimitiveCone: GN_INTERNAL_STRUCTURES_PATH,
|
GN.StructurePrimitiveCone: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
## Monitor
|
## Monitor
|
||||||
GN.MonitorEHField: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorPowerFlux: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorPowerFlux: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorEpsTensor: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorEpsTensor: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorDiffraction: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorDiffraction: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorProjCartEHField: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorProjCartEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorProjAngEHField: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorProjAngEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorProjKSpaceEHField: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorProjKSpaceEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
## Simulation
|
## Simulation
|
||||||
GN.SimulationSimDomain: GN_INTERNAL_SIMULATIONS_PATH,
|
GN.SimulationSimDomain: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
GN.SimulationBoundConds: GN_INTERNAL_SIMULATIONS_PATH,
|
GN.SimulationBoundConds: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
@ -225,7 +225,7 @@ def import_geonodes(
|
||||||
if import_method == 'link' and geonodes in bpy.data.node_groups:
|
if import_method == 'link' and geonodes in bpy.data.node_groups:
|
||||||
return bpy.data.node_groups[geonodes]
|
return bpy.data.node_groups[geonodes]
|
||||||
|
|
||||||
filename = geonodes
|
filename = str(geonodes)
|
||||||
filepath = str(geonodes.parent_path / (geonodes + '.blend') / 'NodeTree' / geonodes)
|
filepath = str(geonodes.parent_path / (geonodes + '.blend') / 'NodeTree' / geonodes)
|
||||||
directory = filepath.removesuffix(geonodes)
|
directory = filepath.removesuffix(geonodes)
|
||||||
log.info(
|
log.info(
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
from blender_maxwell.contracts import (
|
from blender_maxwell.contracts import (
|
||||||
BLClass,
|
BLClass,
|
||||||
BLColorRGBA,
|
BLColorRGBA,
|
||||||
BLEnumElement,
|
BLEnumElement,
|
||||||
BLEnumID,
|
BLEnumID,
|
||||||
BLIcon,
|
BLIcon,
|
||||||
BLIconSet,
|
BLIconSet,
|
||||||
BLIDStruct,
|
BLIDStruct,
|
||||||
BLKeymapItem,
|
BLKeymapItem,
|
||||||
BLModifierType,
|
BLModifierType,
|
||||||
BLNodeTreeInterfaceID,
|
BLNodeTreeInterfaceID,
|
||||||
BLOperatorStatus,
|
BLOperatorStatus,
|
||||||
BLPropFlag,
|
BLPropFlag,
|
||||||
BLRegionType,
|
BLRegionType,
|
||||||
BLSpaceType,
|
BLSpaceType,
|
||||||
KeymapItemDef,
|
KeymapItemDef,
|
||||||
ManagedObjName,
|
ManagedObjName,
|
||||||
OperatorType,
|
OperatorType,
|
||||||
PanelType,
|
PanelType,
|
||||||
PresetName,
|
PresetName,
|
||||||
SocketName,
|
SocketName,
|
||||||
addon,
|
addon,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .bl_socket_types import BLSocketInfo, BLSocketType
|
from .bl_socket_types import BLSocketInfo, BLSocketType
|
||||||
|
@ -27,19 +27,20 @@ from .category_labels import NODE_CAT_LABELS
|
||||||
from .category_types import NodeCategory
|
from .category_types import NodeCategory
|
||||||
from .flow_events import FlowEvent
|
from .flow_events import FlowEvent
|
||||||
from .flow_kinds import (
|
from .flow_kinds import (
|
||||||
ArrayFlow,
|
ArrayFlow,
|
||||||
CapabilitiesFlow,
|
CapabilitiesFlow,
|
||||||
FlowKind,
|
FlowKind,
|
||||||
InfoFlow,
|
InfoFlow,
|
||||||
LazyArrayRangeFlow,
|
LazyArrayRangeFlow,
|
||||||
LazyValueFuncFlow,
|
LazyValueFuncFlow,
|
||||||
ParamsFlow,
|
ParamsFlow,
|
||||||
ValueFlow,
|
ValueFlow,
|
||||||
)
|
)
|
||||||
from .flow_signals import FlowSignal
|
from .flow_signals import FlowSignal
|
||||||
from .icons import Icon
|
from .icons import Icon
|
||||||
from .mobj_types import ManagedObjType
|
from .mobj_types import ManagedObjType
|
||||||
from .node_types import NodeType
|
from .node_types import NodeType
|
||||||
|
from .sim_types import BoundCondType, SimSpaceAxis
|
||||||
from .socket_colors import SOCKET_COLORS
|
from .socket_colors import SOCKET_COLORS
|
||||||
from .socket_types import SocketType
|
from .socket_types import SocketType
|
||||||
from .tree_types import TreeType
|
from .tree_types import TreeType
|
||||||
|
@ -77,6 +78,8 @@ __all__ = [
|
||||||
'BLSocketInfo',
|
'BLSocketInfo',
|
||||||
'BLSocketType',
|
'BLSocketType',
|
||||||
'NodeType',
|
'NodeType',
|
||||||
|
'BoundCondType',
|
||||||
|
'SimSpaceAxis',
|
||||||
'NodeCategory',
|
'NodeCategory',
|
||||||
'NODE_CAT_LABELS',
|
'NODE_CAT_LABELS',
|
||||||
'ManagedObjType',
|
'ManagedObjType',
|
||||||
|
|
|
@ -25,58 +25,60 @@ class BLSocketInfo:
|
||||||
bl_isocket_identifier: spux.ScalarUnitlessRealExpr
|
bl_isocket_identifier: spux.ScalarUnitlessRealExpr
|
||||||
|
|
||||||
|
|
||||||
@blender_type_enum.prefix_values_with('NodeSocket')
|
|
||||||
class BLSocketType(enum.StrEnum):
|
class BLSocketType(enum.StrEnum):
|
||||||
Virtual = 'Virtual'
|
Virtual = 'NodeSocketVirtual'
|
||||||
# Blender
|
# Blender
|
||||||
Image = 'Image'
|
Image = 'NodeSocketImage'
|
||||||
Shader = 'Shader'
|
Shader = 'NodeSocketShader'
|
||||||
Material = 'Material'
|
Material = 'NodeSocketMaterial'
|
||||||
Geometry = 'Material'
|
Geometry = 'NodeSocketGeometry'
|
||||||
Object = 'Object'
|
Object = 'NodeSocketObject'
|
||||||
Collection = 'Collection'
|
Collection = 'NodeSocketCollection'
|
||||||
# Basic
|
# Basic
|
||||||
Bool = 'Bool'
|
Bool = 'NodeSocketBool'
|
||||||
String = 'String'
|
String = 'NodeSocketString'
|
||||||
Menu = 'Menu'
|
Menu = 'NodeSocketMenu'
|
||||||
# Float
|
# Float
|
||||||
Float = 'Float'
|
Float = 'NodeSocketFloat'
|
||||||
FloatUnsigned = 'FloatUnsigned'
|
FloatUnsigned = 'NodeSocketFloatUnsigned'
|
||||||
FloatAngle = 'FloatAngle'
|
FloatAngle = 'NodeSocketFloatAngle'
|
||||||
FloatDistance = 'FloatDistance'
|
FloatDistance = 'NodeSocketFloatDistance'
|
||||||
FloatFactor = 'FloatFactor'
|
FloatFactor = 'NodeSocketFloatFactor'
|
||||||
FloatPercentage = 'FloatPercentage'
|
FloatPercentage = 'NodeSocketFloatPercentage'
|
||||||
FloatTime = 'FloatTime'
|
FloatTime = 'NodeSocketFloatTime'
|
||||||
FloatTimeAbsolute = 'FloatTimeAbsolute'
|
FloatTimeAbsolute = 'NodeSocketFloatTimeAbsolute'
|
||||||
# Int
|
# Int
|
||||||
Int = 'Int'
|
Int = 'NodeSocketInt'
|
||||||
IntFactor = 'IntFactor'
|
IntFactor = 'NodeSocketIntFactor'
|
||||||
IntPercentage = 'IntPercentage'
|
IntPercentage = 'NodeSocketIntPercentage'
|
||||||
IntUnsigned = 'IntUnsigned'
|
IntUnsigned = 'NodeSocketIntUnsigned'
|
||||||
# Vector
|
# Vector
|
||||||
Color = 'Color'
|
Color = 'NodeSocketColor'
|
||||||
Rotation = 'Rotation'
|
Rotation = 'NodeSocketRotation'
|
||||||
Vector = 'Vector'
|
Vector = 'NodeSocketVector'
|
||||||
VectorAcceleration = 'Acceleration'
|
VectorAcceleration = 'NodeSocketAcceleration'
|
||||||
VectorDirection = 'Direction'
|
VectorDirection = 'NodeSocketDirection'
|
||||||
VectorEuler = 'Euler'
|
VectorEuler = 'NodeSocketEuler'
|
||||||
VectorTranslation = 'Translation'
|
VectorTranslation = 'NodeSocketTranslation'
|
||||||
VectorVelocity = 'Velocity'
|
VectorVelocity = 'NodeSocketVelocity'
|
||||||
VectorXYZ = 'XYZ'
|
VectorXYZ = 'NodeSocketXYZ'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_bl_isocket(
|
def from_bl_isocket(
|
||||||
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
||||||
) -> typ.Self:
|
) -> typ.Self:
|
||||||
return BLSocketType[bl_isocket.bl_socket_idname]
|
return BLSocketType(bl_isocket.bl_socket_idname)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def info_from_bl_isocket(
|
def info_from_bl_isocket(
|
||||||
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
||||||
) -> typ.Self:
|
) -> typ.Self:
|
||||||
return BLSocketType.from_bl_isocket(bl_isocket).parse(
|
bl_socket_type = BLSocketType.from_bl_isocket(bl_isocket)
|
||||||
bl_isocket.default_value, bl_isocket.description, bl_isocket.identifier
|
if bl_socket_type.has_support:
|
||||||
)
|
return bl_socket_type.parse(
|
||||||
|
bl_isocket.default_value, bl_isocket.description, bl_isocket.identifier
|
||||||
|
)
|
||||||
|
return bl_socket_type.parse(None, bl_isocket.description, bl_isocket.identifier)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Direct Properties
|
# - Direct Properties
|
||||||
|
@ -288,7 +290,7 @@ class BLSocketType(enum.StrEnum):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Parse the Default Value
|
# Parse the Default Value
|
||||||
if self.mathtype is not None:
|
if self.mathtype is not None and bl_default_value is not None:
|
||||||
if self.size == spux.NumberSize1D.Scalar:
|
if self.size == spux.NumberSize1D.Scalar:
|
||||||
default_value = self.mathtype.pytype(bl_default_value)
|
default_value = self.mathtype.pytype(bl_default_value)
|
||||||
elif description.startswith('2D'):
|
elif description.startswith('2D'):
|
||||||
|
|
|
@ -25,7 +25,7 @@ NODE_CAT_LABELS = {
|
||||||
NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: 'Primitives',
|
NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: 'Primitives',
|
||||||
# Bounds/
|
# Bounds/
|
||||||
NC.MAXWELLSIM_BOUNDS: 'Bounds',
|
NC.MAXWELLSIM_BOUNDS: 'Bounds',
|
||||||
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Bound Conds',
|
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Conds',
|
||||||
# Monitors/
|
# Monitors/
|
||||||
NC.MAXWELLSIM_MONITORS: 'Monitors',
|
NC.MAXWELLSIM_MONITORS: 'Monitors',
|
||||||
NC.MAXWELLSIM_MONITORS_PROJECTED: 'Projected',
|
NC.MAXWELLSIM_MONITORS_PROJECTED: 'Projected',
|
||||||
|
|
|
@ -13,9 +13,12 @@ import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from .socket_types import SocketType
|
from .socket_types import SocketType
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FlowKind(enum.StrEnum):
|
class FlowKind(enum.StrEnum):
|
||||||
"""Defines a kind of data that can flow between nodes.
|
"""Defines a kind of data that can flow between nodes.
|
||||||
|
@ -57,19 +60,22 @@ class FlowKind(enum.StrEnum):
|
||||||
Info = enum.auto()
|
Info = enum.auto()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def scale_to_unit_system(cls, kind: typ.Self, value, socket_type, unit_system):
|
def scale_to_unit_system(
|
||||||
|
cls,
|
||||||
|
kind: typ.Self,
|
||||||
|
value,
|
||||||
|
unit_system: spux.UnitSystem,
|
||||||
|
):
|
||||||
if kind == cls.Value:
|
if kind == cls.Value:
|
||||||
return spux.sympy_to_python(
|
return spux.scale_to_unit_system(
|
||||||
spux.scale_to_unit(
|
value,
|
||||||
value,
|
unit_system,
|
||||||
unit_system[socket_type],
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if kind == cls.LazyArrayRange:
|
if kind == cls.LazyArrayRange:
|
||||||
return value.rescale_to_unit(unit_system[socket_type])
|
return value.rescale_to_unit_system(unit_system)
|
||||||
|
|
||||||
if kind == cls.Params:
|
if kind == cls.Params:
|
||||||
return value.rescale_to_unit(unit_system[socket_type])
|
return value.rescale_to_unit_system(unit_system)
|
||||||
|
|
||||||
msg = 'Tried to scale unknown kind'
|
msg = 'Tried to scale unknown kind'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
@ -84,17 +90,28 @@ class CapabilitiesFlow:
|
||||||
active_kind: FlowKind
|
active_kind: FlowKind
|
||||||
|
|
||||||
is_universal: bool = False
|
is_universal: bool = False
|
||||||
|
|
||||||
|
# == Constraint
|
||||||
must_match: dict[str, typ.Any] = dataclasses.field(default_factory=dict)
|
must_match: dict[str, typ.Any] = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
|
# ∀b (b ∈ A) Constraint
|
||||||
|
## A: allow_any
|
||||||
|
## b∈B: present_any
|
||||||
|
allow_any: set[typ.Any] = dataclasses.field(default_factory=set)
|
||||||
|
present_any: set[typ.Any] = dataclasses.field(default_factory=set)
|
||||||
|
|
||||||
def is_compatible_with(self, other: typ.Self) -> bool:
|
def is_compatible_with(self, other: typ.Self) -> bool:
|
||||||
return other.is_universal or (
|
return other.is_universal or (
|
||||||
self.socket_type == other.socket_type
|
self.socket_type == other.socket_type
|
||||||
and self.active_kind == other.active_kind
|
and self.active_kind == other.active_kind
|
||||||
|
# == Constraint
|
||||||
and all(
|
and all(
|
||||||
name in other.must_match
|
name in other.must_match
|
||||||
and self.must_match[name] == other.must_match[name]
|
and self.must_match[name] == other.must_match[name]
|
||||||
for name in self.must_match
|
for name in self.must_match
|
||||||
)
|
)
|
||||||
|
# ∀b (b ∈ A) Constraint
|
||||||
|
and self.present_any.issubset(other.allow_any)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -187,6 +204,9 @@ class ArrayFlow:
|
||||||
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
def rescale_to_unit_system(self, unit: spu.Quantity) -> typ.Self:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Lazy Value Func
|
# - Lazy Value Func
|
||||||
|
@ -469,14 +489,13 @@ class LazyArrayRangeFlow:
|
||||||
|
|
||||||
# Get Stop Mathtype
|
# Get Stop Mathtype
|
||||||
if isinstance(self.stop, spux.SympyType):
|
if isinstance(self.stop, spux.SympyType):
|
||||||
stop_mathtype = spux.MathType.from_expr(type(self.stop))
|
stop_mathtype = spux.MathType.from_expr(self.stop)
|
||||||
else:
|
else:
|
||||||
stop_mathtype = spux.MathType.from_pytype(type(self.stop))
|
stop_mathtype = spux.MathType.from_pytype(self.stop)
|
||||||
|
|
||||||
# Check Equal
|
# Check Equal
|
||||||
if start_mathtype != stop_mathtype:
|
if start_mathtype != stop_mathtype:
|
||||||
msg = "Mathtypes of start and stop don't agree. Please fix!"
|
return spux.MathType.combine(start_mathtype, stop_mathtype)
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
return start_mathtype
|
return start_mathtype
|
||||||
|
|
||||||
|
@ -525,8 +544,8 @@ class LazyArrayRangeFlow:
|
||||||
"""
|
"""
|
||||||
if self.unit is not None:
|
if self.unit is not None:
|
||||||
return LazyArrayRangeFlow(
|
return LazyArrayRangeFlow(
|
||||||
start=spu.scale_to_unit(self.start * self.unit, unit),
|
start=spux.scale_to_unit(self.start * self.unit, unit),
|
||||||
stop=spu.scale_to_unit(self.stop * self.unit, unit),
|
stop=spux.scale_to_unit(self.stop * self.unit, unit),
|
||||||
steps=self.steps,
|
steps=self.steps,
|
||||||
scaling=self.scaling,
|
scaling=self.scaling,
|
||||||
unit=unit,
|
unit=unit,
|
||||||
|
@ -536,6 +555,39 @@ class LazyArrayRangeFlow:
|
||||||
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
def rescale_to_unit_system(self, unit_system: spux.Unit) -> typ.Self:
|
||||||
|
"""Replaces the units, **with** rescaling of the bounds.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
unit: The unit to convert the bounds to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new `LazyArrayRangeFlow` with replaced unit.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the existing unit is `None`, indicating that there is no unit to correct.
|
||||||
|
"""
|
||||||
|
if self.unit is not None:
|
||||||
|
return LazyArrayRangeFlow(
|
||||||
|
start=spux.strip_unit_system(
|
||||||
|
spux.convert_to_unit_system(self.start * self.unit, unit_system),
|
||||||
|
unit_system,
|
||||||
|
),
|
||||||
|
stop=spux.strip_unit_system(
|
||||||
|
spux.convert_to_unit_system(self.start * self.unit, unit_system),
|
||||||
|
unit_system,
|
||||||
|
),
|
||||||
|
steps=self.steps,
|
||||||
|
scaling=self.scaling,
|
||||||
|
unit=unit_system[spux.PhysicalType.from_unit(self.unit)],
|
||||||
|
symbols=self.symbols,
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = (
|
||||||
|
f'Tried to rescale unitless LazyDataValueRange to unit system {unit_system}'
|
||||||
|
)
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Bound Operations
|
# - Bound Operations
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -16,6 +16,7 @@ class FlowSignal(enum.StrEnum):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
FlowInitializing = enum.auto()
|
||||||
FlowPending = enum.auto()
|
FlowPending = enum.auto()
|
||||||
NoFlow = enum.auto()
|
NoFlow = enum.auto()
|
||||||
|
|
||||||
|
|
|
@ -90,10 +90,8 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
|
||||||
BoundConds = enum.auto()
|
BoundConds = enum.auto()
|
||||||
## Bounds / Bound Conds
|
## Bounds / Bound Conds
|
||||||
PMLBoundCond = enum.auto()
|
PMLBoundCond = enum.auto()
|
||||||
PECBoundCond = enum.auto()
|
|
||||||
PMCBoundCond = enum.auto()
|
|
||||||
BlochBoundCond = enum.auto()
|
BlochBoundCond = enum.auto()
|
||||||
AbsorbingBoundCond = enum.auto()
|
AdiabAbsorbBoundCond = enum.auto()
|
||||||
|
|
||||||
# Monitors
|
# Monitors
|
||||||
EHFieldMonitor = enum.auto()
|
EHFieldMonitor = enum.auto()
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
"""Declares various simulation types for use by nodes and sockets."""
|
||||||
|
|
||||||
|
import enum
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
|
||||||
|
## TODO: Sim Domain type, w/pydantic checks!
|
||||||
|
|
||||||
|
class SimSpaceAxis(enum.StrEnum):
|
||||||
|
"""The axis labels of the global simulation coordinate system."""
|
||||||
|
|
||||||
|
X = enum.auto()
|
||||||
|
Y = enum.auto()
|
||||||
|
Z = enum.auto()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_name(v: typ.Self) -> str:
|
||||||
|
"""Convert the enum value to a human-friendly name.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Used to print names in `EnumProperty`s based on this enum.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A human-friendly name corresponding to the enum value.
|
||||||
|
"""
|
||||||
|
SSA = SimSpaceAxis
|
||||||
|
return {
|
||||||
|
SSA.X: 'x',
|
||||||
|
SSA.Y: 'y',
|
||||||
|
SSA.Z: 'z',
|
||||||
|
}[v]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_icon(_: typ.Self) -> str:
|
||||||
|
"""Convert the enum value to a Blender icon.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Used to print icons in `EnumProperty`s based on this enum.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A human-friendly name corresponding to the enum value.
|
||||||
|
"""
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def axis(self) -> int:
|
||||||
|
"""Deduce the integer index of the axis.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The integer index of the axis.
|
||||||
|
"""
|
||||||
|
SSA = SimSpaceAxis
|
||||||
|
return {SSA.X: 0, SSA.Y: 1, SSA.Z: 2}[self]
|
||||||
|
|
||||||
|
|
||||||
|
class BoundCondType(enum.StrEnum):
|
||||||
|
r"""A type of boundary condition, applied to a half-axis of a simulation domain.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
Pml: "Perfectly Matched Layer" models infinite free space.
|
||||||
|
**Should be placed sufficiently far** (ex. $\frac{\lambda}{2}) from any active structures to mitigate divergence.
|
||||||
|
Periodic: Denotes Bloch-basedrepetition
|
||||||
|
Pec: "Perfect Electrical Conductor" models a surface that perfectly reflects electric fields.
|
||||||
|
Pmc: "Perfect Magnetic Conductor" models a surface that perfectly reflects the magnetic fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Pml = enum.auto()
|
||||||
|
Periodic = enum.auto()
|
||||||
|
Pec = enum.auto()
|
||||||
|
Pmc = enum.auto()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_name(v: typ.Self) -> str:
|
||||||
|
"""Convert the enum value to a human-friendly name.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Used to print names in `EnumProperty`s based on this enum.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A human-friendly name corresponding to the enum value.
|
||||||
|
"""
|
||||||
|
BCT = BoundCondType
|
||||||
|
return {
|
||||||
|
BCT.Pml: 'PML',
|
||||||
|
BCT.Pec: 'PEC',
|
||||||
|
BCT.Pmc: 'PMC',
|
||||||
|
BCT.Periodic: 'Periodic',
|
||||||
|
}[v]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_icon(_: typ.Self) -> str:
|
||||||
|
"""Convert the enum value to a Blender icon.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Used to print icons in `EnumProperty`s based on this enum.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A human-friendly name corresponding to the enum value.
|
||||||
|
"""
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tidy3d_boundary_edge(self) -> td.BoundaryEdge:
|
||||||
|
"""Convert the boundary condition specifier to a corresponding, sensible `tidy3d` boundary edge.
|
||||||
|
|
||||||
|
`td.BoundaryEdge` can be used to declare a half-axis in a `td.BoundarySpec`, which attaches directly to a simulation object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A sensible choice of `tidy3d` object representing the boundary condition.
|
||||||
|
"""
|
||||||
|
BCT = BoundCondType
|
||||||
|
return {
|
||||||
|
BCT.Pml: td.PML(),
|
||||||
|
BCT.Pec: td.PECBoundary(),
|
||||||
|
BCT.Pmc: td.PMCBoundary(),
|
||||||
|
BCT.Periodic: td.Periodic(),
|
||||||
|
}[self]
|
|
@ -110,26 +110,23 @@ class ManagedBLMesh(base.ManagedObj):
|
||||||
|
|
||||||
If it's already included, do nothing.
|
If it's already included, do nothing.
|
||||||
"""
|
"""
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
bl_object = bpy.data.objects.get(self.name)
|
||||||
if bl_object.name not in preview_collection().objects:
|
if bl_object is None:
|
||||||
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
bl_object = self.bl_object()
|
||||||
preview_collection().objects.link(bl_object)
|
|
||||||
else:
|
if bl_object.name not in preview_collection().objects:
|
||||||
msg = 'Managed BLMesh does not exist'
|
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
||||||
raise ValueError(msg)
|
preview_collection().objects.link(bl_object)
|
||||||
|
|
||||||
def hide_preview(self) -> None:
|
def hide_preview(self) -> None:
|
||||||
"""Removes the managed Blender object from the preview collection.
|
"""Removes the managed Blender object from the preview collection.
|
||||||
|
|
||||||
If it's already removed, do nothing.
|
If it's already removed, do nothing.
|
||||||
"""
|
"""
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
bl_object = bpy.data.objects.get(self.name)
|
||||||
if bl_object.name in preview_collection().objects:
|
if bl_object is not None and bl_object.name in preview_collection().objects:
|
||||||
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
||||||
preview_collection().objects.unlink(bl_object)
|
preview_collection().objects.unlink(bl_object)
|
||||||
else:
|
|
||||||
msg = 'Managed BLMesh does not exist'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
def bl_select(self) -> None:
|
def bl_select(self) -> None:
|
||||||
"""Selects the managed Blender object, causing it to be ex. outlined in the 3D viewport."""
|
"""Selects the managed Blender object, causing it to be ex. outlined in the 3D viewport."""
|
||||||
|
|
|
@ -206,6 +206,8 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
"""Unlock all nodes in the node tree, making them editable."""
|
"""Unlock all nodes in the node tree, making them editable."""
|
||||||
log.info('Unlocking All Nodes in NodeTree "%s"', self.bl_label)
|
log.info('Unlocking All Nodes in NodeTree "%s"', self.bl_label)
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
|
if node.type in ['REROUTE', 'FRAME']:
|
||||||
|
continue
|
||||||
node.locked = False
|
node.locked = False
|
||||||
for bl_socket in [*node.inputs, *node.outputs]:
|
for bl_socket in [*node.inputs, *node.outputs]:
|
||||||
bl_socket.locked = False
|
bl_socket.locked = False
|
||||||
|
@ -229,7 +231,9 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def repreview_all(self) -> None:
|
def repreview_all(self) -> None:
|
||||||
all_nodes_with_preview_active = {
|
all_nodes_with_preview_active = {
|
||||||
node.instance_id: node for node in self.nodes if node.preview_active
|
node.instance_id: node
|
||||||
|
for node in self.nodes
|
||||||
|
if node.type not in ['REROUTE', 'FRAME'] and node.preview_active
|
||||||
}
|
}
|
||||||
self.is_currently_repreviewing = True
|
self.is_currently_repreviewing = True
|
||||||
self.newly_previewed_nodes = {}
|
self.newly_previewed_nodes = {}
|
||||||
|
|
|
@ -1,40 +1,37 @@
|
||||||
# from . import kitchen_sink
|
|
||||||
# from . import bounds
|
|
||||||
from . import (
|
from . import (
|
||||||
analysis,
|
analysis,
|
||||||
|
bounds,
|
||||||
inputs,
|
inputs,
|
||||||
mediums,
|
# mediums,
|
||||||
monitors,
|
monitors,
|
||||||
outputs,
|
outputs,
|
||||||
simulations,
|
# simulations,
|
||||||
sources,
|
# sources,
|
||||||
structures,
|
# structures,
|
||||||
utilities,
|
# utilities,
|
||||||
)
|
)
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
# *kitchen_sink.BL_REGISTER,
|
|
||||||
*analysis.BL_REGISTER,
|
*analysis.BL_REGISTER,
|
||||||
*inputs.BL_REGISTER,
|
*inputs.BL_REGISTER,
|
||||||
*outputs.BL_REGISTER,
|
*outputs.BL_REGISTER,
|
||||||
*sources.BL_REGISTER,
|
# *sources.BL_REGISTER,
|
||||||
*mediums.BL_REGISTER,
|
# *mediums.BL_REGISTER,
|
||||||
*structures.BL_REGISTER,
|
# *structures.BL_REGISTER,
|
||||||
# *bounds.BL_REGISTER,
|
*bounds.BL_REGISTER,
|
||||||
*monitors.BL_REGISTER,
|
*monitors.BL_REGISTER,
|
||||||
*simulations.BL_REGISTER,
|
# *simulations.BL_REGISTER,
|
||||||
*utilities.BL_REGISTER,
|
# *utilities.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
# **kitchen_sink.BL_NODES,
|
|
||||||
**analysis.BL_NODES,
|
**analysis.BL_NODES,
|
||||||
**inputs.BL_NODES,
|
**inputs.BL_NODES,
|
||||||
**outputs.BL_NODES,
|
**outputs.BL_NODES,
|
||||||
**sources.BL_NODES,
|
# **sources.BL_NODES,
|
||||||
**mediums.BL_NODES,
|
# **mediums.BL_NODES,
|
||||||
**structures.BL_NODES,
|
# **structures.BL_NODES,
|
||||||
# **bounds.BL_NODES,
|
**bounds.BL_NODES,
|
||||||
**monitors.BL_NODES,
|
**monitors.BL_NODES,
|
||||||
**simulations.BL_NODES,
|
# **simulations.BL_NODES,
|
||||||
**utilities.BL_NODES,
|
# **utilities.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,8 +401,8 @@ class MapMathNode(base.MaxwellSimNode):
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
)
|
)
|
||||||
def on_input_changed(self):
|
def on_input_changed(self):
|
||||||
if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
# if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
||||||
self.operation = bl_cache.Signal.ResetEnumItems
|
self.operation = bl_cache.Signal.ResetEnumItems
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
# Trigger
|
# Trigger
|
||||||
|
|
|
@ -20,6 +20,10 @@ FUNCS = {
|
||||||
'MUL': lambda exprs: exprs[0] * exprs[1],
|
'MUL': lambda exprs: exprs[0] * exprs[1],
|
||||||
'DIV': lambda exprs: exprs[0] / exprs[1],
|
'DIV': lambda exprs: exprs[0] / exprs[1],
|
||||||
'POW': lambda exprs: exprs[0] ** exprs[1],
|
'POW': lambda exprs: exprs[0] ** exprs[1],
|
||||||
|
'ATAN2': lambda exprs: sp.atan2(exprs[1], exprs[0]),
|
||||||
|
# Vector | Vector
|
||||||
|
'VEC_VEC_DOT': lambda exprs: exprs[0].dot(exprs[1]),
|
||||||
|
'CROSS': lambda exprs: exprs[0].cross(exprs[1]),
|
||||||
}
|
}
|
||||||
|
|
||||||
SP_FUNCS = FUNCS
|
SP_FUNCS = FUNCS
|
||||||
|
@ -52,8 +56,8 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
bl_label = 'Operate Math'
|
bl_label = 'Operate Math'
|
||||||
|
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Expr L': sockets.ExprSocketDef(show_info_columns=False),
|
'Expr L': sockets.ExprSocketDef(),
|
||||||
'Expr R': sockets.ExprSocketDef(show_info_columns=False),
|
'Expr R': sockets.ExprSocketDef(),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(),
|
'Expr': sockets.ExprSocketDef(),
|
||||||
|
@ -73,10 +77,12 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
def search_categories(self) -> list[ct.BLEnumElement]:
|
def search_categories(self) -> list[ct.BLEnumElement]:
|
||||||
"""Deduce and return a list of valid categories for the current socket set and input data."""
|
"""Deduce and return a list of valid categories for the current socket set and input data."""
|
||||||
expr_l_info = self._compute_input(
|
expr_l_info = self._compute_input(
|
||||||
'Expr L', kind=ct.FlowKind.Info, optional=True
|
'Expr L',
|
||||||
|
kind=ct.FlowKind.Info,
|
||||||
)
|
)
|
||||||
expr_r_info = self._compute_input(
|
expr_r_info = self._compute_input(
|
||||||
'Expr R', kind=ct.FlowKind.Info, optional=True
|
'Expr R',
|
||||||
|
kind=ct.FlowKind.Info,
|
||||||
)
|
)
|
||||||
|
|
||||||
has_expr_l_info = not ct.FlowSignal.check(expr_l_info)
|
has_expr_l_info = not ct.FlowSignal.check(expr_l_info)
|
||||||
|
@ -121,6 +127,10 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
if expr_l_info.output_shape is None and expr_r_info.output_shape is None:
|
if expr_l_info.output_shape is None and expr_r_info.output_shape is None:
|
||||||
categories = [NUMBER_NUMBER]
|
categories = [NUMBER_NUMBER]
|
||||||
|
|
||||||
|
## * | Number
|
||||||
|
elif expr_r_info.output_shape is None:
|
||||||
|
categories = []
|
||||||
|
|
||||||
## Number | Vector
|
## Number | Vector
|
||||||
elif (
|
elif (
|
||||||
expr_l_info.output_shape is None and len(expr_r_info.output_shape) == 1
|
expr_l_info.output_shape is None and len(expr_r_info.output_shape) == 1
|
||||||
|
@ -170,13 +180,12 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
('POW', 'L^R', 'Power'),
|
('POW', 'L^R', 'Power'),
|
||||||
('ATAN2', 'atan2(L,R)', 'atan2(L,R)'),
|
('ATAN2', 'atan2(L,R)', 'atan2(L,R)'),
|
||||||
]
|
]
|
||||||
if self.category in 'Vector | Vector':
|
if self.category == 'Vector | Vector':
|
||||||
if items:
|
if items:
|
||||||
items += [None]
|
items += [None]
|
||||||
items += [
|
items += [
|
||||||
('VEC_VEC_DOT', 'L · R', 'Vector-Vector Product'),
|
('VEC_VEC_DOT', 'L · R', 'Vector-Vector Product'),
|
||||||
('CROSS', 'L x R', 'Cross Product'),
|
('CROSS', 'L x R', 'Cross Product'),
|
||||||
('PROJ', 'proj(L, R)', 'Projection'),
|
|
||||||
]
|
]
|
||||||
if self.category == 'Matrix | Vector':
|
if self.category == 'Matrix | Vector':
|
||||||
if items:
|
if items:
|
||||||
|
@ -364,9 +373,7 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
'Expr R': ct.FlowKind.Params,
|
'Expr R': ct.FlowKind.Params,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def compute_params(
|
def compute_params(self, props, input_sockets) -> ct.ParamsFlow | ct.FlowSignal:
|
||||||
self, props, input_sockets
|
|
||||||
) -> ct.ParamsFlow | ct.FlowSignal:
|
|
||||||
operation = props['operation']
|
operation = props['operation']
|
||||||
params_l = input_sockets['Expr L']
|
params_l = input_sockets['Expr L']
|
||||||
params_r = input_sockets['Expr R']
|
params_r = input_sockets['Expr R']
|
||||||
|
|
|
@ -2,8 +2,11 @@ import enum
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import jax
|
||||||
|
import jax.numpy as jnp
|
||||||
import jaxtyping as jtyp
|
import jaxtyping as jtyp
|
||||||
import matplotlib.axis as mpl_ax
|
import matplotlib.axis as mpl_ax
|
||||||
|
import sympy as sp
|
||||||
|
|
||||||
from blender_maxwell.utils import bl_cache, image_ops, logger
|
from blender_maxwell.utils import bl_cache, image_ops, logger
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
@ -192,7 +195,10 @@ class VizNode(base.MaxwellSimNode):
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(),
|
'Expr': sockets.ExprSocketDef(
|
||||||
|
symbols={_x := sp.Symbol('x', real=True)},
|
||||||
|
default_value=2 * _x,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Preview': sockets.AnySocketDef(),
|
'Preview': sockets.AnySocketDef(),
|
||||||
|
@ -221,8 +227,12 @@ class VizNode(base.MaxwellSimNode):
|
||||||
## - Mode Searcher
|
## - Mode Searcher
|
||||||
#####################
|
#####################
|
||||||
@property
|
@property
|
||||||
def data_info(self) -> ct.InfoFlow:
|
def data_info(self) -> ct.InfoFlow | None:
|
||||||
return self._compute_input('Expr', kind=ct.FlowKind.Info)
|
info = self._compute_input('Expr', kind=ct.FlowKind.Info)
|
||||||
|
if not ct.FlowSignal.check(info):
|
||||||
|
return info
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def search_modes(self) -> list[ct.BLEnumElement]:
|
def search_modes(self) -> list[ct.BLEnumElement]:
|
||||||
if not ct.FlowSignal.check(self.data_info):
|
if not ct.FlowSignal.check(self.data_info):
|
||||||
|
@ -298,7 +308,9 @@ class VizNode(base.MaxwellSimNode):
|
||||||
managed_objs={'plot'},
|
managed_objs={'plot'},
|
||||||
props={'viz_mode', 'viz_target', 'colormap'},
|
props={'viz_mode', 'viz_target', 'colormap'},
|
||||||
input_sockets={'Expr'},
|
input_sockets={'Expr'},
|
||||||
input_socket_kinds={'Expr': {ct.FlowKind.Array, ct.FlowKind.Info}},
|
input_socket_kinds={
|
||||||
|
'Expr': {ct.FlowKind.Array, ct.FlowKind.LazyValueFunc, ct.FlowKind.Info}
|
||||||
|
},
|
||||||
stop_propagation=True,
|
stop_propagation=True,
|
||||||
)
|
)
|
||||||
def on_show_plot(
|
def on_show_plot(
|
||||||
|
|
|
@ -599,22 +599,28 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
It must be currently active.
|
It must be currently active.
|
||||||
kind: The data flow kind to compute.
|
kind: The data flow kind to compute.
|
||||||
"""
|
"""
|
||||||
if (bl_socket := self.inputs.get(input_socket_name)) is not None:
|
bl_socket = self.inputs.get(input_socket_name)
|
||||||
return (
|
if bl_socket is not None:
|
||||||
ct.FlowKind.scale_to_unit_system(
|
if bl_socket.instance_id:
|
||||||
kind,
|
return (
|
||||||
bl_socket.compute_data(kind=kind),
|
ct.FlowKind.scale_to_unit_system(
|
||||||
bl_socket.socket_type,
|
kind,
|
||||||
unit_system,
|
bl_socket.compute_data(kind=kind),
|
||||||
|
unit_system,
|
||||||
|
)
|
||||||
|
if unit_system is not None
|
||||||
|
else bl_socket.compute_data(kind=kind)
|
||||||
)
|
)
|
||||||
if unit_system is not None
|
|
||||||
else bl_socket.compute_data(kind=kind)
|
# No Socket Instance ID
|
||||||
)
|
## -> Indicates that socket_def.preinit() has not yet run.
|
||||||
|
## -> Anyone needing results will need to wait on preinit().
|
||||||
|
return ct.FlowSignal.FlowInitializing
|
||||||
|
|
||||||
if optional:
|
if optional:
|
||||||
return ct.FlowSignal.NoFlow
|
return ct.FlowSignal.NoFlow
|
||||||
|
|
||||||
msg = f'Input socket "{input_socket_name}" on "{self.bl_idname}" is not an active input socket'
|
msg = f'{self.sim_node_name}: Input socket "{input_socket_name}" cannot be computed, as it is not an active input socket'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from . import bound_box, bound_faces
|
from . import bound_cond_nodes, bound_conds
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*bound_box.BL_REGISTER,
|
*bound_conds.BL_REGISTER,
|
||||||
*bound_faces.BL_REGISTER,
|
*bound_cond_nodes.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**bound_box.BL_NODES,
|
**bound_conds.BL_NODES,
|
||||||
**bound_faces.BL_NODES,
|
**bound_cond_nodes.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
import tidy3d as td
|
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from ... import sockets
|
|
||||||
from .. import base, events
|
|
||||||
|
|
||||||
|
|
||||||
class BoundCondsNode(base.MaxwellSimNode):
|
|
||||||
node_type = ct.NodeType.BoundConds
|
|
||||||
bl_label = 'Bound Box'
|
|
||||||
# bl_icon = ...
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Sockets
|
|
||||||
####################
|
|
||||||
input_sockets = {
|
|
||||||
'+X': sockets.MaxwellBoundCondSocketDef(),
|
|
||||||
'-X': sockets.MaxwellBoundCondSocketDef(),
|
|
||||||
'+Y': sockets.MaxwellBoundCondSocketDef(),
|
|
||||||
'-Y': sockets.MaxwellBoundCondSocketDef(),
|
|
||||||
'+Z': sockets.MaxwellBoundCondSocketDef(),
|
|
||||||
'-Z': sockets.MaxwellBoundCondSocketDef(),
|
|
||||||
}
|
|
||||||
output_sockets = {
|
|
||||||
'BCs': sockets.MaxwellBoundCondsSocketDef(),
|
|
||||||
}
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Output Socket Computation
|
|
||||||
####################
|
|
||||||
@events.computes_output_socket(
|
|
||||||
'BCs', input_sockets={'+X', '-X', '+Y', '-Y', '+Z', '-Z'}
|
|
||||||
)
|
|
||||||
def compute_simulation(self, input_sockets) -> td.BoundarySpec:
|
|
||||||
x_pos = input_sockets['+X']
|
|
||||||
x_neg = input_sockets['-X']
|
|
||||||
y_pos = input_sockets['+Y']
|
|
||||||
y_neg = input_sockets['-Y']
|
|
||||||
z_pos = input_sockets['+Z']
|
|
||||||
z_neg = input_sockets['-Z']
|
|
||||||
|
|
||||||
return td.BoundarySpec(
|
|
||||||
x=td.Boundary(
|
|
||||||
plus=x_pos,
|
|
||||||
minus=x_neg,
|
|
||||||
),
|
|
||||||
y=td.Boundary(
|
|
||||||
plus=y_pos,
|
|
||||||
minus=y_neg,
|
|
||||||
),
|
|
||||||
z=td.Boundary(
|
|
||||||
plus=z_pos,
|
|
||||||
minus=z_neg,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
BoundCondsNode,
|
|
||||||
]
|
|
||||||
BL_NODES = {ct.NodeType.BoundConds: (ct.NodeCategory.MAXWELLSIM_BOUNDS)}
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
from . import (
|
||||||
|
absorbing_bound_cond,
|
||||||
|
bloch_bound_cond,
|
||||||
|
pml_bound_cond,
|
||||||
|
)
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*pml_bound_cond.BL_REGISTER,
|
||||||
|
*bloch_bound_cond.BL_REGISTER,
|
||||||
|
*absorbing_bound_cond.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**pml_bound_cond.BL_NODES,
|
||||||
|
**bloch_bound_cond.BL_NODES,
|
||||||
|
**absorbing_bound_cond.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
"""Implements `AdiabAbsorbBoundCondNode`."""
|
||||||
|
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
|
from .... import contracts as ct
|
||||||
|
from .... import sockets
|
||||||
|
from ... import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AdiabAbsorbBoundCondNode(base.MaxwellSimNode):
|
||||||
|
r"""A boundary condition that generically (adiabatically) absorbs outgoing energy, by gradually ramping up the strength of the conductor over many layers, until a final PEC layer.
|
||||||
|
|
||||||
|
Compared to PML, this boundary is more computationally expensive, and may result in higher reflectivity (and thus lower accuracy).
|
||||||
|
The general reason to use it is **to fix divergence in cases where dispersive materials intersect the simulation boundary**.
|
||||||
|
|
||||||
|
For more theoretical details, please refer to the `tidy3d` resource: <https://docs.flexcompute.com/projects/tidy3d/en/latest/api/_autosummary/tidy3d.Absorber.html>
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
**Ensure** that all simulation structures are $\approx \frac{\lambda}{2}$ from any PML boundary.
|
||||||
|
|
||||||
|
This helps avoid the amplification of stray evanescent waves.
|
||||||
|
|
||||||
|
Socket Sets:
|
||||||
|
Simple: Only specify the number of absorption layers.
|
||||||
|
$40$ should generally be sufficient, but in the case of divergence issues, bumping up the number of layers should be the go-to remedy to try.
|
||||||
|
Full: Specify the conductivity min/max that makes up the absorption up the PML, as well as the order of the polynomial used to scale the effect through the layers.
|
||||||
|
You should probably leave this alone.
|
||||||
|
Since the value units are sim-relative, we've opted to show the scaling information in the node's UI, instead of coercing the values into any particular unit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
node_type = ct.NodeType.AdiabAbsorbBoundCond
|
||||||
|
bl_label = 'Absorber Bound Cond'
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_sockets: typ.ClassVar = {
|
||||||
|
'Layers': sockets.ExprSocketDef(
|
||||||
|
shape=None,
|
||||||
|
mathtype=spux.MathType.Integer,
|
||||||
|
abs_min=1,
|
||||||
|
default_value=40,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
input_socket_sets: typ.ClassVar = {
|
||||||
|
'Simple': {},
|
||||||
|
'Full': {
|
||||||
|
'σ Order': sockets.ExprSocketDef(
|
||||||
|
shape=None,
|
||||||
|
mathtype=spux.MathType.Integer,
|
||||||
|
abs_min=1,
|
||||||
|
default_value=3,
|
||||||
|
),
|
||||||
|
'σ Range': sockets.ExprSocketDef(
|
||||||
|
shape=(2,),
|
||||||
|
mathtype=spux.MathType.Real,
|
||||||
|
default_value=sp.Matrix([0, 1.5]),
|
||||||
|
abs_min=0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'BC': sockets.MaxwellBoundCondSocketDef(),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
|
if self.active_socket_set == 'Full':
|
||||||
|
box = layout.box()
|
||||||
|
row = box.row()
|
||||||
|
row.alignment = 'CENTER'
|
||||||
|
row.label(text='Parameter Scale')
|
||||||
|
|
||||||
|
# Split
|
||||||
|
split = box.split(factor=0.4, align=False)
|
||||||
|
|
||||||
|
## LHS: Parameter Names
|
||||||
|
col = split.column()
|
||||||
|
col.alignment = 'RIGHT'
|
||||||
|
col.label(text='σ:')
|
||||||
|
|
||||||
|
## RHS: Parameter Units
|
||||||
|
col = split.column()
|
||||||
|
col.label(text='2ε₀/Δt')
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'BC',
|
||||||
|
props={'active_socket_set'},
|
||||||
|
input_sockets={
|
||||||
|
'Layers',
|
||||||
|
'σ Order',
|
||||||
|
'σ Range',
|
||||||
|
},
|
||||||
|
input_sockets_optional={
|
||||||
|
'σ Order': True,
|
||||||
|
'σ Range': True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def compute_adiab_absorber_bound_cond(self, props, input_sockets) -> td.Absorber:
|
||||||
|
r"""Computes the adiabatic absorber boundary condition based on the active socket set.
|
||||||
|
|
||||||
|
- **Simple**: Use `tidy3d`'s default parameters for defining the absorber parameters (apart from number of layers).
|
||||||
|
- **Full**: Use the user-defined $\sigma$ parameters, specifically polynomial order and sim-relative min/max conductivity values.
|
||||||
|
"""
|
||||||
|
log.debug(
|
||||||
|
'%s: Computing "%s" Adiabatic Absorber Boundary Condition (Input Sockets = %s)',
|
||||||
|
self.sim_node_name,
|
||||||
|
props['active_socket_set'],
|
||||||
|
input_sockets,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Simple PML
|
||||||
|
if props['active_socket_set'] == 'Simple':
|
||||||
|
return td.Absorber(num_layers=input_sockets['Layers'])
|
||||||
|
|
||||||
|
# Full PML
|
||||||
|
return td.Absorber(
|
||||||
|
num_layers=input_sockets['Layers'],
|
||||||
|
parameters=td.AbsorberParams(
|
||||||
|
sigma_order=input_sockets['σ Order'],
|
||||||
|
sigma_min=input_sockets['σ Range'][0],
|
||||||
|
sigma_max=input_sockets['σ Range'][1],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
AdiabAbsorbBoundCondNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
ct.NodeType.AdiabAbsorbBoundCond: (ct.NodeCategory.MAXWELLSIM_BOUNDS)
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
"""Implements `BlochBoundCondNode`."""
|
||||||
|
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
|
|
||||||
|
from .... import contracts as ct
|
||||||
|
from .... import sockets
|
||||||
|
from ... import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BlochBoundCondNode(base.MaxwellSimNode):
|
||||||
|
r"""A boundary condition that declares an "infinitely repeating" window, by applying Bloch's theorem to accurately describe how a boundary would behave if it were interacting with an infinitely repeating simulation structure.
|
||||||
|
|
||||||
|
# Theory
|
||||||
|
In the simplest case, aka. a normal-incident plane wave, the symmetries of electromagnetic wave propagation behave exactly as expected: Copy-paste the wavevector, but at opposite corners, as part of the FDTD neighbor-cell-update.
|
||||||
|
The moment this plane wave becomes angled, however, this "naive" method will cause **the phase of the periodically propagated fields to diverge from reality**.
|
||||||
|
|
||||||
|
With a bit of hand-waving, this is a natural thing: Fundamentally, the distance from each point on an angled plane wave to the boundary must vary, and if the phase is distance-dependent, then the phase must vary across the boundary.
|
||||||
|
|
||||||
|
Unfortunately, all of the explicitly-defined ways of describing how exactly to correct for this phenomenon depend on not only on what is being simulated, but on what is being studied.
|
||||||
|
The good news is, there are options.
|
||||||
|
|
||||||
|
## A Bloch of a Thing
|
||||||
|
A physicist named Felix Bloch came up with a theorem to help constrain how "wave-like things in periodic stuff" can be thought about, and it looks like
|
||||||
|
|
||||||
|
$$
|
||||||
|
\psi(\mathbf{r}) = u(\mathbf{r}) \cdot \exp_{\mathbb{C}}(\mathbf{k} \cdot \mathbf{r})
|
||||||
|
$$
|
||||||
|
|
||||||
|
for:
|
||||||
|
|
||||||
|
- $\psi$: A wave function (in general, satisfying the Schrödinger equation, but in this context, satisfying Maxwell's equations)
|
||||||
|
- $\mathbf{r}$: A position in 3D space.
|
||||||
|
- $\u$: Some periodic function mapping 3D space to a value. In this context, this might be a 3D function representing our simulation structures.
|
||||||
|
- $\mathbf{k}$: The "Bloch vector", of which there is guaranteed to be at least one, **but of which there may be many**.
|
||||||
|
|
||||||
|
At this point, it becomes interesting to note that pretty much _everything_ is, in fact, a "wave-like thing", so long as "the thing" is small enough.
|
||||||
|
Many such "periodically structured things", which form entire fields of study, can indeed be modelled using this single function:
|
||||||
|
|
||||||
|
- **Photonic Crystals**: The optical properties of many materials can be quite concisely encapsulated by placing regularly placed structures (of sub-wavelength size) within lattice-like structures.
|
||||||
|
- **Phononic Crystals**: A class of metamaterial that can be parameterized and optimized for its acoustic properties, purely by analyzing its periodic behavior, with applications ranging from interesting acoustic devices to seismic modelling.
|
||||||
|
|
||||||
|
## Modes of an Excited Nature
|
||||||
|
For a choice of $u$ (representing the simulation structure), there may be _many continuous_ choices of $\mathbf{k}$ that satisfy the Bloch theorem.
|
||||||
|
Similarly, for a particular choice of $\mathbf{k}$, there may be _several discrete_ particular solutions of the given wave function.
|
||||||
|
|
||||||
|
Thus, we come full circle: **Fully encapsulating** the wave-interactions of a periodic structure requires knowing its behavior at **all valid wave vectors**.
|
||||||
|
It is a sort of deeper truth, that any particular simulation of a unit cell cannot elicit the full story of _how a structure behaves_, since a particular choice of $\mathbf{k}$ must always inevitably be made as part of defining the simulation.
|
||||||
|
|
||||||
|
## Designing Periodically
|
||||||
|
With this insight in mind, we can now design simulations of periodic structures that properly account for the modalities imposed by particular $\mathbf{k}$ choices:
|
||||||
|
|
||||||
|
- **Only Rely on Real Fields**: If only the real parts of the fields are important, then the choice of $\mathbf{k}$ might not matter.
|
||||||
|
Remember, the symptom of needing to understand $\mathbf{k}$ is the phase-shift; if the phase-shift does not matter, then altering the Bloch vector won't change anything.
|
||||||
|
**Be careful**, though, and make sure to validate that the Bloch vector truly doesn't change anything.
|
||||||
|
- **Normal-Injected Plane Waves**: If fields generally only propagate in the normal direction, then again, choices of $\mathbf{k}$ might not matter.
|
||||||
|
Again, phase-shifting due to periodic behavior mainly happens when propagation occurs at grazing angles.
|
||||||
|
Again, **be careful**, and make sure to validate that ex. the Poynting vector truly isn't hitting the boundaries at too-grazing angles.
|
||||||
|
- **Angularly Injected Plane Waves**: If the injected plane wave is known, then we can directly compute a reasonable Bloch vector from the angle and boundary-axis-projected size of the plane wave source.
|
||||||
|
This selection of $\mathbf{k}$
|
||||||
|
- **Brute-Force Bloch-Vector Sweep**: If the nature of a periodic structure needs to be uncovered, and there's no special constraints to rely on, then it would be rightfully tempting to just sweep over all $\mathbf{k}$s, and run a complete simluation for each.
|
||||||
|
By going a step further, and plotting the energy of resonance frequencies noticed at each wave vector (just place point dipoles at random), one might stumble into a "band diagram" describing the possible energy states of electrons at each wave vector.
|
||||||
|
|
||||||
|
In general, these form a very sensible starting point for how to select Bloch vectors for productive use in the simulation.
|
||||||
|
|
||||||
|
NOTE: The Bloch vector is generally represented not as a vector, but as a single phase-shift per boundary axis unit length, mainly for convenience.
|
||||||
|
|
||||||
|
## Further Reading
|
||||||
|
- <https://optics.ansys.com/hc/en-us/articles/360041566614-Rectangular-Photonic-Crystal-Bandstructure>
|
||||||
|
- <https://docs.flexcompute.com/projects/tidy3d/en/v2.1.0/notebooks/Bandstructure.html>
|
||||||
|
- <https://en.wikipedia.org/wiki/Electronic_band_structure>
|
||||||
|
- <https://en.wikipedia.org/wiki/Brillouin_zone>
|
||||||
|
- <https://en.wikipedia.org/wiki/Bloch%27s_theorem>
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
In the naive case, it is presumed that the choice of Bloch vector doesn't matter; therefore it is set to 0.
|
||||||
|
|
||||||
|
Socket Sets:
|
||||||
|
Naive: Specify a Bloch boundary condition where phase shift doesn't matter, and is thus set such that no phase-shift occurs.
|
||||||
|
This is the simplest (and cheapest) mechanism, which merely copy-pastes propagating waves at opposing sides of the simulation.
|
||||||
|
However, **this should not be used for angled plane waves**, as the phase-shift of a propagating angled plane wave **will be wrong**.
|
||||||
|
Source-Derived: Derive a Bloch vector that will be generally correct for a directed source, within a particular choice of axis on a particular simulation domain.
|
||||||
|
**Phase shift correctness is only guaranteed valid for the central frequency of the source**.
|
||||||
|
Thus, a narrow-band source is strongly recommended.
|
||||||
|
Bloch Vector: Specify a true Bloch boundary condition, including the **phase shift per unit length** (aka. the magnitude of the Bloch vector).
|
||||||
|
While the most flexible, **the appropriate choice for this value source of this value depends entirely on what is being simulated**.
|
||||||
|
"""
|
||||||
|
|
||||||
|
node_type = ct.NodeType.BlochBoundCond
|
||||||
|
bl_label = 'Bloch Bound Cond'
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_socket_sets: typ.ClassVar = {
|
||||||
|
'Naive': {},
|
||||||
|
'Source-Derived': {
|
||||||
|
'Angled Source': sockets.MaxwellSourceSocketDef(),
|
||||||
|
## TODO: Constrain to gaussian beam, plane wafe, and tfsf
|
||||||
|
'Sim Domain': sockets.MaxwellSimDomainSocketDef(),
|
||||||
|
},
|
||||||
|
'Manual': {
|
||||||
|
'Bloch Vector': sockets.ExprSocketDef(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'BC': sockets.MaxwellBoundCondSocketDef(),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
valid_sim_axis: ct.SimSpaceAxis = bl_cache.BLField(ct.SimSpaceAxis.X, prop_ui=True)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
|
if self.active_socket_set == 'Source-Derived':
|
||||||
|
layout.prop(self, self.blfields['valid_sim_axis'], expand=True)
|
||||||
|
|
||||||
|
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
|
if self.active_socket_set == 'Manual':
|
||||||
|
box = layout.box()
|
||||||
|
row = box.row()
|
||||||
|
row.alignment = 'CENTER'
|
||||||
|
row.label(text='Interpretation')
|
||||||
|
|
||||||
|
# Split
|
||||||
|
split = box.split(factor=0.6, align=False)
|
||||||
|
|
||||||
|
## LHS: Parameter Names
|
||||||
|
col = split.column()
|
||||||
|
col.alignment = 'RIGHT'
|
||||||
|
col.label(text='Bloch Vec:')
|
||||||
|
|
||||||
|
## RHS: Parameter Units
|
||||||
|
col = split.column()
|
||||||
|
col.label(text='2π/Δℓ')
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Events
|
||||||
|
####################
|
||||||
|
@events.on_value_changed(
|
||||||
|
prop_name={'active_socket_set', 'valid_sim_axis'},
|
||||||
|
run_on_init=True,
|
||||||
|
props={'active_socket_set', 'valid_sim_axis'},
|
||||||
|
)
|
||||||
|
def on_valid_sim_axis_changed(self, props):
|
||||||
|
"""For the source-derived socket set, synchronized the output socket's axis compatibility with the axis onto which the Bloch vector is computed.
|
||||||
|
|
||||||
|
The net result should be that invalid use of the Bloch boundary condition in a particular axis should be rejected.
|
||||||
|
|
||||||
|
- **Source-Derived**: Since the Bloch vector is computed between the source and the axis that this boundary is applied to, the output socket must be altered to **only** declare compatibility with that axis.
|
||||||
|
- **`*`**: Normalize the output socket axis validity to ensure that the boundary condition can be applied to any axis.
|
||||||
|
"""
|
||||||
|
if props['active_socket_set'] == 'Source-Derived':
|
||||||
|
self.outputs['BC'].present_axes = {props['valid_sim_axis']}
|
||||||
|
self.outputs['BC'].remove_invalidated_links()
|
||||||
|
else:
|
||||||
|
self.outputs['BC'].present_axes = {
|
||||||
|
ct.SimSpaceAxis.X,
|
||||||
|
ct.SimSpaceAxis.Y,
|
||||||
|
ct.SimSpaceAxis.Z,
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'BC',
|
||||||
|
props={'active_socket_set', 'valid_sim_axis'},
|
||||||
|
input_sockets={
|
||||||
|
'Angled Source',
|
||||||
|
'Sim Domain',
|
||||||
|
'Bloch Vector',
|
||||||
|
},
|
||||||
|
input_sockets_optional={
|
||||||
|
'Angled Source': True,
|
||||||
|
'Sim Domain': True,
|
||||||
|
'Bloch Vector': True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def compute_bloch_bound_cond(
|
||||||
|
self, props, input_sockets
|
||||||
|
) -> td.Periodic | td.BlochBoundary:
|
||||||
|
r"""Computes the Bloch boundary condition.
|
||||||
|
|
||||||
|
- **Naive**: Set the Bloch vector to 0 by returning a `td.Periodic`.
|
||||||
|
- **Source-Derived**: Derive the Bloch vector from the source, simulation domain, and choice of axis.
|
||||||
|
The Bloch boundary axis **must** be orthogonal to the source's injection axis.
|
||||||
|
- **Manual**: Set the Bloch vector to the user-specified value.
|
||||||
|
"""
|
||||||
|
log.debug(
|
||||||
|
'%s: Computing Bloch Boundary Condition (Socket Set = %s)',
|
||||||
|
self.sim_node_name,
|
||||||
|
props['active_socket_set'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Naive
|
||||||
|
if props['active_socket_set'] == 'Naive':
|
||||||
|
return td.Periodic()
|
||||||
|
|
||||||
|
# Source-Derived
|
||||||
|
if props['active_socket_set'] == 'Naive':
|
||||||
|
sim_domain = input_sockets['Sim Domain']
|
||||||
|
valid_sim_axis = props['valid_sim_axis']
|
||||||
|
|
||||||
|
has_sim_domain = not ct.FlowSignal.check(sim_domain)
|
||||||
|
|
||||||
|
if has_sim_domain:
|
||||||
|
return td.BlochBoundary.from_source(
|
||||||
|
source=input_sockets['Angled Source'],
|
||||||
|
domain_size=sim_domain['size'][valid_sim_axis.axis],
|
||||||
|
axis=valid_sim_axis.axis,
|
||||||
|
medium=sim_domain['medium'],
|
||||||
|
)
|
||||||
|
return ct.FlowSignal.FlowPending
|
||||||
|
|
||||||
|
# Manual
|
||||||
|
return td.BlochBoundary(bloch_vec=input_sockets['Bloch Vector'])
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
BlochBoundCondNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {ct.NodeType.BlochBoundCond: (ct.NodeCategory.MAXWELLSIM_BOUNDS)}
|
|
@ -0,0 +1,190 @@
|
||||||
|
"""Implements `PMLBoundCondNode`."""
|
||||||
|
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
|
from .... import contracts as ct
|
||||||
|
from .... import sockets
|
||||||
|
from ... import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PMLBoundCondNode(base.MaxwellSimNode):
|
||||||
|
r"""A "Perfectly Matched Layer" boundary condition, which is a theoretical medium that attempts to _perfectly_ absorb all outgoing waves, so as to represent "infinite space" in FDTD simulations.
|
||||||
|
|
||||||
|
PML boundary conditions do so by inducing a **frequency-dependent attenuation** on all waves that are outside of the boundary, over the course of several layers.
|
||||||
|
|
||||||
|
It is critical to note that a PML boundary can only absorb **propagating** waves.
|
||||||
|
_Evanescent_ waves oscillating w/o any power flux, ex. close to structures, may actually be **amplified** by a PML boundary.
|
||||||
|
This is the reasoning behind the $\frac{\lambda}{2}$-distance rule of thumb.
|
||||||
|
|
||||||
|
For more theoretical details, please refer to the `tidy3d` resource: <https://docs.flexcompute.com/projects/tidy3d/en/latest/api/_autosummary/tidy3d.PML.html>
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
**Ensure** that all simulation structures are $\approx \frac{\lambda}{2}$ from any PML boundary.
|
||||||
|
|
||||||
|
This helps avoid the amplification of stray evanescent waves.
|
||||||
|
|
||||||
|
Socket Sets:
|
||||||
|
Simple: Only specify the number of PML layers.
|
||||||
|
$12$ should cover the most common cases; $40$ should be extremely stable.
|
||||||
|
Full: Specify the conductivity min/max that make up the PML, as well as the order of approximating polynomials.
|
||||||
|
The meaning of the parameters are rooted in the mathematics that underlie the PML function - if that doesn't mean anything to you, then you should probably leave it alone!
|
||||||
|
Since the value units are sim-relative, we've opted to show the scaling information in the node's UI, instead of coercing the values into any particular unit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
node_type = ct.NodeType.PMLBoundCond
|
||||||
|
bl_label = 'PML Bound Cond'
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_sockets: typ.ClassVar = {
|
||||||
|
'Layers': sockets.ExprSocketDef(
|
||||||
|
shape=None,
|
||||||
|
mathtype=spux.MathType.Integer,
|
||||||
|
abs_min=1,
|
||||||
|
default_value=12,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
input_socket_sets: typ.ClassVar = {
|
||||||
|
'Simple': {},
|
||||||
|
'Full': {
|
||||||
|
'σ Order': sockets.ExprSocketDef(
|
||||||
|
shape=None,
|
||||||
|
mathtype=spux.MathType.Integer,
|
||||||
|
abs_min=1,
|
||||||
|
default_value=3,
|
||||||
|
),
|
||||||
|
'σ Range': sockets.ExprSocketDef(
|
||||||
|
shape=(2,),
|
||||||
|
mathtype=spux.MathType.Real,
|
||||||
|
default_value=sp.Matrix([0, 1.5]),
|
||||||
|
abs_min=0,
|
||||||
|
),
|
||||||
|
'κ Order': sockets.ExprSocketDef(
|
||||||
|
shape=None,
|
||||||
|
mathtype=spux.MathType.Integer,
|
||||||
|
abs_min=1,
|
||||||
|
default_value=3,
|
||||||
|
),
|
||||||
|
'κ Range': sockets.ExprSocketDef(
|
||||||
|
shape=(2,),
|
||||||
|
mathtype=spux.MathType.Real,
|
||||||
|
default_value=sp.Matrix([0, 1.5]),
|
||||||
|
abs_min=0,
|
||||||
|
),
|
||||||
|
'α Order': sockets.ExprSocketDef(
|
||||||
|
shape=None,
|
||||||
|
mathtype=spux.MathType.Integer,
|
||||||
|
abs_min=1,
|
||||||
|
default_value=3,
|
||||||
|
),
|
||||||
|
'α Range': sockets.ExprSocketDef(
|
||||||
|
shape=(2,),
|
||||||
|
mathtype=spux.MathType.Real,
|
||||||
|
default_value=sp.Matrix([0, 1.5]),
|
||||||
|
abs_min=0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'BC': sockets.MaxwellBoundCondSocketDef(),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
|
if self.active_socket_set == 'Full':
|
||||||
|
box = layout.box()
|
||||||
|
row = box.row()
|
||||||
|
row.alignment = 'CENTER'
|
||||||
|
row.label(text='Parameter Scale')
|
||||||
|
|
||||||
|
# Split
|
||||||
|
split = box.split(factor=0.4, align=False)
|
||||||
|
|
||||||
|
## LHS: Parameter Names
|
||||||
|
col = split.column()
|
||||||
|
col.alignment = 'RIGHT'
|
||||||
|
for param in ['σ', 'κ', 'α']:
|
||||||
|
col.label(text=param + ':')
|
||||||
|
|
||||||
|
## RHS: Parameter Units
|
||||||
|
col = split.column()
|
||||||
|
for _ in range(3):
|
||||||
|
col.label(text='2ε₀/Δt')
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'BC',
|
||||||
|
props={'active_socket_set'},
|
||||||
|
input_sockets={
|
||||||
|
'Layers',
|
||||||
|
'σ Order',
|
||||||
|
'σ Range',
|
||||||
|
'κ Order',
|
||||||
|
'κ Range',
|
||||||
|
'α Order',
|
||||||
|
'α Range',
|
||||||
|
},
|
||||||
|
input_sockets_optional={
|
||||||
|
'σ Order': True,
|
||||||
|
'σ Range': True,
|
||||||
|
'κ Order': True,
|
||||||
|
'κ Range': True,
|
||||||
|
'α Order': True,
|
||||||
|
'α Range': True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def compute_pml_boundary_cond(self, props, input_sockets) -> td.PML:
|
||||||
|
r"""Computes the PML boundary condition based on the active socket set.
|
||||||
|
|
||||||
|
- **Simple**: Use `tidy3d`'s default parameters for defining the PML conductor (apart from number of layers).
|
||||||
|
- **Full**: Use the user-defined $\sigma$, $\kappa$, and $\alpha$ parameters, specifically polynomial order and sim-relative min/max conductivity values.
|
||||||
|
"""
|
||||||
|
log.debug(
|
||||||
|
'%s: Computing "%s" PML Boundary Condition (Input Sockets = %s)',
|
||||||
|
self.sim_node_name,
|
||||||
|
props['active_socket_set'],
|
||||||
|
input_sockets,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Simple PML
|
||||||
|
if props['active_socket_set'] == 'Simple':
|
||||||
|
return td.PML(num_layers=input_sockets['Layers'])
|
||||||
|
|
||||||
|
# Full PML
|
||||||
|
return td.PML(
|
||||||
|
num_layers=input_sockets['Layers'],
|
||||||
|
parameters=td.PMLParams(
|
||||||
|
sigma_order=input_sockets['σ Order'],
|
||||||
|
sigma_min=input_sockets['σ Range'][0],
|
||||||
|
sigma_max=input_sockets['σ Range'][1],
|
||||||
|
kappa_order=input_sockets['κ Order'],
|
||||||
|
kappa_min=input_sockets['κ Range'][0],
|
||||||
|
kappa_max=input_sockets['κ Range'][1],
|
||||||
|
alpha_order=input_sockets['α Order'],
|
||||||
|
alpha_min=input_sockets['α Range'][0],
|
||||||
|
alpha_max=input_sockets['α Range'][1],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
PMLBoundCondNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {ct.NodeType.PMLBoundCond: (ct.NodeCategory.MAXWELLSIM_BOUNDS)}
|
|
@ -0,0 +1,158 @@
|
||||||
|
"""Implements `BoundCondsNode`."""
|
||||||
|
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
|
from ... import contracts as ct
|
||||||
|
from ... import sockets
|
||||||
|
from .. import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
SSA = ct.SimSpaceAxis
|
||||||
|
|
||||||
|
|
||||||
|
class BoundCondsNode(base.MaxwellSimNode):
|
||||||
|
"""Provides a hub for joining custom simulation domain boundary conditions by-axis."""
|
||||||
|
|
||||||
|
node_type = ct.NodeType.BoundConds
|
||||||
|
bl_label = 'Bound Conds'
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_socket_sets: typ.ClassVar = {
|
||||||
|
'XYZ': {
|
||||||
|
'X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
},
|
||||||
|
'±X | YZ': {
|
||||||
|
'+X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'-X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
},
|
||||||
|
'X | ±Y | Z': {
|
||||||
|
'X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'+Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'-Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
},
|
||||||
|
'XY | ±Z': {
|
||||||
|
'X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'+Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
'-Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
},
|
||||||
|
'±XY | Z': {
|
||||||
|
'+X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'-X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'+Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'-Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
},
|
||||||
|
'X | ±YZ': {
|
||||||
|
'X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'+Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'-Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'+Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
'-Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
},
|
||||||
|
'±XYZ': {
|
||||||
|
'+X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'-X': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.X}),
|
||||||
|
'+Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'-Y': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Y}),
|
||||||
|
'+Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
'-Z': sockets.MaxwellBoundCondSocketDef(allow_axes={SSA.Z}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'BCs': sockets.MaxwellBoundCondsSocketDef(),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output Socket Computation
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'BCs',
|
||||||
|
input_sockets={'X', 'Y', 'Z', '+X', '-X', '+Y', '-Y', '+Z', '-Z'},
|
||||||
|
input_sockets_optional={
|
||||||
|
'X': True,
|
||||||
|
'Y': True,
|
||||||
|
'Z': True,
|
||||||
|
'+X': True,
|
||||||
|
'-X': True,
|
||||||
|
'+Y': True,
|
||||||
|
'-Y': True,
|
||||||
|
'+Z': True,
|
||||||
|
'-Z': True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def compute_boundary_conds(self, input_sockets) -> td.BoundarySpec:
|
||||||
|
"""Compute the simulation boundary conditions, by combining the individual input by specified half axis."""
|
||||||
|
log.debug(
|
||||||
|
'%s: Computing Boundary Conditions (Input Sockets = %s)',
|
||||||
|
self.sim_node_name,
|
||||||
|
str(input_sockets),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Deduce "Doubledness"
|
||||||
|
## -> A "doubled" axis defines the same bound cond both ways
|
||||||
|
has_doubled_x = not ct.FlowSignal.check(input_sockets['X'])
|
||||||
|
has_doubled_y = not ct.FlowSignal.check(input_sockets['Y'])
|
||||||
|
has_doubled_z = not ct.FlowSignal.check(input_sockets['Z'])
|
||||||
|
|
||||||
|
# Deduce +/- of Each Axis
|
||||||
|
## +/- X
|
||||||
|
if has_doubled_x:
|
||||||
|
x_pos = input_sockets['X']
|
||||||
|
x_neg = input_sockets['X']
|
||||||
|
else:
|
||||||
|
x_pos = input_sockets['+X']
|
||||||
|
x_neg = input_sockets['-X']
|
||||||
|
|
||||||
|
## +/- Y
|
||||||
|
if has_doubled_y:
|
||||||
|
y_pos = input_sockets['Y']
|
||||||
|
y_neg = input_sockets['Y']
|
||||||
|
else:
|
||||||
|
y_pos = input_sockets['+Y']
|
||||||
|
y_neg = input_sockets['-Y']
|
||||||
|
|
||||||
|
## +/- Z
|
||||||
|
if has_doubled_z:
|
||||||
|
z_pos = input_sockets['Z']
|
||||||
|
z_neg = input_sockets['Z']
|
||||||
|
else:
|
||||||
|
z_pos = input_sockets['+Z']
|
||||||
|
z_neg = input_sockets['-Z']
|
||||||
|
|
||||||
|
return td.BoundarySpec(
|
||||||
|
x=td.Boundary(
|
||||||
|
plus=x_pos,
|
||||||
|
minus=x_neg,
|
||||||
|
),
|
||||||
|
y=td.Boundary(
|
||||||
|
plus=y_pos,
|
||||||
|
minus=y_neg,
|
||||||
|
),
|
||||||
|
z=td.Boundary(
|
||||||
|
plus=z_pos,
|
||||||
|
minus=z_neg,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
BoundCondsNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {ct.NodeType.BoundConds: (ct.NodeCategory.MAXWELLSIM_BOUNDS)}
|
|
@ -1,25 +0,0 @@
|
||||||
from . import (
|
|
||||||
absorbing_bound_face,
|
|
||||||
bloch_bound_face,
|
|
||||||
pec_bound_face,
|
|
||||||
periodic_bound_face,
|
|
||||||
pmc_bound_face,
|
|
||||||
pml_bound_face,
|
|
||||||
)
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
|
||||||
*pml_bound_face.BL_REGISTER,
|
|
||||||
*pec_bound_face.BL_REGISTER,
|
|
||||||
*pmc_bound_face.BL_REGISTER,
|
|
||||||
*bloch_bound_face.BL_REGISTER,
|
|
||||||
*periodic_bound_face.BL_REGISTER,
|
|
||||||
*absorbing_bound_face.BL_REGISTER,
|
|
||||||
]
|
|
||||||
BL_NODES = {
|
|
||||||
**pml_bound_face.BL_NODES,
|
|
||||||
**pec_bound_face.BL_NODES,
|
|
||||||
**pmc_bound_face.BL_NODES,
|
|
||||||
**bloch_bound_face.BL_NODES,
|
|
||||||
**periodic_bound_face.BL_NODES,
|
|
||||||
**absorbing_bound_face.BL_NODES,
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = []
|
|
||||||
BL_NODES = {}
|
|
|
@ -1,5 +0,0 @@
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = []
|
|
||||||
BL_NODES = {}
|
|
|
@ -1,5 +0,0 @@
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = []
|
|
||||||
BL_NODES = {}
|
|
|
@ -1,5 +0,0 @@
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = []
|
|
||||||
BL_NODES = {}
|
|
|
@ -1,5 +0,0 @@
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = []
|
|
||||||
BL_NODES = {}
|
|
|
@ -1,5 +0,0 @@
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = []
|
|
||||||
BL_NODES = {}
|
|
|
@ -3,6 +3,7 @@ import inspect
|
||||||
import typing as typ
|
import typing as typ
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
|
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
from blender_maxwell.utils import logger
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from .. import contracts as ct
|
from .. import contracts as ct
|
||||||
|
@ -10,7 +11,6 @@ from .. import contracts as ct
|
||||||
log = logger.get(__name__)
|
log = logger.get(__name__)
|
||||||
|
|
||||||
UnitSystemID = str
|
UnitSystemID = str
|
||||||
UnitSystem = dict[ct.SocketType, typ.Any]
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -70,7 +70,7 @@ def event_decorator(
|
||||||
all_loose_input_sockets: bool = False,
|
all_loose_input_sockets: bool = False,
|
||||||
all_loose_output_sockets: bool = False,
|
all_loose_output_sockets: bool = False,
|
||||||
# Request Unit System Scaling
|
# Request Unit System Scaling
|
||||||
unit_systems: dict[UnitSystemID, UnitSystem] = MappingProxyType({}),
|
unit_systems: dict[UnitSystemID, spux.UnitSystem] = MappingProxyType({}),
|
||||||
scale_input_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
scale_input_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
||||||
scale_output_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
scale_output_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
||||||
):
|
):
|
||||||
|
@ -213,7 +213,6 @@ def event_decorator(
|
||||||
kind=kind,
|
kind=kind,
|
||||||
optional=output_sockets_optional.get(output_socket_name, False),
|
optional=output_sockets_optional.get(output_socket_name, False),
|
||||||
),
|
),
|
||||||
node.outputs[output_socket_name].socket_type,
|
|
||||||
unit_systems.get(scale_output_sockets.get(output_socket_name)),
|
unit_systems.get(scale_output_sockets.get(output_socket_name)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -269,9 +268,22 @@ def event_decorator(
|
||||||
else {}
|
else {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Propagate Initialization
|
||||||
|
## If there is a FlowInitializing, then the method would fail.
|
||||||
|
## Therefore, propagate FlowInitializing if found.
|
||||||
|
if any(
|
||||||
|
ct.FlowSignal.check_single(value, ct.FlowSignal.FlowInitializing)
|
||||||
|
for sockets in [
|
||||||
|
method_kw_args.get('input_sockets', {}),
|
||||||
|
method_kw_args.get('loose_input_sockets', {}),
|
||||||
|
method_kw_args.get('output_sockets', {}),
|
||||||
|
method_kw_args.get('loose_output_sockets', {}),
|
||||||
|
]
|
||||||
|
for value in sockets.values()
|
||||||
|
):
|
||||||
|
return ct.FlowSignal.FlowInitializing
|
||||||
|
|
||||||
# Call Method
|
# Call Method
|
||||||
## If there is a FlowPending, then the method would fail.
|
|
||||||
## Therefore, propagate FlowPending if found.
|
|
||||||
return method(
|
return method(
|
||||||
node,
|
node,
|
||||||
**method_kw_args,
|
**method_kw_args,
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
# from . import scientific_constant
|
from . import (
|
||||||
# from . import physical_constant
|
blender_constant,
|
||||||
from . import blender_constant, expr_constant, number_constant, scientific_constant
|
expr_constant,
|
||||||
|
number_constant,
|
||||||
|
physical_constant,
|
||||||
|
scientific_constant,
|
||||||
|
)
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*expr_constant.BL_REGISTER,
|
*expr_constant.BL_REGISTER,
|
||||||
*scientific_constant.BL_REGISTER,
|
*scientific_constant.BL_REGISTER,
|
||||||
*number_constant.BL_REGISTER,
|
*number_constant.BL_REGISTER,
|
||||||
# *physical_constant.BL_REGISTER,
|
*physical_constant.BL_REGISTER,
|
||||||
*blender_constant.BL_REGISTER,
|
*blender_constant.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**expr_constant.BL_NODES,
|
**expr_constant.BL_NODES,
|
||||||
**scientific_constant.BL_NODES,
|
**scientific_constant.BL_NODES,
|
||||||
**number_constant.BL_NODES,
|
**number_constant.BL_NODES,
|
||||||
# **physical_constant.BL_NODES,
|
**physical_constant.BL_NODES,
|
||||||
**blender_constant.BL_NODES,
|
**blender_constant.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ class NumberConstantNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
def draw_props(self, _, col: bpy.types.UILayout) -> None:
|
||||||
row = col.row(align=True)
|
row = col.row(align=True)
|
||||||
row.prop(self, self.blfields['mathtype'], text='')
|
row.prop(self, self.blfields['mathtype'], text='')
|
||||||
row.prop(self, self.blfields['size'], text='')
|
row.prop(self, self.blfields['size'], text='')
|
||||||
|
@ -56,7 +56,7 @@ class NumberConstantNode(base.MaxwellSimNode):
|
||||||
def on_mathtype_size_changed(self, props) -> None:
|
def on_mathtype_size_changed(self, props) -> None:
|
||||||
"""Change the input/output expression sockets to match the mathtype declared in the node."""
|
"""Change the input/output expression sockets to match the mathtype declared in the node."""
|
||||||
self.inputs['Value'].mathtype = props['mathtype']
|
self.inputs['Value'].mathtype = props['mathtype']
|
||||||
self.inputs['Value'].shape = props['mathtype'].shape
|
self.inputs['Value'].shape = props['size'].shape
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - FlowKind
|
# - FlowKind
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import enum
|
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
|
||||||
from blender_maxwell.utils import bl_cache
|
from blender_maxwell.utils import bl_cache
|
||||||
|
@ -10,7 +10,7 @@ from .... import contracts, sockets
|
||||||
from ... import base, events
|
from ... import base, events
|
||||||
|
|
||||||
|
|
||||||
class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
class PhysicalConstantNode(base.MaxwellSimNode):
|
||||||
"""A number of configurable unit dimension, ex. time, length, etc. .
|
"""A number of configurable unit dimension, ex. time, length, etc. .
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -36,12 +36,12 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||||
prop_ui=True,
|
prop_ui=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
mathtype: enum.Enum = bl_cache.BLField(
|
mathtype: spux.MathType = bl_cache.BLField(
|
||||||
enum_cb=lambda self, _: self.search_mathtypes(),
|
enum_cb=lambda self, _: self.search_mathtypes(),
|
||||||
prop_ui=True,
|
prop_ui=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
size: enum.Enum = bl_cache.BLField(
|
size: spux.NumberSize1D = bl_cache.BLField(
|
||||||
enum_cb=lambda self, _: self.search_sizes(),
|
enum_cb=lambda self, _: self.search_sizes(),
|
||||||
prop_ui=True,
|
prop_ui=True,
|
||||||
)
|
)
|
||||||
|
@ -62,16 +62,25 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||||
if spux.NumberSize1D.supports_shape(shape)
|
if spux.NumberSize1D.supports_shape(shape)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_props(self, _, col: bpy.types.UILayout) -> None:
|
||||||
|
row = col.row(align=True)
|
||||||
|
row.prop(self, self.blfields['mathtype'], text='')
|
||||||
|
row.prop(self, self.blfields['size'], text='')
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Events
|
# - Events
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name={'physical_type', 'mathtype', 'size'},
|
prop_name={'physical_type', 'mathtype', 'size'},
|
||||||
|
run_on_init=True,
|
||||||
props={'physical_type', 'mathtype', 'size'},
|
props={'physical_type', 'mathtype', 'size'},
|
||||||
)
|
)
|
||||||
def on_mathtype_or_size_changed(self, props) -> None:
|
def on_mathtype_or_size_changed(self, props) -> None:
|
||||||
"""Change the input/output expression sockets to match the mathtype and size declared in the node."""
|
"""Change the input/output expression sockets to match the mathtype and size declared in the node."""
|
||||||
shape = spux.NumberSize1D(props['size']).shape
|
shape = props['size'].shape
|
||||||
|
|
||||||
# Set Input Socket Physical Type
|
# Set Input Socket Physical Type
|
||||||
if self.inputs['Value'].physical_type != props['physical_type']:
|
if self.inputs['Value'].physical_type != props['physical_type']:
|
||||||
|
@ -90,9 +99,9 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||||
####################
|
####################
|
||||||
# - Callbacks
|
# - Callbacks
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('value')
|
@events.computes_output_socket('Value', input_sockets={'Value'})
|
||||||
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
def compute_value(self, input_sockets) -> sp.Expr:
|
||||||
return self.compute_input('value')
|
return input_sockets['Value']
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -2,7 +2,7 @@ import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from blender_maxwell.utils import sci_constants as constants
|
from blender_maxwell.utils import bl_cache, sci_constants
|
||||||
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import sockets
|
from .... import sockets
|
||||||
|
@ -20,63 +20,43 @@ class ScientificConstantNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
sci_constant: bpy.props.StringProperty(
|
sci_constant: str = bl_cache.BLField(
|
||||||
name='Sci Constant',
|
'',
|
||||||
description='The name of a scientific constant',
|
prop_ui=True,
|
||||||
default='',
|
str_cb=lambda self, _, edit_text: self.search_sci_constants(edit_text),
|
||||||
search=lambda self, _, edit_text: self.search_sci_constants(edit_text),
|
|
||||||
update=lambda self, context: self.on_update_sci_constant(context),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cache__units: bpy.props.StringProperty(default='')
|
|
||||||
cache__uncertainty: bpy.props.StringProperty(default='')
|
|
||||||
|
|
||||||
def search_sci_constants(
|
def search_sci_constants(
|
||||||
self,
|
self,
|
||||||
edit_text: str,
|
edit_text: str,
|
||||||
):
|
):
|
||||||
return [
|
return [
|
||||||
name
|
name
|
||||||
for name in constants.SCI_CONSTANTS
|
for name in sci_constants.SCI_CONSTANTS
|
||||||
if edit_text.lower() in name.lower()
|
if edit_text.lower() in name.lower()
|
||||||
]
|
]
|
||||||
|
|
||||||
def on_update_sci_constant(
|
|
||||||
self,
|
|
||||||
context: bpy.types.Context,
|
|
||||||
):
|
|
||||||
if self.sci_constant:
|
|
||||||
self.cache__units = str(
|
|
||||||
constants.SCI_CONSTANTS_INFO[self.sci_constant]['units']
|
|
||||||
)
|
|
||||||
self.cache__uncertainty = str(
|
|
||||||
constants.SCI_CONSTANTS_INFO[self.sci_constant]['uncertainty']
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.cache__units = ''
|
|
||||||
self.cache__uncertainty = ''
|
|
||||||
|
|
||||||
self.on_prop_changed('sci_constant', context)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None:
|
def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None:
|
||||||
col.prop(self, 'sci_constant', text='')
|
col.prop(self, self.blfields['sci_constant'], text='')
|
||||||
|
|
||||||
def draw_info(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None:
|
def draw_info(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None:
|
||||||
if self.sci_constant:
|
if self.sci_constant:
|
||||||
col.label(text=f'Units: {self.cache__units}')
|
col.label(
|
||||||
col.label(text=f'Uncertainty: {self.cache__uncertainty}')
|
text=f'Units: {sci_constants.SCI_CONSTANTS_INFO[self.sci_constant]["units"]}'
|
||||||
|
)
|
||||||
col.label(text=f'Ref: {constants.SCI_CONSTANTS_REF[0]}')
|
col.label(
|
||||||
|
text=f'Uncertainty: {sci_constants.SCI_CONSTANTS_INFO[self.sci_constant]["uncertainty"]}'
|
||||||
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Callbacks
|
# - Output
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('Value', props={'sci_constant'})
|
@events.computes_output_socket('Value', props={'sci_constant'})
|
||||||
def compute_value(self, props: dict) -> typ.Any:
|
def compute_value(self, props: dict) -> typ.Any:
|
||||||
return constants.SCI_CONSTANTS[props['sci_constant']]
|
return sci_constants.SCI_CONSTANTS[props['sci_constant']]
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -33,6 +33,7 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
input_socket_sets: typ.ClassVar = {
|
input_socket_sets: typ.ClassVar = {
|
||||||
'Wavelength': {
|
'Wavelength': {
|
||||||
'WL': sockets.ExprSocketDef(
|
'WL': sockets.ExprSocketDef(
|
||||||
|
active_kind=ct.FlowKind.Value,
|
||||||
physical_type=spux.PhysicalType.Length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
# Defaults
|
# Defaults
|
||||||
default_unit=spu.nm,
|
default_unit=spu.nm,
|
||||||
|
@ -58,18 +59,18 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'WL': sockets.ExprSocketDef(
|
'WL': sockets.ExprSocketDef(
|
||||||
active_kind=ct.FlowKind.Value,
|
active_kind=ct.FlowKind.Value,
|
||||||
unit_dimension=spux.Dims.length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
),
|
),
|
||||||
'Freq': sockets.ExprSocketDef(
|
'Freq': sockets.ExprSocketDef(
|
||||||
active_kind=ct.FlowKind.Value,
|
active_kind=ct.FlowKind.Value,
|
||||||
unit_dimension=spux.Dims.frequency,
|
physical_type=spux.PhysicalType.Freq,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
use_range: bool = bl_cache.BLField(False)
|
use_range: bool = bl_cache.BLField(False, prop_ui=True)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
|
@ -80,14 +81,14 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
Parameters:
|
Parameters:
|
||||||
col: Target for defining UI elements.
|
col: Target for defining UI elements.
|
||||||
"""
|
"""
|
||||||
col.prop(self, self.blfields['use_range'], toggle=True)
|
col.prop(self, self.blfields['use_range'], toggle=True, text='Range')
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Events
|
# - Events
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name={'active_socket_set', 'use_range'},
|
prop_name={'active_socket_set', 'use_range'},
|
||||||
props='use_range',
|
props={'use_range'},
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
)
|
)
|
||||||
def on_use_range_changed(self, props: dict) -> None:
|
def on_use_range_changed(self, props: dict) -> None:
|
||||||
|
@ -128,7 +129,8 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_wl_value(self, input_sockets: dict) -> sp.Expr:
|
def compute_wl_value(self, input_sockets: dict) -> sp.Expr:
|
||||||
"""Compute a single wavelength value from either wavelength/frequency."""
|
"""Compute a single wavelength value from either wavelength/frequency."""
|
||||||
if input_sockets['WL'] is not None:
|
has_wl = not ct.FlowSignal.check(input_sockets['WL'])
|
||||||
|
if has_wl:
|
||||||
return input_sockets['WL']
|
return input_sockets['WL']
|
||||||
|
|
||||||
return sci_constants.vac_speed_of_light / input_sockets['Freq']
|
return sci_constants.vac_speed_of_light / input_sockets['Freq']
|
||||||
|
@ -141,7 +143,8 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_freq_value(self, input_sockets: dict) -> sp.Expr:
|
def compute_freq_value(self, input_sockets: dict) -> sp.Expr:
|
||||||
"""Compute a single frequency value from either wavelength/frequency."""
|
"""Compute a single frequency value from either wavelength/frequency."""
|
||||||
if input_sockets['Freq'] is not None:
|
has_freq = not ct.FlowSignal.check(input_sockets['Freq'])
|
||||||
|
if has_freq:
|
||||||
return input_sockets['Freq']
|
return input_sockets['Freq']
|
||||||
|
|
||||||
return sci_constants.vac_speed_of_light / input_sockets['WL']
|
return sci_constants.vac_speed_of_light / input_sockets['WL']
|
||||||
|
@ -158,11 +161,20 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_wl_range(self, input_sockets: dict) -> sp.Expr:
|
def compute_wl_range(self, input_sockets: dict) -> sp.Expr:
|
||||||
"""Compute wavelength range from either wavelength/frequency ranges."""
|
"""Compute wavelength range from either wavelength/frequency ranges."""
|
||||||
if input_sockets['WL'] is not None:
|
has_wl = not ct.FlowSignal.check(input_sockets['WL'])
|
||||||
|
if has_wl:
|
||||||
return input_sockets['WL']
|
return input_sockets['WL']
|
||||||
|
|
||||||
return input_sockets['Freq'].rescale_bounds(
|
freq = input_sockets['Freq']
|
||||||
lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
|
return ct.LazyArrayRangeFlow(
|
||||||
|
start=spux.scale_to_unit(
|
||||||
|
sci_constants.vac_speed_of_light / (freq.stop * freq.unit), spu.um
|
||||||
|
),
|
||||||
|
stop=spux.scale_to_unit(
|
||||||
|
sci_constants.vac_speed_of_light / (freq.start * freq.unit), spu.um
|
||||||
|
),
|
||||||
|
steps=freq.steps,
|
||||||
|
unit=spu.um,
|
||||||
)
|
)
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
|
@ -177,11 +189,20 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_freq_range(self, input_sockets: dict) -> sp.Expr:
|
def compute_freq_range(self, input_sockets: dict) -> sp.Expr:
|
||||||
"""Compute frequency range from either wavelength/frequency ranges."""
|
"""Compute frequency range from either wavelength/frequency ranges."""
|
||||||
if input_sockets['Freq'] is not None:
|
has_freq = not ct.FlowSignal.check(input_sockets['Freq'])
|
||||||
|
if has_freq:
|
||||||
return input_sockets['Freq']
|
return input_sockets['Freq']
|
||||||
|
|
||||||
return input_sockets['WL'].rescale_bounds(
|
wl = input_sockets['WL']
|
||||||
lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
|
return ct.LazyArrayRangeFlow(
|
||||||
|
start=spux.scale_to_unit(
|
||||||
|
sci_constants.vac_speed_of_light / (wl.stop * wl.unit), spux.THz
|
||||||
|
),
|
||||||
|
stop=spux.scale_to_unit(
|
||||||
|
sci_constants.vac_speed_of_light / (wl.start * wl.unit), spux.THz
|
||||||
|
),
|
||||||
|
steps=wl.steps,
|
||||||
|
unit=spux.THz,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ class LoadCloudSim(bpy.types.Operator):
|
||||||
node = context.node
|
node = context.node
|
||||||
|
|
||||||
# Try Loading Simulation Data
|
# Try Loading Simulation Data
|
||||||
node.sim_data = bl_cache.Signal.InvalidateCache
|
#node.sim_data = bl_cache.Signal.InvalidateCache
|
||||||
sim_data = node.sim_data
|
sim_data = node.sim_data
|
||||||
if sim_data is None:
|
if sim_data is None:
|
||||||
self.report(
|
self.report(
|
||||||
|
@ -70,18 +70,26 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
||||||
should_exist=True,
|
should_exist=True,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
sim_data_loaded: bool = bl_cache.BLField(False)
|
sim_data_loaded: bool = bl_cache.BLField(False)
|
||||||
|
|
||||||
@bl_cache.cached_bl_property()
|
####################
|
||||||
|
# - Computed
|
||||||
|
####################
|
||||||
|
@property
|
||||||
def sim_data(self) -> td.SimulationData | None:
|
def sim_data(self) -> td.SimulationData | None:
|
||||||
cloud_task = self._compute_input(
|
cloud_task = self._compute_input(
|
||||||
'Cloud Task', kind=ct.FlowKind.Value, optional=True
|
'Cloud Task', kind=ct.FlowKind.Value, optional=True
|
||||||
)
|
)
|
||||||
|
has_cloud_task = not ct.FlowSignal.check(cloud_task)
|
||||||
if (
|
if (
|
||||||
# Check Flow
|
has_cloud_task
|
||||||
not ct.FlowSignal.check(cloud_task)
|
|
||||||
# Check Task
|
|
||||||
and cloud_task is not None
|
and cloud_task is not None
|
||||||
and isinstance(cloud_task, tdcloud.CloudTask)
|
and isinstance(cloud_task, tdcloud.CloudTask)
|
||||||
and cloud_task.status == 'success'
|
and cloud_task.status == 'success'
|
||||||
|
@ -97,7 +105,7 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_operators(self, context, layout):
|
def draw_operators(self, _: bpy.types.Context, layout: bpy.types.UILayout):
|
||||||
if self.sim_data_loaded:
|
if self.sim_data_loaded:
|
||||||
layout.operator(ct.OperatorType.NodeLoadCloudSim, text='Reload Sim')
|
layout.operator(ct.OperatorType.NodeLoadCloudSim, text='Reload Sim')
|
||||||
else:
|
else:
|
||||||
|
@ -106,11 +114,6 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Events
|
# - Events
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(socket_name='Cloud Task')
|
|
||||||
def on_cloud_task_changed(self):
|
|
||||||
self.inputs['Cloud Task'].on_cloud_updated()
|
|
||||||
## TODO: Must we babysit sockets like this?
|
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name='sim_data_loaded', run_on_init=True, props={'sim_data_loaded'}
|
prop_name='sim_data_loaded', run_on_init=True, props={'sim_data_loaded'}
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,6 +33,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
'Size': sockets.ExprSocketDef(
|
'Size': sockets.ExprSocketDef(
|
||||||
shape=(3,),
|
shape=(3,),
|
||||||
physical_type=spux.PhysicalType.Length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_value=sp.Matrix([1, 1, 1]),
|
||||||
),
|
),
|
||||||
'Spatial Subdivs': sockets.ExprSocketDef(
|
'Spatial Subdivs': sockets.ExprSocketDef(
|
||||||
shape=(3,),
|
shape=(3,),
|
||||||
|
@ -124,11 +125,30 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
# - Preview
|
# - Preview
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
socket_name={'Center', 'Size'},
|
# Trigger
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
|
# Loaded
|
||||||
|
managed_objs={'mesh'},
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
input_sockets={'Center', 'Size'},
|
input_sockets={'Center', 'Size'},
|
||||||
|
)
|
||||||
|
def on_preview_changed(self, managed_objs, props, input_sockets):
|
||||||
|
"""Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen."""
|
||||||
|
mesh = managed_objs['mesh']
|
||||||
|
|
||||||
|
# Push Preview State to Managed Mesh
|
||||||
|
if props['preview_active']:
|
||||||
|
mesh.show_preview()
|
||||||
|
else:
|
||||||
|
mesh.hide_preview()
|
||||||
|
|
||||||
|
@events.on_value_changed(
|
||||||
|
# Trigger
|
||||||
|
socket_name={'Center', 'Size'},
|
||||||
|
run_on_init=True,
|
||||||
|
# Loaded
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'mesh', 'modifier'},
|
||||||
|
input_sockets={'Center', 'Size'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
scale_input_sockets={
|
scale_input_sockets={
|
||||||
'Center': 'BlenderUnits',
|
'Center': 'BlenderUnits',
|
||||||
|
@ -136,7 +156,6 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_inputs_changed(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
|
||||||
managed_objs: dict,
|
managed_objs: dict,
|
||||||
input_sockets: dict,
|
input_sockets: dict,
|
||||||
unit_systems: dict,
|
unit_systems: dict,
|
||||||
|
@ -153,9 +172,6 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -31,6 +31,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
'Size': sockets.ExprSocketDef(
|
'Size': sockets.ExprSocketDef(
|
||||||
shape=(3,),
|
shape=(3,),
|
||||||
physical_type=spux.PhysicalType.Length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_value=sp.Matrix([1, 1, 1]),
|
||||||
),
|
),
|
||||||
'Samples/Space': sockets.ExprSocketDef(
|
'Samples/Space': sockets.ExprSocketDef(
|
||||||
shape=(3,),
|
shape=(3,),
|
||||||
|
@ -123,11 +124,29 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
# - Preview - Changes to Input Sockets
|
# - Preview - Changes to Input Sockets
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
socket_name={'Center', 'Size'},
|
# Trigger
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
|
# Loaded
|
||||||
|
managed_objs={'mesh'},
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
input_sockets={'Center', 'Size'},
|
)
|
||||||
|
def on_preview_changed(self, managed_objs, props):
|
||||||
|
"""Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen."""
|
||||||
|
mesh = managed_objs['mesh']
|
||||||
|
|
||||||
|
# Push Preview State to Managed Mesh
|
||||||
|
if props['preview_active']:
|
||||||
|
mesh.show_preview()
|
||||||
|
else:
|
||||||
|
mesh.hide_preview()
|
||||||
|
|
||||||
|
@events.on_value_changed(
|
||||||
|
# Trigger
|
||||||
|
socket_name={'Center', 'Size'},
|
||||||
|
run_on_init=True,
|
||||||
|
# Loaded
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'mesh', 'modifier'},
|
||||||
|
input_sockets={'Center', 'Size'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
scale_input_sockets={
|
scale_input_sockets={
|
||||||
'Center': 'BlenderUnits',
|
'Center': 'BlenderUnits',
|
||||||
|
@ -135,7 +154,6 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_inputs_changed(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
|
||||||
managed_objs: dict,
|
managed_objs: dict,
|
||||||
input_sockets: dict,
|
input_sockets: dict,
|
||||||
unit_systems: dict,
|
unit_systems: dict,
|
||||||
|
@ -152,9 +170,6 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from . import file_exporters, viewer, web_exporters
|
#from . import file_exporters, viewer, web_exporters
|
||||||
|
from . import viewer
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*viewer.BL_REGISTER,
|
*viewer.BL_REGISTER,
|
||||||
*file_exporters.BL_REGISTER,
|
#*file_exporters.BL_REGISTER,
|
||||||
*web_exporters.BL_REGISTER,
|
#*web_exporters.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**viewer.BL_NODES,
|
**viewer.BL_NODES,
|
||||||
**file_exporters.BL_NODES,
|
#**file_exporters.BL_NODES,
|
||||||
**web_exporters.BL_NODES,
|
#**web_exporters.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,27 +35,8 @@ class SimDomainNode(base.MaxwellSimNode):
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods
|
# - Events
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
|
||||||
'Domain',
|
|
||||||
input_sockets={'Duration', 'Center', 'Size', 'Grid', 'Ambient Medium'},
|
|
||||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Duration': 'Tidy3DUnits',
|
|
||||||
'Center': 'Tidy3DUnits',
|
|
||||||
'Size': 'Tidy3DUnits',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
def compute_domain(self, input_sockets: dict, unit_systems) -> sp.Expr:
|
|
||||||
return {
|
|
||||||
'run_time': input_sockets['Duration'],
|
|
||||||
'center': input_sockets['Center'],
|
|
||||||
'size': input_sockets['Size'],
|
|
||||||
'grid_spec': input_sockets['Grid'],
|
|
||||||
'medium': input_sockets['Ambient Medium'],
|
|
||||||
}
|
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
socket_name={'Center', 'Size'},
|
socket_name={'Center', 'Size'},
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
|
@ -91,6 +72,28 @@ class SimDomainNode(base.MaxwellSimNode):
|
||||||
if props['preview_active']:
|
if props['preview_active']:
|
||||||
managed_objs['mesh'].show_preview()
|
managed_objs['mesh'].show_preview()
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Outputs
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'Domain',
|
||||||
|
input_sockets={'Duration', 'Center', 'Size', 'Grid', 'Ambient Medium'},
|
||||||
|
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
||||||
|
scale_input_sockets={
|
||||||
|
'Duration': 'Tidy3DUnits',
|
||||||
|
'Center': 'Tidy3DUnits',
|
||||||
|
'Size': 'Tidy3DUnits',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def compute_domain(self, input_sockets, unit_systems) -> sp.Expr:
|
||||||
|
return {
|
||||||
|
'run_time': input_sockets['Duration'],
|
||||||
|
'center': input_sockets['Center'],
|
||||||
|
'size': input_sockets['Size'],
|
||||||
|
'grid_spec': input_sockets['Grid'],
|
||||||
|
'medium': input_sockets['Ambient Medium'],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Blender Registration
|
# - Blender Registration
|
||||||
|
|
|
@ -217,7 +217,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
Called by `self.on_prop_changed()` when `self.active_kind` was changed.
|
Called by `self.on_prop_changed()` when `self.active_kind` was changed.
|
||||||
"""
|
"""
|
||||||
self.display_shape = (
|
self.display_shape = (
|
||||||
'SQUARE' if self.active_kind == ct.FlowKind.LazyValueRange else 'CIRCLE'
|
'SQUARE' if self.active_kind == ct.FlowKind.LazyArrayRange else 'CIRCLE'
|
||||||
) # + ('_DOT' if self.use_units else '')
|
) # + ('_DOT' if self.use_units else '')
|
||||||
## TODO: Valid Active Kinds should be a subset/subenum(?) of FlowKind
|
## TODO: Valid Active Kinds should be a subset/subenum(?) of FlowKind
|
||||||
|
|
||||||
|
@ -373,6 +373,32 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
"""
|
"""
|
||||||
self.trigger_event(ct.FlowEvent.LinkChanged)
|
self.trigger_event(ct.FlowEvent.LinkChanged)
|
||||||
|
|
||||||
|
def remove_invalidated_links(self) -> None:
|
||||||
|
"""Reevaluates the capabilities of all socket links, and removes any that no longer match.
|
||||||
|
|
||||||
|
Links are removed with a simple `node_tree.links.remove()`, which directly emulates a user trying to remove the node link.
|
||||||
|
**Note** that all of the usual consent-semantics apply just the same as if the user had manually tried to remove the link.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Called by nodes directly on their sockets, after altering any property that might influence the capabilities of that socket.
|
||||||
|
|
||||||
|
This prevents invalid use when the user alters a property, which **would** disallow adding a _new_ link identical to one that already exists.
|
||||||
|
In such a case, the existing (non-capability-respecting) link should be removed, as it has become invalid.
|
||||||
|
"""
|
||||||
|
node_tree = self.id_data
|
||||||
|
for link in self.links:
|
||||||
|
if not link.from_socket.capabilities.is_compatible_with(
|
||||||
|
link.to_socket.capabilities
|
||||||
|
):
|
||||||
|
log.error(
|
||||||
|
'Deleted link between "%s" (%s) and "%s" (%s) due to invalidated capabilities',
|
||||||
|
link.from_socket.bl_label,
|
||||||
|
link.from_socket.capabilities,
|
||||||
|
link.to_socket.bl_label,
|
||||||
|
link.to_socket.capabilities,
|
||||||
|
)
|
||||||
|
node_tree.links.remove(link)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Chain
|
# - Event Chain
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -2,6 +2,7 @@ import enum
|
||||||
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 blender_maxwell.utils import bl_cache, logger
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
|
@ -63,6 +64,7 @@ class InfoDisplayCol(enum.StrEnum):
|
||||||
class ExprBLSocket(base.MaxwellSimSocket):
|
class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
socket_type = ct.SocketType.Expr
|
socket_type = ct.SocketType.Expr
|
||||||
bl_label = 'Expr'
|
bl_label = 'Expr'
|
||||||
|
use_info_draw = True
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
|
@ -70,7 +72,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
shape: tuple[int, ...] | None = bl_cache.BLField(None)
|
shape: tuple[int, ...] | None = bl_cache.BLField(None)
|
||||||
mathtype: spux.MathType = bl_cache.BLField(spux.MathType.Real, prop_ui=True)
|
mathtype: spux.MathType = bl_cache.BLField(spux.MathType.Real, prop_ui=True)
|
||||||
physical_type: spux.PhysicalType | None = bl_cache.BLField(None)
|
physical_type: spux.PhysicalType | None = bl_cache.BLField(None)
|
||||||
symbols: frozenset[spux.Symbol] = bl_cache.BLField(frozenset())
|
symbols: frozenset[sp.Symbol] = bl_cache.BLField(frozenset())
|
||||||
|
|
||||||
active_unit: enum.Enum = bl_cache.BLField(
|
active_unit: enum.Enum = bl_cache.BLField(
|
||||||
None, enum_cb=lambda self, _: self.search_units(), prop_ui=True
|
None, enum_cb=lambda self, _: self.search_units(), prop_ui=True
|
||||||
|
@ -102,7 +104,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
)
|
)
|
||||||
|
|
||||||
# UI: LazyArrayRange
|
# UI: LazyArrayRange
|
||||||
steps: int = bl_cache.BLField(2, abs_min=2)
|
steps: int = bl_cache.BLField(2, abs_min=2, prop_ui=True)
|
||||||
## Expression
|
## Expression
|
||||||
raw_min_spstr: str = bl_cache.BLField('', prop_ui=True)
|
raw_min_spstr: str = bl_cache.BLField('', prop_ui=True)
|
||||||
raw_max_spstr: str = bl_cache.BLField('', prop_ui=True)
|
raw_max_spstr: str = bl_cache.BLField('', prop_ui=True)
|
||||||
|
@ -125,6 +127,15 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
# - Computed: Raw Expressions
|
# - Computed: Raw Expressions
|
||||||
####################
|
####################
|
||||||
|
@property
|
||||||
|
def sorted_symbols(self) -> list[sp.Symbol]:
|
||||||
|
"""Retrieves all symbols and sorts them by name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Repeateably ordered list of symbols.
|
||||||
|
"""
|
||||||
|
return sorted(self.symbols, key=lambda sym: sym.name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def raw_value_sp(self) -> spux.SympyExpr:
|
def raw_value_sp(self) -> spux.SympyExpr:
|
||||||
return self._parse_expr_str(self.raw_value_spstr)
|
return self._parse_expr_str(self.raw_value_spstr)
|
||||||
|
@ -140,7 +151,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
# - Computed: Units
|
# - Computed: Units
|
||||||
####################
|
####################
|
||||||
def search_units(self, _: bpy.types.Context) -> list[ct.BLEnumElement]:
|
def search_units(self) -> list[ct.BLEnumElement]:
|
||||||
if self.physical_type is not None:
|
if self.physical_type is not None:
|
||||||
return [
|
return [
|
||||||
(sp.sstr(unit), spux.sp_to_str(unit), sp.sstr(unit), '', i)
|
(sp.sstr(unit), spux.sp_to_str(unit), sp.sstr(unit), '', i)
|
||||||
|
@ -163,33 +174,38 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@unit.setter
|
@unit.setter
|
||||||
def unit(self, unit: spux.Unit) -> None:
|
def unit(self, unit: spux.Unit | None) -> None:
|
||||||
"""Set the unit, without touching the `raw_*` UI properties.
|
"""Set the unit, without touching the `raw_*` UI properties.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
To set a new unit, **and** convert the `raw_*` UI properties to the new unit, use `self.convert_unit()` instead.
|
To set a new unit, **and** convert the `raw_*` UI properties to the new unit, use `self.convert_unit()` instead.
|
||||||
"""
|
"""
|
||||||
if unit in self.physical_type.valid_units:
|
if self.physical_type is not None:
|
||||||
self.active_unit = sp.sstr(unit)
|
if unit in self.physical_type.valid_units:
|
||||||
|
self.active_unit = sp.sstr(unit)
|
||||||
msg = f'Tried to set invalid unit {unit} (physical type "{self.physical_type}" only supports "{self.physical_type.valid_units}")'
|
else:
|
||||||
raise ValueError(msg)
|
msg = f'Tried to set invalid unit {unit} (physical type "{self.physical_type}" only supports "{self.physical_type.valid_units}")'
|
||||||
|
raise ValueError(msg)
|
||||||
|
elif unit is not None:
|
||||||
|
msg = f'Tried to set invalid unit {unit} (physical type is {self.physical_type}, and has no unit support!)")'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
def convert_unit(self, unit_to: spux.Unit) -> None:
|
def convert_unit(self, unit_to: spux.Unit) -> None:
|
||||||
if self.active_kind == ct.FlowKind.Value:
|
current_value = self.value
|
||||||
current_value = self.value
|
current_lazy_array_range = self.lazy_array_range
|
||||||
self.unit = unit_to
|
|
||||||
self.value = current_value
|
self.unit = bl_cache.Signal.InvalidateCache
|
||||||
elif self.active_kind == ct.FlowKind.LazyArrayRange:
|
|
||||||
current_lazy_array_range = self.lazy_array_range
|
self.value = current_value
|
||||||
self.unit = unit_to
|
self.lazy_array_range = current_lazy_array_range
|
||||||
self.lazy_array_range = current_lazy_array_range
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Property Callback
|
# - Property Callback
|
||||||
####################
|
####################
|
||||||
def on_socket_prop_changed(self, prop_name: str) -> None:
|
def on_socket_prop_changed(self, prop_name: str) -> None:
|
||||||
if prop_name == 'unit' and self.active_unit is not None:
|
if prop_name == 'physical_type':
|
||||||
|
self.active_unit = bl_cache.Signal.ResetEnumItems
|
||||||
|
if prop_name == 'active_unit' and self.active_unit is not None:
|
||||||
self.convert_unit(spux.unit_str_to_unit(self.active_unit))
|
self.convert_unit(spux.unit_str_to_unit(self.active_unit))
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -200,23 +216,23 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
) -> tuple[spux.MathType, tuple[int, ...] | None, spux.UnitDimension]:
|
) -> tuple[spux.MathType, tuple[int, ...] | None, spux.UnitDimension]:
|
||||||
# Parse MathType
|
# Parse MathType
|
||||||
mathtype = spux.MathType.from_expr(expr)
|
mathtype = spux.MathType.from_expr(expr)
|
||||||
if self.mathtype != mathtype:
|
if not self.mathtype.is_compatible(mathtype):
|
||||||
msg = f'MathType is {self.mathtype}, but tried to set expr {expr} with mathtype {mathtype}'
|
msg = f'MathType is {self.mathtype}, but tried to set expr {expr} with mathtype {mathtype}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
# Parse Symbols
|
# Parse Symbols
|
||||||
if expr.free_symbols:
|
if expr.free_symbols and not expr.free_symbols.issubset(self.symbols):
|
||||||
if self.mathtype is not None:
|
msg = f'Tried to set expr {expr} with free symbols {expr.free_symbols}, which is incompatible with socket symbols {self.symbols}'
|
||||||
msg = f'MathType is {self.mathtype}, but tried to set expr {expr} with free symbols {expr.free_symbols}'
|
raise ValueError(msg)
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
if not expr.free_symbols.issubset(self.symbols):
|
|
||||||
msg = f'Tried to set expr {expr} with free symbols {expr.free_symbols}, which is incompatible with socket symbols {self.symbols}'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
# Parse Dimensions
|
# Parse Dimensions
|
||||||
shape = spux.parse_shape(expr)
|
shape = spux.parse_shape(expr)
|
||||||
if shape != self.shape:
|
if shape != self.shape and not (
|
||||||
|
shape is not None
|
||||||
|
and self.shape is not None
|
||||||
|
and len(self.shape) == 1
|
||||||
|
and 1 in shape
|
||||||
|
):
|
||||||
msg = f'Expr {expr} has shape {shape}, which is incompatible with the expr socket (shape {self.shape})'
|
msg = f'Expr {expr} has shape {shape}, which is incompatible with the expr socket (shape {self.shape})'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
@ -238,7 +254,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
# Try Parsing and Returning the Expression
|
# Try Parsing and Returning the Expression
|
||||||
try:
|
try:
|
||||||
self._parse_expr_info(expr)
|
self._parse_expr_info(expr)
|
||||||
except ValueError(expr) as ex:
|
except ValueError:
|
||||||
log.exception(
|
log.exception(
|
||||||
'Couldn\'t parse expression "%s" in Expr socket.',
|
'Couldn\'t parse expression "%s" in Expr socket.',
|
||||||
expr_spstr,
|
expr_spstr,
|
||||||
|
@ -270,6 +286,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
expr = self.raw_value_sp
|
expr = self.raw_value_sp
|
||||||
if expr is None:
|
if expr is None:
|
||||||
return ct.FlowSignal.FlowPending
|
return ct.FlowSignal.FlowPending
|
||||||
|
return expr
|
||||||
|
|
||||||
MT_Z = spux.MathType.Integer
|
MT_Z = spux.MathType.Integer
|
||||||
MT_Q = spux.MathType.Rational
|
MT_Q = spux.MathType.Rational
|
||||||
|
@ -312,7 +329,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
Notes:
|
Notes:
|
||||||
Called to set the internal `FlowKind.Value` of this socket.
|
Called to set the internal `FlowKind.Value` of this socket.
|
||||||
"""
|
"""
|
||||||
mathtype, shape = self._parse_expr_info(expr)
|
_mathtype, _shape = self._parse_expr_info(expr)
|
||||||
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
||||||
self.raw_value_spstr = sp.sstr(expr)
|
self.raw_value_spstr = sp.sstr(expr)
|
||||||
|
|
||||||
|
@ -321,32 +338,33 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
MT_Q = spux.MathType.Rational
|
MT_Q = spux.MathType.Rational
|
||||||
MT_R = spux.MathType.Real
|
MT_R = spux.MathType.Real
|
||||||
MT_C = spux.MathType.Complex
|
MT_C = spux.MathType.Complex
|
||||||
if shape is None:
|
if self.shape is None:
|
||||||
if mathtype == MT_Z:
|
if self.mathtype == MT_Z:
|
||||||
self.raw_value_int = self._to_raw_value(expr)
|
self.raw_value_int = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_Q:
|
elif self.mathtype == MT_Q:
|
||||||
self.raw_value_rat = self._to_raw_value(expr)
|
self.raw_value_rat = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_R:
|
elif self.mathtype == MT_R:
|
||||||
self.raw_value_float = self._to_raw_value(expr)
|
self.raw_value_float = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_C:
|
elif self.mathtype == MT_C:
|
||||||
self.raw_value_complex = self._to_raw_value(expr)
|
self.raw_value_complex = self._to_raw_value(expr)
|
||||||
elif shape == (2,):
|
elif self.shape == (2,):
|
||||||
if mathtype == MT_Z:
|
if self.mathtype == MT_Z:
|
||||||
self.raw_value_int2 = self._to_raw_value(expr)
|
self.raw_value_int2 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_Q:
|
elif self.mathtype == MT_Q:
|
||||||
self.raw_value_rat2 = self._to_raw_value(expr)
|
self.raw_value_rat2 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_R:
|
elif self.mathtype == MT_R:
|
||||||
self.raw_value_float2 = self._to_raw_value(expr)
|
self.raw_value_float2 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_C:
|
elif self.mathtype == MT_C:
|
||||||
self.raw_value_complex2 = self._to_raw_value(expr)
|
self.raw_value_complex2 = self._to_raw_value(expr)
|
||||||
elif shape == (3,):
|
elif self.shape == (3,):
|
||||||
if mathtype == MT_Z:
|
log.critical(expr)
|
||||||
|
if self.mathtype == MT_Z:
|
||||||
self.raw_value_int3 = self._to_raw_value(expr)
|
self.raw_value_int3 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_Q:
|
elif self.mathtype == MT_Q:
|
||||||
self.raw_value_rat3 = self._to_raw_value(expr)
|
self.raw_value_rat3 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_R:
|
elif self.mathtype == MT_R:
|
||||||
self.raw_value_float3 = self._to_raw_value(expr)
|
self.raw_value_float3 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_C:
|
elif self.mathtype == MT_C:
|
||||||
self.raw_value_complex3 = self._to_raw_value(expr)
|
self.raw_value_complex3 = self._to_raw_value(expr)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -404,7 +422,6 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
Called to compute the internal `FlowKind.LazyArrayRange` of this socket.
|
Called to compute the internal `FlowKind.LazyArrayRange` of this socket.
|
||||||
"""
|
"""
|
||||||
self.steps = value.steps
|
self.steps = value.steps
|
||||||
self.unit = value.unit
|
|
||||||
|
|
||||||
if self.symbols:
|
if self.symbols:
|
||||||
self.raw_min_spstr = sp.sstr(value.start)
|
self.raw_min_spstr = sp.sstr(value.start)
|
||||||
|
@ -416,21 +433,26 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
MT_R = spux.MathType.Real
|
MT_R = spux.MathType.Real
|
||||||
MT_C = spux.MathType.Complex
|
MT_C = spux.MathType.Complex
|
||||||
|
|
||||||
|
unit = value.unit if value.unit is not None else 1
|
||||||
if value.mathtype == MT_Z:
|
if value.mathtype == MT_Z:
|
||||||
self.raw_range_int = [
|
self.raw_range_int = [
|
||||||
self._to_raw_value(bound) for bound in [value.start, value.stop]
|
self._to_raw_value(bound * unit)
|
||||||
|
for bound in [value.start, value.stop]
|
||||||
]
|
]
|
||||||
elif value.mathtype == MT_Q:
|
elif value.mathtype == MT_Q:
|
||||||
self.raw_range_rat = [
|
self.raw_range_rat = [
|
||||||
self._to_raw_value(bound) for bound in [value.start, value.stop]
|
self._to_raw_value(bound * unit)
|
||||||
|
for bound in [value.start, value.stop]
|
||||||
]
|
]
|
||||||
elif value.mathtype == MT_R:
|
elif value.mathtype == MT_R:
|
||||||
self.raw_range_float = [
|
self.raw_range_float = [
|
||||||
self._to_raw_value(bound) for bound in [value.start, value.stop]
|
self._to_raw_value(bound * unit)
|
||||||
|
for bound in [value.start, value.stop]
|
||||||
]
|
]
|
||||||
elif value.mathtype == MT_C:
|
elif value.mathtype == MT_C:
|
||||||
self.raw_range_complex = [
|
self.raw_range_complex = [
|
||||||
self._to_raw_value(bound) for bound in [value.start, value.stop]
|
self._to_raw_value(bound * unit)
|
||||||
|
for bound in [value.start, value.stop]
|
||||||
]
|
]
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -441,8 +463,8 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
# Lazy Value: Arbitrary Expression
|
# Lazy Value: Arbitrary Expression
|
||||||
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
||||||
return ct.LazyValueFuncFlow(
|
return ct.LazyValueFuncFlow(
|
||||||
func=sp.lambdify(self.symbols, self.value, 'jax'),
|
func=sp.lambdify(self.sorted_symbols, self.value, 'jax'),
|
||||||
func_args=[spux.MathType.from_expr(sym) for sym in self.symbols],
|
func_args=[spux.MathType.from_expr(sym) for sym in self.sorted_symbols],
|
||||||
supports_jax=True,
|
supports_jax=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -482,8 +504,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
unit=self.unit,
|
unit=self.unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = "Expr socket can't produce array from expression with free symbols"
|
return ct.FlowSignal.NoFlow
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - FlowKind: Info
|
# - FlowKind: Info
|
||||||
|
@ -496,6 +517,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
output_mathtype=self.mathtype,
|
output_mathtype=self.mathtype,
|
||||||
output_unit=self.unit,
|
output_unit=self.unit,
|
||||||
)
|
)
|
||||||
|
## TODO: When expression can be used w/arrays, then allow directly outputting a LazyArrayRange pumped through the given expression. Or something like that.
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - FlowKind: Capabilities
|
# - FlowKind: Capabilities
|
||||||
|
@ -520,10 +542,11 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
_row.label(text=text)
|
_row.label(text=text)
|
||||||
|
|
||||||
_col = split.column(align=True)
|
_col = split.column(align=True)
|
||||||
_col.prop(self, 'active_unit', text='')
|
_col.prop(self, self.blfields['active_unit'], text='')
|
||||||
|
else:
|
||||||
|
row.label(text=text)
|
||||||
|
|
||||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
# Property Interface
|
|
||||||
if self.symbols:
|
if self.symbols:
|
||||||
col.prop(self, self.blfields['raw_value_spstr'], text='')
|
col.prop(self, self.blfields['raw_value_spstr'], text='')
|
||||||
|
|
||||||
|
@ -575,6 +598,27 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
for sym in self.symbols:
|
for sym in self.symbols:
|
||||||
col.label(text=spux.pretty_symbol(sym))
|
col.label(text=spux.pretty_symbol(sym))
|
||||||
|
|
||||||
|
def draw_lazy_array_range(self, col: bpy.types.UILayout) -> None:
|
||||||
|
if self.symbols:
|
||||||
|
col.prop(self, self.blfields['raw_min_spstr'], text='')
|
||||||
|
col.prop(self, self.blfields['raw_max_spstr'], text='')
|
||||||
|
|
||||||
|
else:
|
||||||
|
MT_Z = spux.MathType.Integer
|
||||||
|
MT_Q = spux.MathType.Rational
|
||||||
|
MT_R = spux.MathType.Real
|
||||||
|
MT_C = spux.MathType.Complex
|
||||||
|
if self.mathtype == MT_Z:
|
||||||
|
col.prop(self, self.blfields['raw_range_int'], text='')
|
||||||
|
elif self.mathtype == MT_Q:
|
||||||
|
col.prop(self, self.blfields['raw_range_rat'], text='')
|
||||||
|
elif self.mathtype == MT_R:
|
||||||
|
col.prop(self, self.blfields['raw_range_float'], text='')
|
||||||
|
elif self.mathtype == MT_C:
|
||||||
|
col.prop(self, self.blfields['raw_range_complex'], text='')
|
||||||
|
|
||||||
|
col.prop(self, self.blfields['steps'], text='')
|
||||||
|
|
||||||
def draw_input_label_row(self, row: bpy.types.UILayout, text) -> None:
|
def draw_input_label_row(self, row: bpy.types.UILayout, text) -> None:
|
||||||
info = self.compute_data(kind=ct.FlowKind.Info)
|
info = self.compute_data(kind=ct.FlowKind.Info)
|
||||||
has_dims = not ct.FlowSignal.check(info) and info.dim_names
|
has_dims = not ct.FlowSignal.check(info) and info.dim_names
|
||||||
|
@ -630,7 +674,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
_row.label(text=text)
|
_row.label(text=text)
|
||||||
|
|
||||||
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
|
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
|
||||||
if info.dim_names and self.show_info_columns:
|
if self.show_info_columns:
|
||||||
row = col.row()
|
row = col.row()
|
||||||
box = row.box()
|
box = row.box()
|
||||||
grid = box.grid_flow(
|
grid = box.grid_flow(
|
||||||
|
@ -696,19 +740,91 @@ class ExprSocketDef(base.SocketDef):
|
||||||
default_unit: spux.Unit | None = None
|
default_unit: spux.Unit | None = None
|
||||||
|
|
||||||
# FlowKind: Value
|
# FlowKind: Value
|
||||||
default_value: spux.SympyExpr = sp.S(0)
|
default_value: spux.SympyExpr = sp.RealNumber(0)
|
||||||
|
abs_min: spux.SympyExpr | None = None ## TODO: Not used (yet)
|
||||||
|
abs_max: spux.SympyExpr | None = None ## TODO: Not used (yet)
|
||||||
|
## TODO: Idea is to use this scalar uniformly for all shape elements
|
||||||
|
## TODO: -> But we may want to **allow** using same-shape for diff. bounds.
|
||||||
|
|
||||||
# FlowKind: LazyArrayRange
|
# FlowKind: LazyArrayRange
|
||||||
default_min: spux.SympyExpr = sp.S(0)
|
default_min: spux.SympyExpr = sp.RealNumber(0)
|
||||||
default_max: spux.SympyExpr = sp.S(1)
|
default_max: spux.SympyExpr = sp.RealNumber(1)
|
||||||
default_steps: int = 2
|
default_steps: int = 2
|
||||||
## TODO: Configure lin/log/... scaling (w/enumprop in UI)
|
## TODO: Configure lin/log/... scaling (w/enumprop in UI)
|
||||||
|
|
||||||
## TODO: Buncha validation :)
|
|
||||||
|
|
||||||
# UI
|
# UI
|
||||||
show_info_columns: bool = False
|
show_info_columns: bool = False
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Validators - Coersion
|
||||||
|
####################
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def shape_value_coersion(self) -> str:
|
||||||
|
if self.shape is not None and not isinstance(self.default_value, sp.MatrixBase):
|
||||||
|
if len(self.shape) == 1:
|
||||||
|
self.default_value = self.default_value * sp.Matrix.ones(
|
||||||
|
self.shape[0], 1
|
||||||
|
)
|
||||||
|
if len(self.shape) == 2:
|
||||||
|
self.default_value = self.default_value * sp.Matrix.ones(*self.shape)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def unit_coersion(self) -> str:
|
||||||
|
if self.physical_type is not None and self.default_unit is None:
|
||||||
|
self.default_unit = self.physical_type.default_unit
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Validators - Assertion
|
||||||
|
####################
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def valid_shapes(self) -> str:
|
||||||
|
if self.active_kind == ct.FlowKind.LazyArrayRange and self.shape is not None:
|
||||||
|
msg = "Can't have a non-None shape when LazyArrayRange is set as the active kind."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def mathtype_value(self) -> str:
|
||||||
|
default_value_mathtype = spux.MathType.from_expr(self.default_value)
|
||||||
|
if not self.mathtype.is_compatible(default_value_mathtype):
|
||||||
|
msg = f'MathType is {self.mathtype}, but tried to set default value {self.default_value} with mathtype {default_value_mathtype}'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def symbols_value(self) -> str:
|
||||||
|
if (
|
||||||
|
self.default_value.free_symbols
|
||||||
|
and not self.default_value.free_symbols.issubset(self.symbols)
|
||||||
|
):
|
||||||
|
msg = f'Tried to set default value {self.default_value} with free symbols {self.default_value.free_symbols}, which is incompatible with socket symbols {self.symbols}'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def shape_value(self) -> str:
|
||||||
|
shape = spux.parse_shape(self.default_value)
|
||||||
|
if shape != self.shape and not (
|
||||||
|
shape is not None
|
||||||
|
and self.shape is not None
|
||||||
|
and len(self.shape) == 1
|
||||||
|
and 1 in shape
|
||||||
|
):
|
||||||
|
msg = f'Default value {self.default_value} has shape {shape}, which is incompatible with the expr socket (shape {self.shape})'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Initialization
|
||||||
|
####################
|
||||||
def init(self, bl_socket: ExprBLSocket) -> None:
|
def init(self, bl_socket: ExprBLSocket) -> None:
|
||||||
bl_socket.active_kind = self.active_kind
|
bl_socket.active_kind = self.active_kind
|
||||||
|
|
||||||
|
@ -718,12 +834,12 @@ class ExprSocketDef(base.SocketDef):
|
||||||
bl_socket.physical_type = self.physical_type
|
bl_socket.physical_type = self.physical_type
|
||||||
bl_socket.symbols = self.symbols
|
bl_socket.symbols = self.symbols
|
||||||
|
|
||||||
# Socket Units
|
# Socket Units & FlowKind.Value
|
||||||
if self.default_unit is not None:
|
if self.physical_type is not None:
|
||||||
bl_socket.unit = self.default_unit
|
bl_socket.unit = self.default_unit
|
||||||
|
bl_socket.value = self.default_value * self.default_unit
|
||||||
# FlowKind: Value
|
else:
|
||||||
bl_socket.value = self.default_value
|
bl_socket.value = self.default_value
|
||||||
|
|
||||||
# FlowKind: LazyArrayRange
|
# FlowKind: LazyArrayRange
|
||||||
bl_socket.lazy_array_range = ct.LazyArrayRangeFlow(
|
bl_socket.lazy_array_range = ct.LazyArrayRangeFlow(
|
||||||
|
|
|
@ -3,51 +3,64 @@ import typing as typ
|
||||||
import bpy
|
import bpy
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from .. import base
|
from .. import base
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
|
class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
|
||||||
|
"""Describes a single of boundary condition to apply to the half-axis of a simulation domain.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
default: The default boundary condition type.
|
||||||
|
"""
|
||||||
|
|
||||||
socket_type = ct.SocketType.MaxwellBoundCond
|
socket_type = ct.SocketType.MaxwellBoundCond
|
||||||
bl_label = 'Maxwell Bound Face'
|
bl_label = 'Maxwell Bound Cond'
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
default_choice: bpy.props.EnumProperty(
|
default: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||||
name='Bound Face',
|
|
||||||
description='A choice of default boundary face',
|
# Capabilities
|
||||||
items=[
|
## Allow a boundary condition compatible with any of the following axes.
|
||||||
('PML', 'PML', 'Perfectly matched layer'),
|
allow_axes: set[ct.SimSpaceAxis] = bl_cache.BLField(
|
||||||
('PEC', 'PEC', 'Perfect electrical conductor'),
|
{ct.SimSpaceAxis.X, ct.SimSpaceAxis.Y, ct.SimSpaceAxis.Z},
|
||||||
('PMC', 'PMC', 'Perfect magnetic conductor'),
|
)
|
||||||
('PERIODIC', 'Periodic', 'Infinitely periodic layer'),
|
## Present a boundary condition compatible with any of the following axes.
|
||||||
],
|
present_axes: set[ct.SimSpaceAxis] = bl_cache.BLField(
|
||||||
default='PML',
|
{ct.SimSpaceAxis.X, ct.SimSpaceAxis.Y, ct.SimSpaceAxis.Z},
|
||||||
update=(lambda self, context: self.on_prop_changed('default_choice', context)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
col.prop(self, 'default_choice', text='')
|
col.prop(self, self.blfields['default'], text='')
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Computation of Default Value
|
# - FlowKind
|
||||||
####################
|
####################
|
||||||
@property
|
@property
|
||||||
def value(self) -> td.BoundarySpec:
|
def capabilities(self) -> ct.CapabilitiesFlow:
|
||||||
return {
|
return ct.CapabilitiesFlow(
|
||||||
'PML': td.PML(num_layers=12),
|
socket_type=self.socket_type,
|
||||||
'PEC': td.PECBoundary(),
|
active_kind=self.active_kind,
|
||||||
'PMC': td.PMCBoundary(),
|
allow_any=self.allow_axes,
|
||||||
'PERIODIC': td.Periodic(),
|
present_any=self.present_axes,
|
||||||
}[self.default_choice]
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> td.BoundaryEdge:
|
||||||
|
return self.default.tidy3d_boundary_edge
|
||||||
|
|
||||||
@value.setter
|
@value.setter
|
||||||
def value(self, value: typ.Literal['PML', 'PEC', 'PMC', 'PERIODIC']) -> None:
|
def value(self, value: ct.BoundCondType) -> None:
|
||||||
self.default_choice = value
|
self.default = value
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -56,10 +69,23 @@ class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
|
||||||
class MaxwellBoundCondSocketDef(base.SocketDef):
|
class MaxwellBoundCondSocketDef(base.SocketDef):
|
||||||
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundCond
|
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundCond
|
||||||
|
|
||||||
default_choice: typ.Literal['PML', 'PEC', 'PMC', 'PERIODIC'] = 'PML'
|
default: ct.BoundCondType = ct.BoundCondType.Pml
|
||||||
|
allow_axes: set[ct.SimSpaceAxis] = {
|
||||||
|
ct.SimSpaceAxis.X,
|
||||||
|
ct.SimSpaceAxis.Y,
|
||||||
|
ct.SimSpaceAxis.Z,
|
||||||
|
}
|
||||||
|
present_axes: set[ct.SimSpaceAxis] = {
|
||||||
|
ct.SimSpaceAxis.X,
|
||||||
|
ct.SimSpaceAxis.Y,
|
||||||
|
ct.SimSpaceAxis.Z,
|
||||||
|
}
|
||||||
|
|
||||||
def init(self, bl_socket: MaxwellBoundCondBLSocket) -> None:
|
def init(self, bl_socket: MaxwellBoundCondBLSocket) -> None:
|
||||||
bl_socket.value = self.default_choice
|
bl_socket.default = self.default
|
||||||
|
|
||||||
|
bl_socket.allow_axes = self.allow_axes
|
||||||
|
bl_socket.present_axes = self.present_axes
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,123 +1,101 @@
|
||||||
|
"""Implements the `MaxwellBoundCondsBLSocket` socket."""
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from .. import base
|
from .. import base
|
||||||
|
|
||||||
BOUND_FACE_ITEMS = [
|
log = logger.get(__name__)
|
||||||
('PML', 'PML', 'Perfectly matched layer'),
|
|
||||||
('PEC', 'PEC', 'Perfect electrical conductor'),
|
|
||||||
('PMC', 'PMC', 'Perfect magnetic conductor'),
|
|
||||||
('PERIODIC', 'Periodic', 'Infinitely periodic layer'),
|
|
||||||
]
|
|
||||||
BOUND_MAP = {
|
|
||||||
'PML': td.PML(),
|
|
||||||
'PEC': td.PECBoundary(),
|
|
||||||
'PMC': td.PMCBoundary(),
|
|
||||||
'PERIODIC': td.Periodic(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
|
class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
|
||||||
|
"""Describes a set of boundary conditions to apply to a simulation domain.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
show_definition: Toggle to show/hide default per-axis boundary conditions.
|
||||||
|
x_pos: Default boundary condition to apply at the boundary of the sim domain's positive x-axis.
|
||||||
|
x_neg: Default boundary condition to apply at the boundary of the sim domain's negative x-axis.
|
||||||
|
y_pos: Default boundary condition to apply at the boundary of the sim domain's positive y-axis.
|
||||||
|
y_neg: Default boundary condition to apply at the boundary of the sim domain's negative y-axis.
|
||||||
|
z_pos: Default boundary condition to apply at the boundary of the sim domain's positive z-axis.
|
||||||
|
z_neg: Default boundary condition to apply at the boundary of the sim domain's negative z-axis.
|
||||||
|
"""
|
||||||
|
|
||||||
socket_type = ct.SocketType.MaxwellBoundConds
|
socket_type = ct.SocketType.MaxwellBoundConds
|
||||||
bl_label = 'Maxwell Bound Box'
|
bl_label = 'Maxwell Bound Box'
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
show_definition: bpy.props.BoolProperty(
|
show_definition: bool = bl_cache.BLField(False, prop_ui=True)
|
||||||
name='Show Bounds Definition',
|
|
||||||
description='Toggle to show bound faces',
|
|
||||||
default=False,
|
|
||||||
update=(lambda self, context: self.on_prop_changed('show_definition', context)),
|
|
||||||
)
|
|
||||||
|
|
||||||
x_pos: bpy.props.EnumProperty(
|
x_pos: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||||
name='+x Bound Face',
|
x_neg: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||||
description='+x choice of default boundary face',
|
y_pos: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||||
items=BOUND_FACE_ITEMS,
|
y_neg: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||||
default='PML',
|
z_pos: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||||
update=(lambda self, context: self.on_prop_changed('x_pos', context)),
|
z_neg: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||||
)
|
|
||||||
x_neg: bpy.props.EnumProperty(
|
|
||||||
name='-x Bound Face',
|
|
||||||
description='-x choice of default boundary face',
|
|
||||||
items=BOUND_FACE_ITEMS,
|
|
||||||
default='PML',
|
|
||||||
update=(lambda self, context: self.on_prop_changed('x_neg', context)),
|
|
||||||
)
|
|
||||||
y_pos: bpy.props.EnumProperty(
|
|
||||||
name='+y Bound Face',
|
|
||||||
description='+y choice of default boundary face',
|
|
||||||
items=BOUND_FACE_ITEMS,
|
|
||||||
default='PML',
|
|
||||||
update=(lambda self, context: self.on_prop_changed('y_pos', context)),
|
|
||||||
)
|
|
||||||
y_neg: bpy.props.EnumProperty(
|
|
||||||
name='-y Bound Face',
|
|
||||||
description='-y choice of default boundary face',
|
|
||||||
items=BOUND_FACE_ITEMS,
|
|
||||||
default='PML',
|
|
||||||
update=(lambda self, context: self.on_prop_changed('y_neg', context)),
|
|
||||||
)
|
|
||||||
z_pos: bpy.props.EnumProperty(
|
|
||||||
name='+z Bound Face',
|
|
||||||
description='+z choice of default boundary face',
|
|
||||||
items=BOUND_FACE_ITEMS,
|
|
||||||
default='PML',
|
|
||||||
update=(lambda self, context: self.on_prop_changed('z_pos', context)),
|
|
||||||
)
|
|
||||||
z_neg: bpy.props.EnumProperty(
|
|
||||||
name='-z Bound Face',
|
|
||||||
description='-z choice of default boundary face',
|
|
||||||
items=BOUND_FACE_ITEMS,
|
|
||||||
default='PML',
|
|
||||||
update=(lambda self, context: self.on_prop_changed('z_neg', context)),
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_label_row(self, row: bpy.types.UILayout, text) -> None:
|
def draw_label_row(self, row: bpy.types.UILayout, text) -> None:
|
||||||
row.label(text=text)
|
row.label(text=text)
|
||||||
row.prop(self, 'show_definition', toggle=True, text='', icon='MOD_LENGTH')
|
row.prop(
|
||||||
|
self,
|
||||||
|
self.blfields['show_definition'],
|
||||||
|
toggle=True,
|
||||||
|
text='',
|
||||||
|
icon=ct.Icon.ToggleSocketInfo,
|
||||||
|
)
|
||||||
|
|
||||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
if not self.show_definition:
|
if self.show_definition:
|
||||||
return
|
for axis in ['x', 'y', 'z']:
|
||||||
|
row = col.row(align=False)
|
||||||
|
split = row.split(factor=0.2, align=False)
|
||||||
|
|
||||||
for axis in ['x', 'y', 'z']:
|
_col = split.column(align=True)
|
||||||
row = col.row(align=False)
|
_col.alignment = 'RIGHT'
|
||||||
split = row.split(factor=0.2, align=False)
|
_col.label(text=axis + ' -')
|
||||||
|
_col.label(text=' +')
|
||||||
|
|
||||||
_col = split.column(align=True)
|
_col = split.column(align=True)
|
||||||
_col.alignment = 'RIGHT'
|
_col.prop(self, self.blfields[axis + '_neg'], text='')
|
||||||
_col.label(text=axis + ' -')
|
_col.prop(self, self.blfields[axis + '_pos'], text='')
|
||||||
_col.label(text=' +')
|
|
||||||
|
|
||||||
_col = split.column(align=True)
|
|
||||||
_col.prop(self, axis + '_neg', text='')
|
|
||||||
_col.prop(self, axis + '_pos', text='')
|
|
||||||
|
|
||||||
draw_value_array = draw_value
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Computation of Default Value
|
# - Computation of Default Value
|
||||||
####################
|
####################
|
||||||
@property
|
@property
|
||||||
def value(self) -> td.BoundarySpec:
|
def value(self) -> td.BoundarySpec:
|
||||||
|
"""Compute a user-defined default value for simulation boundary conditions, from certain common/sensible options.
|
||||||
|
|
||||||
|
Each half-axis has a selection pulled from `ct.BoundCondType`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A usable `tidy3d` boundary specification.
|
||||||
|
"""
|
||||||
|
log.debug(
|
||||||
|
'%s|%s: Computing default value for Boundary Conditions',
|
||||||
|
self.node.sim_node_name,
|
||||||
|
self.bl_label,
|
||||||
|
)
|
||||||
return td.BoundarySpec(
|
return td.BoundarySpec(
|
||||||
x=td.Boundary(
|
x=td.Boundary(
|
||||||
plus=BOUND_MAP[self.x_pos],
|
plus=self.x_pos.tidy3d_boundary_edge,
|
||||||
minus=BOUND_MAP[self.x_neg],
|
minus=self.x_neg.tidy3d_boundary_edge,
|
||||||
),
|
),
|
||||||
y=td.Boundary(
|
y=td.Boundary(
|
||||||
plus=BOUND_MAP[self.y_pos],
|
plus=self.y_pos.tidy3d_boundary_edge,
|
||||||
minus=BOUND_MAP[self.y_neg],
|
minus=self.y_neg.tidy3d_boundary_edge,
|
||||||
),
|
),
|
||||||
z=td.Boundary(
|
z=td.Boundary(
|
||||||
plus=BOUND_MAP[self.z_pos],
|
plus=self.z_pos.tidy3d_boundary_edge,
|
||||||
minus=BOUND_MAP[self.z_neg],
|
minus=self.z_neg.tidy3d_boundary_edge,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -128,8 +106,20 @@ class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
|
||||||
class MaxwellBoundCondsSocketDef(base.SocketDef):
|
class MaxwellBoundCondsSocketDef(base.SocketDef):
|
||||||
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundConds
|
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundConds
|
||||||
|
|
||||||
|
default_x_pos: ct.BoundCondType = ct.BoundCondType.Pml
|
||||||
|
default_x_neg: ct.BoundCondType = ct.BoundCondType.Pml
|
||||||
|
default_y_pos: ct.BoundCondType = ct.BoundCondType.Pml
|
||||||
|
default_y_neg: ct.BoundCondType = ct.BoundCondType.Pml
|
||||||
|
default_z_pos: ct.BoundCondType = ct.BoundCondType.Pml
|
||||||
|
default_z_neg: ct.BoundCondType = ct.BoundCondType.Pml
|
||||||
|
|
||||||
def init(self, bl_socket: MaxwellBoundCondsBLSocket) -> None:
|
def init(self, bl_socket: MaxwellBoundCondsBLSocket) -> None:
|
||||||
pass
|
bl_socket.x_pos = self.default_x_pos
|
||||||
|
bl_socket.x_neg = self.default_x_neg
|
||||||
|
bl_socket.y_pos = self.default_y_pos
|
||||||
|
bl_socket.y_neg = self.default_y_neg
|
||||||
|
bl_socket.z_pos = self.default_z_pos
|
||||||
|
bl_socket.z_neg = self.default_z_neg
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from .. import base
|
from .. import base
|
||||||
|
|
||||||
|
@ -16,6 +15,8 @@ class MaxwellSourceSocketDef(base.SocketDef):
|
||||||
|
|
||||||
is_list: bool = False
|
is_list: bool = False
|
||||||
|
|
||||||
|
## TODO: capabilities() to require source sockets
|
||||||
|
|
||||||
def init(self, bl_socket: MaxwellSourceBLSocket) -> None:
|
def init(self, bl_socket: MaxwellSourceBLSocket) -> None:
|
||||||
if self.is_list:
|
if self.is_list:
|
||||||
bl_socket.active_kind = ct.FlowKind.Array
|
bl_socket.active_kind = ct.FlowKind.Array
|
||||||
|
|
|
@ -96,6 +96,23 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
|
|
||||||
new_task_name: str = bl_cache.BLField('', prop_ui=True)
|
new_task_name: str = bl_cache.BLField('', prop_ui=True)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Property Changes
|
||||||
|
####################
|
||||||
|
def on_socket_prop_changed(self, prop_name: str) -> None:
|
||||||
|
if prop_name in [
|
||||||
|
'api_key',
|
||||||
|
'existing_folder_id',
|
||||||
|
'existing_task_id',
|
||||||
|
'new_task_name',
|
||||||
|
'should_exist',
|
||||||
|
]:
|
||||||
|
self.existing_folder_id = bl_cache.Signal.ResetEnumItems
|
||||||
|
self.existing_task_id = bl_cache.Signal.ResetEnumItems
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - FlowKinds
|
||||||
|
####################
|
||||||
@property
|
@property
|
||||||
def capabilities(self) -> ct.CapabilitiesFlow:
|
def capabilities(self) -> ct.CapabilitiesFlow:
|
||||||
return ct.CapabilitiesFlow(
|
return ct.CapabilitiesFlow(
|
||||||
|
@ -122,7 +139,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
return (self.new_task_name, cloud_folder)
|
return (self.new_task_name, cloud_folder)
|
||||||
|
|
||||||
# No Task Selected: Return None
|
# No Task Selected: Return None
|
||||||
if self.existing_task_id == 'NONE':
|
if self.existing_task_id is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Retrieve Cloud Task
|
# Retrieve Cloud Task
|
||||||
|
@ -135,7 +152,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
|
|
||||||
return cloud_task
|
return cloud_task
|
||||||
|
|
||||||
return None
|
return ct.FlowSignal.FlowPending
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Searchers
|
# - Searchers
|
||||||
|
@ -158,7 +175,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def search_cloud_tasks(self) -> list[ct.BLEnumElement]:
|
def search_cloud_tasks(self) -> list[ct.BLEnumElement]:
|
||||||
if self.existing_folder_id == 'NONE' or not tdcloud.IS_AUTHENTICATED:
|
if self.existing_folder_id is None or not tdcloud.IS_AUTHENTICATED:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Get Cloud Folder
|
# Get Cloud Folder
|
||||||
|
@ -221,10 +238,6 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
def on_prepare_new_task(self):
|
def on_prepare_new_task(self):
|
||||||
self.should_exist = False
|
self.should_exist = False
|
||||||
|
|
||||||
def on_cloud_updated(self):
|
|
||||||
self.existing_folder_id = bl_cache.Signal.ResetEnumItems
|
|
||||||
self.existing_task_id = bl_cache.Signal.ResetEnumItems
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -13,6 +13,7 @@ def prefix_values_with(prefix: str) -> type[enum.Enum]:
|
||||||
Returns:
|
Returns:
|
||||||
A new StrEnum class with altered member values.
|
A new StrEnum class with altered member values.
|
||||||
"""
|
"""
|
||||||
|
## TODO: DO NOT USE FOR ENUMS WITH METHODS
|
||||||
|
|
||||||
def _decorator(cls: enum.StrEnum):
|
def _decorator(cls: enum.StrEnum):
|
||||||
new_members = {
|
new_members = {
|
||||||
|
|
|
@ -547,6 +547,9 @@ class BLField:
|
||||||
self._str_cb = str_cb
|
self._str_cb = str_cb
|
||||||
self._enum_cb = enum_cb
|
self._enum_cb = enum_cb
|
||||||
|
|
||||||
|
## Type Coercion
|
||||||
|
self._coerce_output_to = None
|
||||||
|
|
||||||
## Vector/Matrix Identity
|
## Vector/Matrix Identity
|
||||||
## -> Matrix Shape assists in the workaround for Matrix Display Bug
|
## -> Matrix Shape assists in the workaround for Matrix Display Bug
|
||||||
self._is_vector = False
|
self._is_vector = False
|
||||||
|
@ -797,7 +800,11 @@ class BLField:
|
||||||
}
|
}
|
||||||
|
|
||||||
## StrEnum
|
## StrEnum
|
||||||
elif inspect.isclass(AttrType) and issubclass(AttrType, enum.StrEnum):
|
elif (
|
||||||
|
inspect.isclass(AttrType)
|
||||||
|
and issubclass(AttrType, enum.StrEnum)
|
||||||
|
and self._enum_cb is None
|
||||||
|
):
|
||||||
default_value = self._default_value
|
default_value = self._default_value
|
||||||
BLProp = bpy.props.EnumProperty
|
BLProp = bpy.props.EnumProperty
|
||||||
kwargs_prop |= {
|
kwargs_prop |= {
|
||||||
|
@ -814,9 +821,14 @@ class BLField:
|
||||||
}
|
}
|
||||||
if self._enum_many:
|
if self._enum_many:
|
||||||
kwargs_prop['options'].add('ENUM_FLAG')
|
kwargs_prop['options'].add('ENUM_FLAG')
|
||||||
|
self._coerce_output_to = AttrType
|
||||||
|
|
||||||
## Dynamic Enum
|
## Dynamic Enum
|
||||||
elif AttrType is enum.Enum and self._enum_cb is not None:
|
elif (
|
||||||
|
AttrType is enum.Enum
|
||||||
|
or (inspect.isclass(AttrType) and issubclass(AttrType, enum.StrEnum))
|
||||||
|
and self._enum_cb is not None
|
||||||
|
):
|
||||||
if self._default_value is not None:
|
if self._default_value is not None:
|
||||||
msg = 'When using dynamic enum, default value must be None'
|
msg = 'When using dynamic enum, default value must be None'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
@ -828,6 +840,8 @@ class BLField:
|
||||||
}
|
}
|
||||||
if self._enum_many:
|
if self._enum_many:
|
||||||
kwargs_prop['options'].add('ENUM_FLAG')
|
kwargs_prop['options'].add('ENUM_FLAG')
|
||||||
|
if AttrType is not enum.Enum:
|
||||||
|
self._coerce_output_to = AttrType
|
||||||
|
|
||||||
## BL Reference
|
## BL Reference
|
||||||
elif AttrType in typ.get_args(ct.BLIDStruct):
|
elif AttrType in typ.get_args(ct.BLIDStruct):
|
||||||
|
@ -888,6 +902,9 @@ class BLField:
|
||||||
def __get__(
|
def __get__(
|
||||||
self, bl_instance: BLInstance | None, owner: type[BLInstance]
|
self, bl_instance: BLInstance | None, owner: type[BLInstance]
|
||||||
) -> typ.Any:
|
) -> typ.Any:
|
||||||
|
if bl_instance is None:
|
||||||
|
return None
|
||||||
|
|
||||||
value = self._cached_bl_property.__get__(bl_instance, owner)
|
value = self._cached_bl_property.__get__(bl_instance, owner)
|
||||||
|
|
||||||
# enum.Enum: Cast Auto-Injected Dynamic Enum 'NONE' -> None
|
# enum.Enum: Cast Auto-Injected Dynamic Enum 'NONE' -> None
|
||||||
|
@ -913,7 +930,7 @@ class BLField:
|
||||||
## -> Reject modernity. Return to tuple[].
|
## -> Reject modernity. Return to tuple[].
|
||||||
if self._is_vector:
|
if self._is_vector:
|
||||||
## -> tuple()ify the np.array to respect tuple[] type annotation.
|
## -> tuple()ify the np.array to respect tuple[] type annotation.
|
||||||
return tuple(np.array(value))
|
return tuple(value)
|
||||||
|
|
||||||
if self._is_matrix:
|
if self._is_matrix:
|
||||||
# Matrix Display Bug: Correctly Read Row-Major Values w/Reshape
|
# Matrix Display Bug: Correctly Read Row-Major Values w/Reshape
|
||||||
|
@ -921,6 +938,13 @@ class BLField:
|
||||||
map(tuple, np.array(value).flatten().reshape(self._matrix_shape))
|
map(tuple, np.array(value).flatten().reshape(self._matrix_shape))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Coerce Output
|
||||||
|
## -> Mainly useful for getting the "real" StrEnum back.
|
||||||
|
if self._coerce_output_to is not None and value is not None:
|
||||||
|
if self._enum_many:
|
||||||
|
return {self._coerce_output_to(v) for v in value}
|
||||||
|
return self._coerce_output_to(value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __set__(self, bl_instance: BLInstance | None, value: typ.Any) -> None:
|
def __set__(self, bl_instance: BLInstance | None, value: typ.Any) -> None:
|
||||||
|
|
|
@ -25,6 +25,10 @@ from pydantic_core import core_schema as pyd_core_schema
|
||||||
|
|
||||||
from blender_maxwell import contracts as ct
|
from blender_maxwell import contracts as ct
|
||||||
|
|
||||||
|
from . import logger
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
SympyType = (
|
SympyType = (
|
||||||
sp.Basic
|
sp.Basic
|
||||||
| sp.Expr
|
| sp.Expr
|
||||||
|
@ -47,21 +51,42 @@ class MathType(enum.StrEnum):
|
||||||
Real = enum.auto()
|
Real = enum.auto()
|
||||||
Complex = enum.auto()
|
Complex = enum.auto()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def combine(*mathtypes: list[typ.Self]) -> typ.Self:
|
def combine(*mathtypes: list[typ.Self]) -> typ.Self:
|
||||||
if MathType.Complex in mathtypes:
|
if MathType.Complex in mathtypes:
|
||||||
return MathType.Complex
|
return MathType.Complex
|
||||||
elif MathType.Real in mathtypes:
|
if MathType.Real in mathtypes:
|
||||||
return MathType.Real
|
return MathType.Real
|
||||||
elif MathType.Rational in mathtypes:
|
if MathType.Rational in mathtypes:
|
||||||
return MathType.Rational
|
return MathType.Rational
|
||||||
elif MathType.Integer in mathtypes:
|
if MathType.Integer in mathtypes:
|
||||||
return MathType.Integer
|
return MathType.Integer
|
||||||
elif MathType.Bool in mathtypes:
|
if MathType.Bool in mathtypes:
|
||||||
return MathType.Bool
|
return MathType.Bool
|
||||||
|
|
||||||
|
msg = f"Can't combine mathtypes {mathtypes}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
def is_compatible(self, other: typ.Self) -> bool:
|
||||||
|
MT = MathType
|
||||||
|
return (
|
||||||
|
other
|
||||||
|
in {
|
||||||
|
MT.Bool: [MT.Bool],
|
||||||
|
MT.Integer: [MT.Integer],
|
||||||
|
MT.Rational: [MT.Integer, MT.Rational],
|
||||||
|
MT.Real: [MT.Integer, MT.Rational, MT.Real],
|
||||||
|
MT.Complex: [MT.Integer, MT.Rational, MT.Real, MT.Complex],
|
||||||
|
}[self]
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_expr(sp_obj: SympyType) -> type:
|
def from_expr(sp_obj: SympyType) -> type:
|
||||||
## TODO: Support for sp.Matrix
|
if isinstance(sp_obj, sp.MatrixBase):
|
||||||
|
return MathType.combine(
|
||||||
|
*[MathType.from_expr(v) for v in sp.flatten(sp_obj)]
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(sp_obj, sp.logic.boolalg.Boolean):
|
if isinstance(sp_obj, sp.logic.boolalg.Boolean):
|
||||||
return MathType.Bool
|
return MathType.Bool
|
||||||
if sp_obj.is_integer:
|
if sp_obj.is_integer:
|
||||||
|
@ -172,7 +197,7 @@ class NumberSize1D(enum.StrEnum):
|
||||||
None: NS.Scalar,
|
None: NS.Scalar,
|
||||||
(2,): NS.Vec2,
|
(2,): NS.Vec2,
|
||||||
(3,): NS.Vec3,
|
(3,): NS.Vec3,
|
||||||
(4,): NS.Vec3,
|
(4,): NS.Vec4,
|
||||||
}[shape]
|
}[shape]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -182,7 +207,7 @@ class NumberSize1D(enum.StrEnum):
|
||||||
NS.Scalar: None,
|
NS.Scalar: None,
|
||||||
NS.Vec2: (2,),
|
NS.Vec2: (2,),
|
||||||
NS.Vec3: (3,),
|
NS.Vec3: (3,),
|
||||||
NS.Vec3: (4,),
|
NS.Vec4: (4,),
|
||||||
}[self]
|
}[self]
|
||||||
|
|
||||||
|
|
||||||
|
@ -702,7 +727,6 @@ def scale_to_unit(sp_obj: SympyType, unit: spu.Quantity) -> Number:
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the result of unit-conversion and -stripping still has units, as determined by `uses_units()`.
|
ValueError: If the result of unit-conversion and -stripping still has units, as determined by `uses_units()`.
|
||||||
"""
|
"""
|
||||||
## TODO: An LFU cache could do better than an LRU.
|
|
||||||
unitless_expr = spu.convert_to(sp_obj, unit) / unit
|
unitless_expr = spu.convert_to(sp_obj, unit) / unit
|
||||||
if not uses_units(unitless_expr):
|
if not uses_units(unitless_expr):
|
||||||
return unitless_expr
|
return unitless_expr
|
||||||
|
@ -739,7 +763,7 @@ def unit_str_to_unit(unit_str: str) -> Unit | None:
|
||||||
if unit_str in _UNIT_STR_MAP:
|
if unit_str in _UNIT_STR_MAP:
|
||||||
return _UNIT_STR_MAP[unit_str]
|
return _UNIT_STR_MAP[unit_str]
|
||||||
|
|
||||||
msg = 'No valid unit for unit string {unit_str}'
|
msg = f'No valid unit for unit string {unit_str}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
@ -802,7 +826,7 @@ class PhysicalType(enum.StrEnum):
|
||||||
# Global
|
# Global
|
||||||
PT.Time: Dims.time,
|
PT.Time: Dims.time,
|
||||||
PT.Angle: Dims.angle,
|
PT.Angle: Dims.angle,
|
||||||
PT.SolidAngle: Dims.steradian, ## MISSING
|
PT.SolidAngle: spu.steradian.dimension, ## MISSING
|
||||||
PT.Freq: Dims.frequency,
|
PT.Freq: Dims.frequency,
|
||||||
PT.AngFreq: Dims.angle * Dims.frequency,
|
PT.AngFreq: Dims.angle * Dims.frequency,
|
||||||
# Cartesian
|
# Cartesian
|
||||||
|
@ -836,7 +860,7 @@ class PhysicalType(enum.StrEnum):
|
||||||
PT.HField: Dims.current / Dims.length,
|
PT.HField: Dims.current / Dims.length,
|
||||||
# Luminal
|
# Luminal
|
||||||
PT.LumIntensity: Dims.luminous_intensity,
|
PT.LumIntensity: Dims.luminous_intensity,
|
||||||
PT.LumFlux: Dims.luminous_intensity * Dims.steradian,
|
PT.LumFlux: Dims.luminous_intensity * spu.steradian.dimension,
|
||||||
PT.Illuminance: Dims.luminous_intensity / Dims.length**2,
|
PT.Illuminance: Dims.luminous_intensity / Dims.length**2,
|
||||||
# Optics
|
# Optics
|
||||||
PT.OrdinaryWaveVector: Dims.frequency,
|
PT.OrdinaryWaveVector: Dims.frequency,
|
||||||
|
@ -1263,12 +1287,23 @@ def convert_to_unit_system(sp_obj: SympyExpr, unit_system: UnitSystem) -> SympyE
|
||||||
return spu.convert_to(sp_obj, _flat_unit_system_units(unit_system))
|
return spu.convert_to(sp_obj, _flat_unit_system_units(unit_system))
|
||||||
|
|
||||||
|
|
||||||
|
def strip_unit_system(sp_obj: SympyExpr, unit_system: UnitSystem) -> SympyExpr:
|
||||||
|
"""Strip units occurring in the given unit system from the expression.
|
||||||
|
|
||||||
|
Unit stripping is a "dumb" operation: "Substitute any `sympy` object in `unit_system.values()` with `1`".
|
||||||
|
Obviously, the semantic correctness of this operation depends entirely on _the units adding no semantic meaning to the expression_.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
You should probably use `scale_to_unit_system()` or `convert_to_unit_system()`.
|
||||||
|
"""
|
||||||
|
return sp_obj.subs({unit: 1 for unit in unit_system.values()})
|
||||||
|
|
||||||
|
|
||||||
def scale_to_unit_system(
|
def scale_to_unit_system(
|
||||||
sp_obj: SympyExpr, unit_system: UnitSystem, use_jax_array: bool = False
|
sp_obj: SympyExpr, unit_system: UnitSystem, use_jax_array: bool = False
|
||||||
) -> int | float | complex | tuple | jax.Array:
|
) -> int | float | complex | tuple | jax.Array:
|
||||||
"""Convert an expression to the units of a given unit system, then strip all units of the unit system.
|
"""Convert an expression to the units of a given unit system, then strip all units of the unit system.
|
||||||
|
|
||||||
Unit stripping is "dumb": Substitute any `sympy` object in `unit_system.values()` with `1`.
|
|
||||||
Afterwards, it is converted to an appropriate Python type.
|
Afterwards, it is converted to an appropriate Python type.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
@ -1287,8 +1322,6 @@ def scale_to_unit_system(
|
||||||
If the returned type is array-like, and `use_jax_array` is specified, then (and **only** then) will a `jax.Array` be returned instead of a nested `tuple`.
|
If the returned type is array-like, and `use_jax_array` is specified, then (and **only** then) will a `jax.Array` be returned instead of a nested `tuple`.
|
||||||
"""
|
"""
|
||||||
return sympy_to_python(
|
return sympy_to_python(
|
||||||
convert_to_unit_system(sp_obj, unit_system).subs(
|
strip_unit_system(convert_to_unit_system(sp_obj, unit_system), unit_system),
|
||||||
{unit: 1 for unit in unit_system.values()}
|
|
||||||
),
|
|
||||||
use_jax_array=use_jax_array,
|
use_jax_array=use_jax_array,
|
||||||
)
|
)
|
||||||
|
|
|
@ -81,6 +81,7 @@ _NaivelyEncodableTypeSet = frozenset(typ.get_args(NaivelyEncodableType))
|
||||||
class TypeID(enum.StrEnum):
|
class TypeID(enum.StrEnum):
|
||||||
Complex: str = '!type=complex'
|
Complex: str = '!type=complex'
|
||||||
SympyType: str = '!type=sympytype'
|
SympyType: str = '!type=sympytype'
|
||||||
|
SympyExpr: str = '!type=sympyexpr'
|
||||||
SocketDef: str = '!type=socketdef'
|
SocketDef: str = '!type=socketdef'
|
||||||
ManagedObj: str = '!type=managedobj'
|
ManagedObj: str = '!type=managedobj'
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue