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
|
||||
- [ ] Wave Constant
|
||||
- Bounds
|
||||
- [ ] Boundary Conds
|
||||
- [ ] PML
|
||||
- [ ] PEC
|
||||
- [ ] PMC
|
||||
- [ ] Bloch
|
||||
- [ ] Absorbing
|
||||
- [x] Wave Constant
|
||||
- Sources
|
||||
- [ ] Temporal Shapes / Continuous Wave Temporal Shape
|
||||
- [ ] Temporal Shapes / Symbolic Temporal Shape
|
||||
|
@ -18,8 +11,8 @@
|
|||
- [ ] Data File Import
|
||||
- [ ] DataFit Medium
|
||||
- Monitors
|
||||
- [ ] EH Field
|
||||
- [ ] Power Flux
|
||||
- [x] EH Field
|
||||
- [x] Power Flux
|
||||
- [ ] Permittivity
|
||||
- [ ] Diffraction
|
||||
- Structures
|
||||
|
@ -49,9 +42,9 @@
|
|||
- Integration
|
||||
- [ ] Simulation and Analysis of Maxim's Cavity
|
||||
- Constants
|
||||
- [ ] Number Constant
|
||||
- [ ] Vector Constant
|
||||
- [ ] Physical Constant
|
||||
- [x] Number Constant
|
||||
- [x] Vector Constant
|
||||
- [x] Physical Constant
|
||||
|
||||
- [ ] 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.
|
||||
|
||||
- [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.
|
||||
- [ ] Implement support for additional symbols via `Expr`.
|
||||
- [x] Math / Filter Math
|
||||
|
@ -81,8 +74,6 @@
|
|||
|
||||
## Inputs
|
||||
- [x] Wave Constant
|
||||
- [ ] Fix the LazyValueRange (again!)
|
||||
- [ ] Document
|
||||
- [x] Scene
|
||||
- [ ] 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.
|
||||
|
@ -90,14 +81,14 @@
|
|||
- [x] Constants / Expr Constant
|
||||
- See IDEAS.
|
||||
- [x] Constants / Number Constant
|
||||
- [ ] Fix non-integer sockets
|
||||
- [ ] Constants / Vector Constant
|
||||
- [ ] Constants / Physical Constant
|
||||
- [x] Constants / Vector Constant
|
||||
- [x] Constants / Physical Constant
|
||||
- [x] Constants / Scientific Constant
|
||||
- [ ] 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.
|
||||
- [x] Constants / Blender Constant
|
||||
- [ ] Constants / Blender Constant
|
||||
- [ ] Fix it!
|
||||
|
||||
- [ ] Web / Tidy3D Web Importer
|
||||
|
@ -200,13 +191,13 @@
|
|||
|
||||
## Bounds
|
||||
- [x] Boundary Conds
|
||||
- [ ] Boundary Cond / PML Bound Face
|
||||
- [ ] Dropdown for "Normal" and "Stable"
|
||||
- [ ] Boundary Cond / PEC Bound Face
|
||||
- [ ] Boundary Cond / PMC Bound Face
|
||||
- [ ] Boundary Cond / Bloch Bound Face
|
||||
- [ ] Implement "simple" mode aka "periodic" mode in Tidy3D
|
||||
- [ ] Boundary Cond / Absorbing Bound Face
|
||||
- [x] Boundary Cond / PML Bound Cond
|
||||
- [ ] 1D plot visualizing the effect of parameters on a 1D wave function
|
||||
- [x] Boundary Cond / Bloch Bound Cond
|
||||
- [x] Implement "simple" mode aka "periodic" mode in Tidy3D
|
||||
- [ ] 1D plot visualizing the effect of parameters on a 1D wave function
|
||||
- [x] Boundary Cond / Absorbing Bound Cond
|
||||
- [ ] 1D plot visualizing the effect of parameters on a 1D wave function
|
||||
|
||||
## Monitors
|
||||
- [x] EH Field Monitor
|
||||
|
|
|
@ -168,13 +168,13 @@ class GeoNodes(enum.StrEnum):
|
|||
GN.StructurePrimitiveCapsule: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.StructurePrimitiveCone: GN_INTERNAL_STRUCTURES_PATH,
|
||||
## Monitor
|
||||
GN.MonitorEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorPowerFlux: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorEpsTensor: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorDiffraction: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorProjCartEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorProjAngEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorProjKSpaceEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorEHField: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorPowerFlux: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorEpsTensor: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorDiffraction: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorProjCartEHField: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorProjAngEHField: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorProjKSpaceEHField: GN_INTERNAL_MONITORS_PATH,
|
||||
## Simulation
|
||||
GN.SimulationSimDomain: 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:
|
||||
return bpy.data.node_groups[geonodes]
|
||||
|
||||
filename = geonodes
|
||||
filename = str(geonodes)
|
||||
filepath = str(geonodes.parent_path / (geonodes + '.blend') / 'NodeTree' / geonodes)
|
||||
directory = filepath.removesuffix(geonodes)
|
||||
log.info(
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
from blender_maxwell.contracts import (
|
||||
BLClass,
|
||||
BLColorRGBA,
|
||||
BLEnumElement,
|
||||
BLEnumID,
|
||||
BLIcon,
|
||||
BLIconSet,
|
||||
BLIDStruct,
|
||||
BLKeymapItem,
|
||||
BLModifierType,
|
||||
BLNodeTreeInterfaceID,
|
||||
BLOperatorStatus,
|
||||
BLPropFlag,
|
||||
BLRegionType,
|
||||
BLSpaceType,
|
||||
KeymapItemDef,
|
||||
ManagedObjName,
|
||||
OperatorType,
|
||||
PanelType,
|
||||
PresetName,
|
||||
SocketName,
|
||||
addon,
|
||||
BLClass,
|
||||
BLColorRGBA,
|
||||
BLEnumElement,
|
||||
BLEnumID,
|
||||
BLIcon,
|
||||
BLIconSet,
|
||||
BLIDStruct,
|
||||
BLKeymapItem,
|
||||
BLModifierType,
|
||||
BLNodeTreeInterfaceID,
|
||||
BLOperatorStatus,
|
||||
BLPropFlag,
|
||||
BLRegionType,
|
||||
BLSpaceType,
|
||||
KeymapItemDef,
|
||||
ManagedObjName,
|
||||
OperatorType,
|
||||
PanelType,
|
||||
PresetName,
|
||||
SocketName,
|
||||
addon,
|
||||
)
|
||||
|
||||
from .bl_socket_types import BLSocketInfo, BLSocketType
|
||||
|
@ -27,19 +27,20 @@ from .category_labels import NODE_CAT_LABELS
|
|||
from .category_types import NodeCategory
|
||||
from .flow_events import FlowEvent
|
||||
from .flow_kinds import (
|
||||
ArrayFlow,
|
||||
CapabilitiesFlow,
|
||||
FlowKind,
|
||||
InfoFlow,
|
||||
LazyArrayRangeFlow,
|
||||
LazyValueFuncFlow,
|
||||
ParamsFlow,
|
||||
ValueFlow,
|
||||
ArrayFlow,
|
||||
CapabilitiesFlow,
|
||||
FlowKind,
|
||||
InfoFlow,
|
||||
LazyArrayRangeFlow,
|
||||
LazyValueFuncFlow,
|
||||
ParamsFlow,
|
||||
ValueFlow,
|
||||
)
|
||||
from .flow_signals import FlowSignal
|
||||
from .icons import Icon
|
||||
from .mobj_types import ManagedObjType
|
||||
from .node_types import NodeType
|
||||
from .sim_types import BoundCondType, SimSpaceAxis
|
||||
from .socket_colors import SOCKET_COLORS
|
||||
from .socket_types import SocketType
|
||||
from .tree_types import TreeType
|
||||
|
@ -77,6 +78,8 @@ __all__ = [
|
|||
'BLSocketInfo',
|
||||
'BLSocketType',
|
||||
'NodeType',
|
||||
'BoundCondType',
|
||||
'SimSpaceAxis',
|
||||
'NodeCategory',
|
||||
'NODE_CAT_LABELS',
|
||||
'ManagedObjType',
|
||||
|
|
|
@ -25,58 +25,60 @@ class BLSocketInfo:
|
|||
bl_isocket_identifier: spux.ScalarUnitlessRealExpr
|
||||
|
||||
|
||||
@blender_type_enum.prefix_values_with('NodeSocket')
|
||||
class BLSocketType(enum.StrEnum):
|
||||
Virtual = 'Virtual'
|
||||
Virtual = 'NodeSocketVirtual'
|
||||
# Blender
|
||||
Image = 'Image'
|
||||
Shader = 'Shader'
|
||||
Material = 'Material'
|
||||
Geometry = 'Material'
|
||||
Object = 'Object'
|
||||
Collection = 'Collection'
|
||||
Image = 'NodeSocketImage'
|
||||
Shader = 'NodeSocketShader'
|
||||
Material = 'NodeSocketMaterial'
|
||||
Geometry = 'NodeSocketGeometry'
|
||||
Object = 'NodeSocketObject'
|
||||
Collection = 'NodeSocketCollection'
|
||||
# Basic
|
||||
Bool = 'Bool'
|
||||
String = 'String'
|
||||
Menu = 'Menu'
|
||||
Bool = 'NodeSocketBool'
|
||||
String = 'NodeSocketString'
|
||||
Menu = 'NodeSocketMenu'
|
||||
# Float
|
||||
Float = 'Float'
|
||||
FloatUnsigned = 'FloatUnsigned'
|
||||
FloatAngle = 'FloatAngle'
|
||||
FloatDistance = 'FloatDistance'
|
||||
FloatFactor = 'FloatFactor'
|
||||
FloatPercentage = 'FloatPercentage'
|
||||
FloatTime = 'FloatTime'
|
||||
FloatTimeAbsolute = 'FloatTimeAbsolute'
|
||||
Float = 'NodeSocketFloat'
|
||||
FloatUnsigned = 'NodeSocketFloatUnsigned'
|
||||
FloatAngle = 'NodeSocketFloatAngle'
|
||||
FloatDistance = 'NodeSocketFloatDistance'
|
||||
FloatFactor = 'NodeSocketFloatFactor'
|
||||
FloatPercentage = 'NodeSocketFloatPercentage'
|
||||
FloatTime = 'NodeSocketFloatTime'
|
||||
FloatTimeAbsolute = 'NodeSocketFloatTimeAbsolute'
|
||||
# Int
|
||||
Int = 'Int'
|
||||
IntFactor = 'IntFactor'
|
||||
IntPercentage = 'IntPercentage'
|
||||
IntUnsigned = 'IntUnsigned'
|
||||
Int = 'NodeSocketInt'
|
||||
IntFactor = 'NodeSocketIntFactor'
|
||||
IntPercentage = 'NodeSocketIntPercentage'
|
||||
IntUnsigned = 'NodeSocketIntUnsigned'
|
||||
# Vector
|
||||
Color = 'Color'
|
||||
Rotation = 'Rotation'
|
||||
Vector = 'Vector'
|
||||
VectorAcceleration = 'Acceleration'
|
||||
VectorDirection = 'Direction'
|
||||
VectorEuler = 'Euler'
|
||||
VectorTranslation = 'Translation'
|
||||
VectorVelocity = 'Velocity'
|
||||
VectorXYZ = 'XYZ'
|
||||
Color = 'NodeSocketColor'
|
||||
Rotation = 'NodeSocketRotation'
|
||||
Vector = 'NodeSocketVector'
|
||||
VectorAcceleration = 'NodeSocketAcceleration'
|
||||
VectorDirection = 'NodeSocketDirection'
|
||||
VectorEuler = 'NodeSocketEuler'
|
||||
VectorTranslation = 'NodeSocketTranslation'
|
||||
VectorVelocity = 'NodeSocketVelocity'
|
||||
VectorXYZ = 'NodeSocketXYZ'
|
||||
|
||||
@staticmethod
|
||||
def from_bl_isocket(
|
||||
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
||||
) -> typ.Self:
|
||||
return BLSocketType[bl_isocket.bl_socket_idname]
|
||||
return BLSocketType(bl_isocket.bl_socket_idname)
|
||||
|
||||
@staticmethod
|
||||
def info_from_bl_isocket(
|
||||
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
||||
) -> typ.Self:
|
||||
return BLSocketType.from_bl_isocket(bl_isocket).parse(
|
||||
bl_isocket.default_value, bl_isocket.description, bl_isocket.identifier
|
||||
)
|
||||
bl_socket_type = BLSocketType.from_bl_isocket(bl_isocket)
|
||||
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
|
||||
|
@ -288,7 +290,7 @@ class BLSocketType(enum.StrEnum):
|
|||
)
|
||||
|
||||
# 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:
|
||||
default_value = self.mathtype.pytype(bl_default_value)
|
||||
elif description.startswith('2D'):
|
||||
|
|
|
@ -25,7 +25,7 @@ NODE_CAT_LABELS = {
|
|||
NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: 'Primitives',
|
||||
# Bounds/
|
||||
NC.MAXWELLSIM_BOUNDS: 'Bounds',
|
||||
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Bound Conds',
|
||||
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Conds',
|
||||
# Monitors/
|
||||
NC.MAXWELLSIM_MONITORS: 'Monitors',
|
||||
NC.MAXWELLSIM_MONITORS_PROJECTED: 'Projected',
|
||||
|
|
|
@ -13,9 +13,12 @@ import sympy as sp
|
|||
import sympy.physics.units as spu
|
||||
|
||||
from blender_maxwell.utils import extra_sympy_units as spux
|
||||
from blender_maxwell.utils import logger
|
||||
|
||||
from .socket_types import SocketType
|
||||
|
||||
log = logger.get(__name__)
|
||||
|
||||
|
||||
class FlowKind(enum.StrEnum):
|
||||
"""Defines a kind of data that can flow between nodes.
|
||||
|
@ -57,19 +60,22 @@ class FlowKind(enum.StrEnum):
|
|||
Info = enum.auto()
|
||||
|
||||
@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:
|
||||
return spux.sympy_to_python(
|
||||
spux.scale_to_unit(
|
||||
value,
|
||||
unit_system[socket_type],
|
||||
)
|
||||
return spux.scale_to_unit_system(
|
||||
value,
|
||||
unit_system,
|
||||
)
|
||||
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:
|
||||
return value.rescale_to_unit(unit_system[socket_type])
|
||||
return value.rescale_to_unit_system(unit_system)
|
||||
|
||||
msg = 'Tried to scale unknown kind'
|
||||
raise ValueError(msg)
|
||||
|
@ -84,17 +90,28 @@ class CapabilitiesFlow:
|
|||
active_kind: FlowKind
|
||||
|
||||
is_universal: bool = False
|
||||
|
||||
# == Constraint
|
||||
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:
|
||||
return other.is_universal or (
|
||||
self.socket_type == other.socket_type
|
||||
and self.active_kind == other.active_kind
|
||||
# == Constraint
|
||||
and all(
|
||||
name in other.must_match
|
||||
and self.must_match[name] == other.must_match[name]
|
||||
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}'
|
||||
raise ValueError(msg)
|
||||
|
||||
def rescale_to_unit_system(self, unit: spu.Quantity) -> typ.Self:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
####################
|
||||
# - Lazy Value Func
|
||||
|
@ -469,14 +489,13 @@ class LazyArrayRangeFlow:
|
|||
|
||||
# Get Stop Mathtype
|
||||
if isinstance(self.stop, spux.SympyType):
|
||||
stop_mathtype = spux.MathType.from_expr(type(self.stop))
|
||||
stop_mathtype = spux.MathType.from_expr(self.stop)
|
||||
else:
|
||||
stop_mathtype = spux.MathType.from_pytype(type(self.stop))
|
||||
stop_mathtype = spux.MathType.from_pytype(self.stop)
|
||||
|
||||
# Check Equal
|
||||
if start_mathtype != stop_mathtype:
|
||||
msg = "Mathtypes of start and stop don't agree. Please fix!"
|
||||
raise ValueError(msg)
|
||||
return spux.MathType.combine(start_mathtype, stop_mathtype)
|
||||
|
||||
return start_mathtype
|
||||
|
||||
|
@ -525,8 +544,8 @@ class LazyArrayRangeFlow:
|
|||
"""
|
||||
if self.unit is not None:
|
||||
return LazyArrayRangeFlow(
|
||||
start=spu.scale_to_unit(self.start * self.unit, unit),
|
||||
stop=spu.scale_to_unit(self.stop * self.unit, unit),
|
||||
start=spux.scale_to_unit(self.start * self.unit, unit),
|
||||
stop=spux.scale_to_unit(self.stop * self.unit, unit),
|
||||
steps=self.steps,
|
||||
scaling=self.scaling,
|
||||
unit=unit,
|
||||
|
@ -536,6 +555,39 @@ class LazyArrayRangeFlow:
|
|||
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
||||
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
|
||||
####################
|
||||
|
|
|
@ -16,6 +16,7 @@ class FlowSignal(enum.StrEnum):
|
|||
|
||||
"""
|
||||
|
||||
FlowInitializing = enum.auto()
|
||||
FlowPending = enum.auto()
|
||||
NoFlow = enum.auto()
|
||||
|
||||
|
|
|
@ -90,10 +90,8 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
|
|||
BoundConds = enum.auto()
|
||||
## Bounds / Bound Conds
|
||||
PMLBoundCond = enum.auto()
|
||||
PECBoundCond = enum.auto()
|
||||
PMCBoundCond = enum.auto()
|
||||
BlochBoundCond = enum.auto()
|
||||
AbsorbingBoundCond = enum.auto()
|
||||
AdiabAbsorbBoundCond = enum.auto()
|
||||
|
||||
# Monitors
|
||||
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 (bl_object := bpy.data.objects.get(self.name)) is not None:
|
||||
if bl_object.name not in preview_collection().objects:
|
||||
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
||||
preview_collection().objects.link(bl_object)
|
||||
else:
|
||||
msg = 'Managed BLMesh does not exist'
|
||||
raise ValueError(msg)
|
||||
bl_object = bpy.data.objects.get(self.name)
|
||||
if bl_object is None:
|
||||
bl_object = self.bl_object()
|
||||
|
||||
if bl_object.name not in preview_collection().objects:
|
||||
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
||||
preview_collection().objects.link(bl_object)
|
||||
|
||||
def hide_preview(self) -> None:
|
||||
"""Removes the managed Blender object from the preview collection.
|
||||
|
||||
If it's already removed, do nothing.
|
||||
"""
|
||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
||||
if bl_object.name in preview_collection().objects:
|
||||
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
||||
preview_collection().objects.unlink(bl_object)
|
||||
else:
|
||||
msg = 'Managed BLMesh does not exist'
|
||||
raise ValueError(msg)
|
||||
bl_object = bpy.data.objects.get(self.name)
|
||||
if bl_object is not None and bl_object.name in preview_collection().objects:
|
||||
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
||||
preview_collection().objects.unlink(bl_object)
|
||||
|
||||
def bl_select(self) -> None:
|
||||
"""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."""
|
||||
log.info('Unlocking All Nodes in NodeTree "%s"', self.bl_label)
|
||||
for node in self.nodes:
|
||||
if node.type in ['REROUTE', 'FRAME']:
|
||||
continue
|
||||
node.locked = False
|
||||
for bl_socket in [*node.inputs, *node.outputs]:
|
||||
bl_socket.locked = False
|
||||
|
@ -229,7 +231,9 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
|||
@contextlib.contextmanager
|
||||
def repreview_all(self) -> None:
|
||||
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.newly_previewed_nodes = {}
|
||||
|
|
|
@ -1,40 +1,37 @@
|
|||
# from . import kitchen_sink
|
||||
# from . import bounds
|
||||
from . import (
|
||||
analysis,
|
||||
bounds,
|
||||
inputs,
|
||||
mediums,
|
||||
# mediums,
|
||||
monitors,
|
||||
outputs,
|
||||
simulations,
|
||||
sources,
|
||||
structures,
|
||||
utilities,
|
||||
# simulations,
|
||||
# sources,
|
||||
# structures,
|
||||
# utilities,
|
||||
)
|
||||
|
||||
BL_REGISTER = [
|
||||
# *kitchen_sink.BL_REGISTER,
|
||||
*analysis.BL_REGISTER,
|
||||
*inputs.BL_REGISTER,
|
||||
*outputs.BL_REGISTER,
|
||||
*sources.BL_REGISTER,
|
||||
*mediums.BL_REGISTER,
|
||||
*structures.BL_REGISTER,
|
||||
# *bounds.BL_REGISTER,
|
||||
# *sources.BL_REGISTER,
|
||||
# *mediums.BL_REGISTER,
|
||||
# *structures.BL_REGISTER,
|
||||
*bounds.BL_REGISTER,
|
||||
*monitors.BL_REGISTER,
|
||||
*simulations.BL_REGISTER,
|
||||
*utilities.BL_REGISTER,
|
||||
# *simulations.BL_REGISTER,
|
||||
# *utilities.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
# **kitchen_sink.BL_NODES,
|
||||
**analysis.BL_NODES,
|
||||
**inputs.BL_NODES,
|
||||
**outputs.BL_NODES,
|
||||
**sources.BL_NODES,
|
||||
**mediums.BL_NODES,
|
||||
**structures.BL_NODES,
|
||||
# **bounds.BL_NODES,
|
||||
# **sources.BL_NODES,
|
||||
# **mediums.BL_NODES,
|
||||
# **structures.BL_NODES,
|
||||
**bounds.BL_NODES,
|
||||
**monitors.BL_NODES,
|
||||
**simulations.BL_NODES,
|
||||
**utilities.BL_NODES,
|
||||
# **simulations.BL_NODES,
|
||||
# **utilities.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -401,8 +401,8 @@ class MapMathNode(base.MaxwellSimNode):
|
|||
run_on_init=True,
|
||||
)
|
||||
def on_input_changed(self):
|
||||
if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
||||
self.operation = bl_cache.Signal.ResetEnumItems
|
||||
# if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
||||
self.operation = bl_cache.Signal.ResetEnumItems
|
||||
|
||||
@events.on_value_changed(
|
||||
# Trigger
|
||||
|
|
|
@ -20,6 +20,10 @@ FUNCS = {
|
|||
'MUL': lambda exprs: exprs[0] * exprs[1],
|
||||
'DIV': 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
|
||||
|
@ -52,8 +56,8 @@ class OperateMathNode(base.MaxwellSimNode):
|
|||
bl_label = 'Operate Math'
|
||||
|
||||
input_sockets: typ.ClassVar = {
|
||||
'Expr L': sockets.ExprSocketDef(show_info_columns=False),
|
||||
'Expr R': sockets.ExprSocketDef(show_info_columns=False),
|
||||
'Expr L': sockets.ExprSocketDef(),
|
||||
'Expr R': sockets.ExprSocketDef(),
|
||||
}
|
||||
output_sockets: typ.ClassVar = {
|
||||
'Expr': sockets.ExprSocketDef(),
|
||||
|
@ -73,10 +77,12 @@ class OperateMathNode(base.MaxwellSimNode):
|
|||
def search_categories(self) -> list[ct.BLEnumElement]:
|
||||
"""Deduce and return a list of valid categories for the current socket set and input data."""
|
||||
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', kind=ct.FlowKind.Info, optional=True
|
||||
'Expr R',
|
||||
kind=ct.FlowKind.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:
|
||||
categories = [NUMBER_NUMBER]
|
||||
|
||||
## * | Number
|
||||
elif expr_r_info.output_shape is None:
|
||||
categories = []
|
||||
|
||||
## Number | Vector
|
||||
elif (
|
||||
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'),
|
||||
('ATAN2', 'atan2(L,R)', 'atan2(L,R)'),
|
||||
]
|
||||
if self.category in 'Vector | Vector':
|
||||
if self.category == 'Vector | Vector':
|
||||
if items:
|
||||
items += [None]
|
||||
items += [
|
||||
('VEC_VEC_DOT', 'L · R', 'Vector-Vector Product'),
|
||||
('CROSS', 'L x R', 'Cross Product'),
|
||||
('PROJ', 'proj(L, R)', 'Projection'),
|
||||
]
|
||||
if self.category == 'Matrix | Vector':
|
||||
if items:
|
||||
|
@ -364,9 +373,7 @@ class OperateMathNode(base.MaxwellSimNode):
|
|||
'Expr R': ct.FlowKind.Params,
|
||||
},
|
||||
)
|
||||
def compute_params(
|
||||
self, props, input_sockets
|
||||
) -> ct.ParamsFlow | ct.FlowSignal:
|
||||
def compute_params(self, props, input_sockets) -> ct.ParamsFlow | ct.FlowSignal:
|
||||
operation = props['operation']
|
||||
params_l = input_sockets['Expr L']
|
||||
params_r = input_sockets['Expr R']
|
||||
|
|
|
@ -2,8 +2,11 @@ import enum
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
import jaxtyping as jtyp
|
||||
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 extra_sympy_units as spux
|
||||
|
@ -192,7 +195,10 @@ class VizNode(base.MaxwellSimNode):
|
|||
# - Sockets
|
||||
####################
|
||||
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 = {
|
||||
'Preview': sockets.AnySocketDef(),
|
||||
|
@ -221,8 +227,12 @@ class VizNode(base.MaxwellSimNode):
|
|||
## - Mode Searcher
|
||||
#####################
|
||||
@property
|
||||
def data_info(self) -> ct.InfoFlow:
|
||||
return self._compute_input('Expr', kind=ct.FlowKind.Info)
|
||||
def data_info(self) -> ct.InfoFlow | None:
|
||||
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]:
|
||||
if not ct.FlowSignal.check(self.data_info):
|
||||
|
@ -298,7 +308,9 @@ class VizNode(base.MaxwellSimNode):
|
|||
managed_objs={'plot'},
|
||||
props={'viz_mode', 'viz_target', 'colormap'},
|
||||
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,
|
||||
)
|
||||
def on_show_plot(
|
||||
|
|
|
@ -599,22 +599,28 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
It must be currently active.
|
||||
kind: The data flow kind to compute.
|
||||
"""
|
||||
if (bl_socket := self.inputs.get(input_socket_name)) is not None:
|
||||
return (
|
||||
ct.FlowKind.scale_to_unit_system(
|
||||
kind,
|
||||
bl_socket.compute_data(kind=kind),
|
||||
bl_socket.socket_type,
|
||||
unit_system,
|
||||
bl_socket = self.inputs.get(input_socket_name)
|
||||
if bl_socket is not None:
|
||||
if bl_socket.instance_id:
|
||||
return (
|
||||
ct.FlowKind.scale_to_unit_system(
|
||||
kind,
|
||||
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:
|
||||
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)
|
||||
|
||||
####################
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from . import bound_box, bound_faces
|
||||
from . import bound_cond_nodes, bound_conds
|
||||
|
||||
BL_REGISTER = [
|
||||
*bound_box.BL_REGISTER,
|
||||
*bound_faces.BL_REGISTER,
|
||||
*bound_conds.BL_REGISTER,
|
||||
*bound_cond_nodes.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**bound_box.BL_NODES,
|
||||
**bound_faces.BL_NODES,
|
||||
**bound_conds.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
|
||||
from types import MappingProxyType
|
||||
|
||||
from blender_maxwell.utils import extra_sympy_units as spux
|
||||
from blender_maxwell.utils import logger
|
||||
|
||||
from .. import contracts as ct
|
||||
|
@ -10,7 +11,6 @@ from .. import contracts as ct
|
|||
log = logger.get(__name__)
|
||||
|
||||
UnitSystemID = str
|
||||
UnitSystem = dict[ct.SocketType, typ.Any]
|
||||
|
||||
|
||||
####################
|
||||
|
@ -70,7 +70,7 @@ def event_decorator(
|
|||
all_loose_input_sockets: bool = False,
|
||||
all_loose_output_sockets: bool = False,
|
||||
# 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_output_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
||||
):
|
||||
|
@ -213,7 +213,6 @@ def event_decorator(
|
|||
kind=kind,
|
||||
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)),
|
||||
)
|
||||
|
||||
|
@ -269,9 +268,22 @@ def event_decorator(
|
|||
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
|
||||
## If there is a FlowPending, then the method would fail.
|
||||
## Therefore, propagate FlowPending if found.
|
||||
return method(
|
||||
node,
|
||||
**method_kw_args,
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
# from . import scientific_constant
|
||||
# from . import physical_constant
|
||||
from . import blender_constant, expr_constant, number_constant, scientific_constant
|
||||
from . import (
|
||||
blender_constant,
|
||||
expr_constant,
|
||||
number_constant,
|
||||
physical_constant,
|
||||
scientific_constant,
|
||||
)
|
||||
|
||||
BL_REGISTER = [
|
||||
*expr_constant.BL_REGISTER,
|
||||
*scientific_constant.BL_REGISTER,
|
||||
*number_constant.BL_REGISTER,
|
||||
# *physical_constant.BL_REGISTER,
|
||||
*physical_constant.BL_REGISTER,
|
||||
*blender_constant.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**expr_constant.BL_NODES,
|
||||
**scientific_constant.BL_NODES,
|
||||
**number_constant.BL_NODES,
|
||||
# **physical_constant.BL_NODES,
|
||||
**physical_constant.BL_NODES,
|
||||
**blender_constant.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ class NumberConstantNode(base.MaxwellSimNode):
|
|||
####################
|
||||
# - 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.prop(self, self.blfields['mathtype'], text='')
|
||||
row.prop(self, self.blfields['size'], text='')
|
||||
|
@ -56,7 +56,7 @@ class NumberConstantNode(base.MaxwellSimNode):
|
|||
def on_mathtype_size_changed(self, props) -> None:
|
||||
"""Change the input/output expression sockets to match the mathtype declared in the node."""
|
||||
self.inputs['Value'].mathtype = props['mathtype']
|
||||
self.inputs['Value'].shape = props['mathtype'].shape
|
||||
self.inputs['Value'].shape = props['size'].shape
|
||||
|
||||
####################
|
||||
# - FlowKind
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import enum
|
||||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
|
||||
from blender_maxwell.utils import bl_cache
|
||||
|
@ -10,7 +10,7 @@ from .... import contracts, sockets
|
|||
from ... import base, events
|
||||
|
||||
|
||||
class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||
class PhysicalConstantNode(base.MaxwellSimNode):
|
||||
"""A number of configurable unit dimension, ex. time, length, etc. .
|
||||
|
||||
Attributes:
|
||||
|
@ -36,12 +36,12 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
|||
prop_ui=True,
|
||||
)
|
||||
|
||||
mathtype: enum.Enum = bl_cache.BLField(
|
||||
mathtype: spux.MathType = bl_cache.BLField(
|
||||
enum_cb=lambda self, _: self.search_mathtypes(),
|
||||
prop_ui=True,
|
||||
)
|
||||
|
||||
size: enum.Enum = bl_cache.BLField(
|
||||
size: spux.NumberSize1D = bl_cache.BLField(
|
||||
enum_cb=lambda self, _: self.search_sizes(),
|
||||
prop_ui=True,
|
||||
)
|
||||
|
@ -62,16 +62,25 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
|||
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.on_value_changed(
|
||||
prop_name={'physical_type', 'mathtype', 'size'},
|
||||
run_on_init=True,
|
||||
props={'physical_type', 'mathtype', 'size'},
|
||||
)
|
||||
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."""
|
||||
shape = spux.NumberSize1D(props['size']).shape
|
||||
shape = props['size'].shape
|
||||
|
||||
# Set Input Socket Physical Type
|
||||
if self.inputs['Value'].physical_type != props['physical_type']:
|
||||
|
@ -90,9 +99,9 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
|||
####################
|
||||
# - Callbacks
|
||||
####################
|
||||
@events.computes_output_socket('value')
|
||||
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
||||
return self.compute_input('value')
|
||||
@events.computes_output_socket('Value', input_sockets={'Value'})
|
||||
def compute_value(self, input_sockets) -> sp.Expr:
|
||||
return input_sockets['Value']
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -2,7 +2,7 @@ import typing as typ
|
|||
|
||||
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 sockets
|
||||
|
@ -20,63 +20,43 @@ class ScientificConstantNode(base.MaxwellSimNode):
|
|||
####################
|
||||
# - Properties
|
||||
####################
|
||||
sci_constant: bpy.props.StringProperty(
|
||||
name='Sci Constant',
|
||||
description='The name of a scientific constant',
|
||||
default='',
|
||||
search=lambda self, _, edit_text: self.search_sci_constants(edit_text),
|
||||
update=lambda self, context: self.on_update_sci_constant(context),
|
||||
sci_constant: str = bl_cache.BLField(
|
||||
'',
|
||||
prop_ui=True,
|
||||
str_cb=lambda self, _, edit_text: self.search_sci_constants(edit_text),
|
||||
)
|
||||
|
||||
cache__units: bpy.props.StringProperty(default='')
|
||||
cache__uncertainty: bpy.props.StringProperty(default='')
|
||||
|
||||
def search_sci_constants(
|
||||
self,
|
||||
edit_text: str,
|
||||
):
|
||||
return [
|
||||
name
|
||||
for name in constants.SCI_CONSTANTS
|
||||
for name in sci_constants.SCI_CONSTANTS
|
||||
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
|
||||
####################
|
||||
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:
|
||||
if self.sci_constant:
|
||||
col.label(text=f'Units: {self.cache__units}')
|
||||
col.label(text=f'Uncertainty: {self.cache__uncertainty}')
|
||||
|
||||
col.label(text=f'Ref: {constants.SCI_CONSTANTS_REF[0]}')
|
||||
col.label(
|
||||
text=f'Units: {sci_constants.SCI_CONSTANTS_INFO[self.sci_constant]["units"]}'
|
||||
)
|
||||
col.label(
|
||||
text=f'Uncertainty: {sci_constants.SCI_CONSTANTS_INFO[self.sci_constant]["uncertainty"]}'
|
||||
)
|
||||
|
||||
####################
|
||||
# - Callbacks
|
||||
# - Output
|
||||
####################
|
||||
@events.computes_output_socket('Value', props={'sci_constant'})
|
||||
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 = {
|
||||
'Wavelength': {
|
||||
'WL': sockets.ExprSocketDef(
|
||||
active_kind=ct.FlowKind.Value,
|
||||
physical_type=spux.PhysicalType.Length,
|
||||
# Defaults
|
||||
default_unit=spu.nm,
|
||||
|
@ -58,18 +59,18 @@ class WaveConstantNode(base.MaxwellSimNode):
|
|||
output_sockets: typ.ClassVar = {
|
||||
'WL': sockets.ExprSocketDef(
|
||||
active_kind=ct.FlowKind.Value,
|
||||
unit_dimension=spux.Dims.length,
|
||||
physical_type=spux.PhysicalType.Length,
|
||||
),
|
||||
'Freq': sockets.ExprSocketDef(
|
||||
active_kind=ct.FlowKind.Value,
|
||||
unit_dimension=spux.Dims.frequency,
|
||||
physical_type=spux.PhysicalType.Freq,
|
||||
),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
use_range: bool = bl_cache.BLField(False)
|
||||
use_range: bool = bl_cache.BLField(False, prop_ui=True)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
|
@ -80,14 +81,14 @@ class WaveConstantNode(base.MaxwellSimNode):
|
|||
Parameters:
|
||||
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.on_value_changed(
|
||||
prop_name={'active_socket_set', 'use_range'},
|
||||
props='use_range',
|
||||
props={'use_range'},
|
||||
run_on_init=True,
|
||||
)
|
||||
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:
|
||||
"""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 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:
|
||||
"""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 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:
|
||||
"""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['Freq'].rescale_bounds(
|
||||
lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
|
||||
freq = input_sockets['Freq']
|
||||
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(
|
||||
|
@ -177,11 +189,20 @@ class WaveConstantNode(base.MaxwellSimNode):
|
|||
)
|
||||
def compute_freq_range(self, input_sockets: dict) -> sp.Expr:
|
||||
"""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['WL'].rescale_bounds(
|
||||
lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
|
||||
wl = input_sockets['WL']
|
||||
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
|
||||
|
||||
# Try Loading Simulation Data
|
||||
node.sim_data = bl_cache.Signal.InvalidateCache
|
||||
#node.sim_data = bl_cache.Signal.InvalidateCache
|
||||
sim_data = node.sim_data
|
||||
if sim_data is None:
|
||||
self.report(
|
||||
|
@ -70,18 +70,26 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
|||
should_exist=True,
|
||||
),
|
||||
}
|
||||
output_sockets: typ.ClassVar = {
|
||||
'Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
sim_data_loaded: bool = bl_cache.BLField(False)
|
||||
|
||||
@bl_cache.cached_bl_property()
|
||||
####################
|
||||
# - Computed
|
||||
####################
|
||||
@property
|
||||
def sim_data(self) -> td.SimulationData | None:
|
||||
cloud_task = self._compute_input(
|
||||
'Cloud Task', kind=ct.FlowKind.Value, optional=True
|
||||
)
|
||||
has_cloud_task = not ct.FlowSignal.check(cloud_task)
|
||||
if (
|
||||
# Check Flow
|
||||
not ct.FlowSignal.check(cloud_task)
|
||||
# Check Task
|
||||
has_cloud_task
|
||||
and cloud_task is not None
|
||||
and isinstance(cloud_task, tdcloud.CloudTask)
|
||||
and cloud_task.status == 'success'
|
||||
|
@ -97,7 +105,7 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
|||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_operators(self, context, layout):
|
||||
def draw_operators(self, _: bpy.types.Context, layout: bpy.types.UILayout):
|
||||
if self.sim_data_loaded:
|
||||
layout.operator(ct.OperatorType.NodeLoadCloudSim, text='Reload Sim')
|
||||
else:
|
||||
|
@ -106,11 +114,6 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
|||
####################
|
||||
# - 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(
|
||||
prop_name='sim_data_loaded', run_on_init=True, props={'sim_data_loaded'}
|
||||
)
|
||||
|
|
|
@ -33,6 +33,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
|||
'Size': sockets.ExprSocketDef(
|
||||
shape=(3,),
|
||||
physical_type=spux.PhysicalType.Length,
|
||||
default_value=sp.Matrix([1, 1, 1]),
|
||||
),
|
||||
'Spatial Subdivs': sockets.ExprSocketDef(
|
||||
shape=(3,),
|
||||
|
@ -124,11 +125,30 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
|||
# - Preview
|
||||
####################
|
||||
@events.on_value_changed(
|
||||
socket_name={'Center', 'Size'},
|
||||
# Trigger
|
||||
prop_name='preview_active',
|
||||
# Loaded
|
||||
managed_objs={'mesh'},
|
||||
props={'preview_active'},
|
||||
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'},
|
||||
input_sockets={'Center', 'Size'},
|
||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||
scale_input_sockets={
|
||||
'Center': 'BlenderUnits',
|
||||
|
@ -136,7 +156,6 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
|||
)
|
||||
def on_inputs_changed(
|
||||
self,
|
||||
props: dict,
|
||||
managed_objs: dict,
|
||||
input_sockets: 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(
|
||||
shape=(3,),
|
||||
physical_type=spux.PhysicalType.Length,
|
||||
default_value=sp.Matrix([1, 1, 1]),
|
||||
),
|
||||
'Samples/Space': sockets.ExprSocketDef(
|
||||
shape=(3,),
|
||||
|
@ -123,11 +124,29 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
|||
# - Preview - Changes to Input Sockets
|
||||
####################
|
||||
@events.on_value_changed(
|
||||
socket_name={'Center', 'Size'},
|
||||
# Trigger
|
||||
prop_name='preview_active',
|
||||
# Loaded
|
||||
managed_objs={'mesh'},
|
||||
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'},
|
||||
input_sockets={'Center', 'Size'},
|
||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||
scale_input_sockets={
|
||||
'Center': 'BlenderUnits',
|
||||
|
@ -135,7 +154,6 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
|||
)
|
||||
def on_inputs_changed(
|
||||
self,
|
||||
props: dict,
|
||||
managed_objs: dict,
|
||||
input_sockets: 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 = [
|
||||
*viewer.BL_REGISTER,
|
||||
*file_exporters.BL_REGISTER,
|
||||
*web_exporters.BL_REGISTER,
|
||||
#*file_exporters.BL_REGISTER,
|
||||
#*web_exporters.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**viewer.BL_NODES,
|
||||
**file_exporters.BL_NODES,
|
||||
**web_exporters.BL_NODES,
|
||||
#**file_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(
|
||||
socket_name={'Center', 'Size'},
|
||||
prop_name='preview_active',
|
||||
|
@ -91,6 +72,28 @@ class SimDomainNode(base.MaxwellSimNode):
|
|||
if props['preview_active']:
|
||||
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
|
||||
|
|
|
@ -217,7 +217,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
Called by `self.on_prop_changed()` when `self.active_kind` was changed.
|
||||
"""
|
||||
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 '')
|
||||
## 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)
|
||||
|
||||
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
|
||||
####################
|
||||
|
|
|
@ -2,6 +2,7 @@ import enum
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import pydantic as pyd
|
||||
import sympy as sp
|
||||
|
||||
from blender_maxwell.utils import bl_cache, logger
|
||||
|
@ -63,6 +64,7 @@ class InfoDisplayCol(enum.StrEnum):
|
|||
class ExprBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.Expr
|
||||
bl_label = 'Expr'
|
||||
use_info_draw = True
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
|
@ -70,7 +72,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
shape: tuple[int, ...] | None = bl_cache.BLField(None)
|
||||
mathtype: spux.MathType = bl_cache.BLField(spux.MathType.Real, prop_ui=True)
|
||||
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(
|
||||
None, enum_cb=lambda self, _: self.search_units(), prop_ui=True
|
||||
|
@ -102,7 +104,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
)
|
||||
|
||||
# UI: LazyArrayRange
|
||||
steps: int = bl_cache.BLField(2, abs_min=2)
|
||||
steps: int = bl_cache.BLField(2, abs_min=2, prop_ui=True)
|
||||
## Expression
|
||||
raw_min_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
|
||||
####################
|
||||
@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
|
||||
def raw_value_sp(self) -> spux.SympyExpr:
|
||||
return self._parse_expr_str(self.raw_value_spstr)
|
||||
|
@ -140,7 +151,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
####################
|
||||
# - 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:
|
||||
return [
|
||||
(sp.sstr(unit), spux.sp_to_str(unit), sp.sstr(unit), '', i)
|
||||
|
@ -163,33 +174,38 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
return None
|
||||
|
||||
@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.
|
||||
|
||||
Notes:
|
||||
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:
|
||||
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}")'
|
||||
raise ValueError(msg)
|
||||
if self.physical_type is not None:
|
||||
if unit in self.physical_type.valid_units:
|
||||
self.active_unit = sp.sstr(unit)
|
||||
else:
|
||||
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:
|
||||
if self.active_kind == ct.FlowKind.Value:
|
||||
current_value = self.value
|
||||
self.unit = unit_to
|
||||
self.value = current_value
|
||||
elif self.active_kind == ct.FlowKind.LazyArrayRange:
|
||||
current_lazy_array_range = self.lazy_array_range
|
||||
self.unit = unit_to
|
||||
self.lazy_array_range = current_lazy_array_range
|
||||
current_value = self.value
|
||||
current_lazy_array_range = self.lazy_array_range
|
||||
|
||||
self.unit = bl_cache.Signal.InvalidateCache
|
||||
|
||||
self.value = current_value
|
||||
self.lazy_array_range = current_lazy_array_range
|
||||
|
||||
####################
|
||||
# - Property Callback
|
||||
####################
|
||||
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))
|
||||
|
||||
####################
|
||||
|
@ -200,23 +216,23 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
) -> tuple[spux.MathType, tuple[int, ...] | None, spux.UnitDimension]:
|
||||
# Parse MathType
|
||||
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}'
|
||||
raise ValueError(msg)
|
||||
|
||||
# Parse Symbols
|
||||
if expr.free_symbols:
|
||||
if self.mathtype is not None:
|
||||
msg = f'MathType is {self.mathtype}, but tried to set expr {expr} with free symbols {expr.free_symbols}'
|
||||
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)
|
||||
if expr.free_symbols and 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
|
||||
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})'
|
||||
raise ValueError(msg)
|
||||
|
||||
|
@ -238,7 +254,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
# Try Parsing and Returning the Expression
|
||||
try:
|
||||
self._parse_expr_info(expr)
|
||||
except ValueError(expr) as ex:
|
||||
except ValueError:
|
||||
log.exception(
|
||||
'Couldn\'t parse expression "%s" in Expr socket.',
|
||||
expr_spstr,
|
||||
|
@ -270,6 +286,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
expr = self.raw_value_sp
|
||||
if expr is None:
|
||||
return ct.FlowSignal.FlowPending
|
||||
return expr
|
||||
|
||||
MT_Z = spux.MathType.Integer
|
||||
MT_Q = spux.MathType.Rational
|
||||
|
@ -312,7 +329,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
Notes:
|
||||
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,)]:
|
||||
self.raw_value_spstr = sp.sstr(expr)
|
||||
|
||||
|
@ -321,32 +338,33 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
MT_Q = spux.MathType.Rational
|
||||
MT_R = spux.MathType.Real
|
||||
MT_C = spux.MathType.Complex
|
||||
if shape is None:
|
||||
if mathtype == MT_Z:
|
||||
if self.shape is None:
|
||||
if self.mathtype == MT_Z:
|
||||
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)
|
||||
elif mathtype == MT_R:
|
||||
elif self.mathtype == MT_R:
|
||||
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)
|
||||
elif shape == (2,):
|
||||
if mathtype == MT_Z:
|
||||
elif self.shape == (2,):
|
||||
if self.mathtype == MT_Z:
|
||||
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)
|
||||
elif mathtype == MT_R:
|
||||
elif self.mathtype == MT_R:
|
||||
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)
|
||||
elif shape == (3,):
|
||||
if mathtype == MT_Z:
|
||||
elif self.shape == (3,):
|
||||
log.critical(expr)
|
||||
if self.mathtype == MT_Z:
|
||||
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)
|
||||
elif mathtype == MT_R:
|
||||
elif self.mathtype == MT_R:
|
||||
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)
|
||||
|
||||
####################
|
||||
|
@ -404,7 +422,6 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
Called to compute the internal `FlowKind.LazyArrayRange` of this socket.
|
||||
"""
|
||||
self.steps = value.steps
|
||||
self.unit = value.unit
|
||||
|
||||
if self.symbols:
|
||||
self.raw_min_spstr = sp.sstr(value.start)
|
||||
|
@ -416,21 +433,26 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
MT_R = spux.MathType.Real
|
||||
MT_C = spux.MathType.Complex
|
||||
|
||||
unit = value.unit if value.unit is not None else 1
|
||||
if value.mathtype == MT_Z:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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
|
||||
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
||||
return ct.LazyValueFuncFlow(
|
||||
func=sp.lambdify(self.symbols, self.value, 'jax'),
|
||||
func_args=[spux.MathType.from_expr(sym) for sym in self.symbols],
|
||||
func=sp.lambdify(self.sorted_symbols, self.value, 'jax'),
|
||||
func_args=[spux.MathType.from_expr(sym) for sym in self.sorted_symbols],
|
||||
supports_jax=True,
|
||||
)
|
||||
|
||||
|
@ -482,8 +504,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
unit=self.unit,
|
||||
)
|
||||
|
||||
msg = "Expr socket can't produce array from expression with free symbols"
|
||||
raise ValueError(msg)
|
||||
return ct.FlowSignal.NoFlow
|
||||
|
||||
####################
|
||||
# - FlowKind: Info
|
||||
|
@ -496,6 +517,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
output_mathtype=self.mathtype,
|
||||
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
|
||||
|
@ -520,10 +542,11 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
_row.label(text=text)
|
||||
|
||||
_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:
|
||||
# Property Interface
|
||||
if self.symbols:
|
||||
col.prop(self, self.blfields['raw_value_spstr'], text='')
|
||||
|
||||
|
@ -575,6 +598,27 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
for sym in self.symbols:
|
||||
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:
|
||||
info = self.compute_data(kind=ct.FlowKind.Info)
|
||||
has_dims = not ct.FlowSignal.check(info) and info.dim_names
|
||||
|
@ -630,7 +674,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
_row.label(text=text)
|
||||
|
||||
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()
|
||||
box = row.box()
|
||||
grid = box.grid_flow(
|
||||
|
@ -696,19 +740,91 @@ class ExprSocketDef(base.SocketDef):
|
|||
default_unit: spux.Unit | None = None
|
||||
|
||||
# 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
|
||||
default_min: spux.SympyExpr = sp.S(0)
|
||||
default_max: spux.SympyExpr = sp.S(1)
|
||||
default_min: spux.SympyExpr = sp.RealNumber(0)
|
||||
default_max: spux.SympyExpr = sp.RealNumber(1)
|
||||
default_steps: int = 2
|
||||
## TODO: Configure lin/log/... scaling (w/enumprop in UI)
|
||||
|
||||
## TODO: Buncha validation :)
|
||||
|
||||
# UI
|
||||
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:
|
||||
bl_socket.active_kind = self.active_kind
|
||||
|
||||
|
@ -718,12 +834,12 @@ class ExprSocketDef(base.SocketDef):
|
|||
bl_socket.physical_type = self.physical_type
|
||||
bl_socket.symbols = self.symbols
|
||||
|
||||
# Socket Units
|
||||
if self.default_unit is not None:
|
||||
# Socket Units & FlowKind.Value
|
||||
if self.physical_type is not None:
|
||||
bl_socket.unit = self.default_unit
|
||||
|
||||
# FlowKind: Value
|
||||
bl_socket.value = self.default_value
|
||||
bl_socket.value = self.default_value * self.default_unit
|
||||
else:
|
||||
bl_socket.value = self.default_value
|
||||
|
||||
# FlowKind: LazyArrayRange
|
||||
bl_socket.lazy_array_range = ct.LazyArrayRangeFlow(
|
||||
|
|
|
@ -3,51 +3,64 @@ 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 base
|
||||
|
||||
log = logger.get(__name__)
|
||||
|
||||
|
||||
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
|
||||
bl_label = 'Maxwell Bound Face'
|
||||
bl_label = 'Maxwell Bound Cond'
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
default_choice: bpy.props.EnumProperty(
|
||||
name='Bound Face',
|
||||
description='A choice of default boundary face',
|
||||
items=[
|
||||
('PML', 'PML', 'Perfectly matched layer'),
|
||||
('PEC', 'PEC', 'Perfect electrical conductor'),
|
||||
('PMC', 'PMC', 'Perfect magnetic conductor'),
|
||||
('PERIODIC', 'Periodic', 'Infinitely periodic layer'),
|
||||
],
|
||||
default='PML',
|
||||
update=(lambda self, context: self.on_prop_changed('default_choice', context)),
|
||||
default: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||
|
||||
# Capabilities
|
||||
## Allow a boundary condition compatible with any of the following axes.
|
||||
allow_axes: set[ct.SimSpaceAxis] = bl_cache.BLField(
|
||||
{ct.SimSpaceAxis.X, ct.SimSpaceAxis.Y, ct.SimSpaceAxis.Z},
|
||||
)
|
||||
## Present a boundary condition compatible with any of the following axes.
|
||||
present_axes: set[ct.SimSpaceAxis] = bl_cache.BLField(
|
||||
{ct.SimSpaceAxis.X, ct.SimSpaceAxis.Y, ct.SimSpaceAxis.Z},
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
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
|
||||
def value(self) -> td.BoundarySpec:
|
||||
return {
|
||||
'PML': td.PML(num_layers=12),
|
||||
'PEC': td.PECBoundary(),
|
||||
'PMC': td.PMCBoundary(),
|
||||
'PERIODIC': td.Periodic(),
|
||||
}[self.default_choice]
|
||||
def capabilities(self) -> ct.CapabilitiesFlow:
|
||||
return ct.CapabilitiesFlow(
|
||||
socket_type=self.socket_type,
|
||||
active_kind=self.active_kind,
|
||||
allow_any=self.allow_axes,
|
||||
present_any=self.present_axes,
|
||||
)
|
||||
|
||||
@property
|
||||
def value(self) -> td.BoundaryEdge:
|
||||
return self.default.tidy3d_boundary_edge
|
||||
|
||||
@value.setter
|
||||
def value(self, value: typ.Literal['PML', 'PEC', 'PMC', 'PERIODIC']) -> None:
|
||||
self.default_choice = value
|
||||
def value(self, value: ct.BoundCondType) -> None:
|
||||
self.default = value
|
||||
|
||||
|
||||
####################
|
||||
|
@ -56,10 +69,23 @@ class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
|
|||
class MaxwellBoundCondSocketDef(base.SocketDef):
|
||||
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:
|
||||
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 tidy3d as td
|
||||
|
||||
from blender_maxwell.utils import bl_cache, logger
|
||||
|
||||
from ... import contracts as ct
|
||||
from .. import base
|
||||
|
||||
BOUND_FACE_ITEMS = [
|
||||
('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(),
|
||||
}
|
||||
log = logger.get(__name__)
|
||||
|
||||
|
||||
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
|
||||
bl_label = 'Maxwell Bound Box'
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
show_definition: bpy.props.BoolProperty(
|
||||
name='Show Bounds Definition',
|
||||
description='Toggle to show bound faces',
|
||||
default=False,
|
||||
update=(lambda self, context: self.on_prop_changed('show_definition', context)),
|
||||
)
|
||||
show_definition: bool = bl_cache.BLField(False, prop_ui=True)
|
||||
|
||||
x_pos: 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_pos', context)),
|
||||
)
|
||||
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)),
|
||||
)
|
||||
x_pos: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||
x_neg: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||
y_pos: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||
y_neg: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||
z_pos: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||
z_neg: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_label_row(self, row: bpy.types.UILayout, text) -> None:
|
||||
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:
|
||||
if not self.show_definition:
|
||||
return
|
||||
if self.show_definition:
|
||||
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']:
|
||||
row = col.row(align=False)
|
||||
split = row.split(factor=0.2, align=False)
|
||||
_col = split.column(align=True)
|
||||
_col.alignment = 'RIGHT'
|
||||
_col.label(text=axis + ' -')
|
||||
_col.label(text=' +')
|
||||
|
||||
_col = split.column(align=True)
|
||||
_col.alignment = 'RIGHT'
|
||||
_col.label(text=axis + ' -')
|
||||
_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
|
||||
_col = split.column(align=True)
|
||||
_col.prop(self, self.blfields[axis + '_neg'], text='')
|
||||
_col.prop(self, self.blfields[axis + '_pos'], text='')
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
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(
|
||||
x=td.Boundary(
|
||||
plus=BOUND_MAP[self.x_pos],
|
||||
minus=BOUND_MAP[self.x_neg],
|
||||
plus=self.x_pos.tidy3d_boundary_edge,
|
||||
minus=self.x_neg.tidy3d_boundary_edge,
|
||||
),
|
||||
y=td.Boundary(
|
||||
plus=BOUND_MAP[self.y_pos],
|
||||
minus=BOUND_MAP[self.y_neg],
|
||||
plus=self.y_pos.tidy3d_boundary_edge,
|
||||
minus=self.y_neg.tidy3d_boundary_edge,
|
||||
),
|
||||
z=td.Boundary(
|
||||
plus=BOUND_MAP[self.z_pos],
|
||||
minus=BOUND_MAP[self.z_neg],
|
||||
plus=self.z_pos.tidy3d_boundary_edge,
|
||||
minus=self.z_neg.tidy3d_boundary_edge,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -128,8 +106,20 @@ class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
|
|||
class MaxwellBoundCondsSocketDef(base.SocketDef):
|
||||
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:
|
||||
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 base
|
||||
|
||||
|
@ -16,6 +15,8 @@ class MaxwellSourceSocketDef(base.SocketDef):
|
|||
|
||||
is_list: bool = False
|
||||
|
||||
## TODO: capabilities() to require source sockets
|
||||
|
||||
def init(self, bl_socket: MaxwellSourceBLSocket) -> None:
|
||||
if self.is_list:
|
||||
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)
|
||||
|
||||
####################
|
||||
# - 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
|
||||
def capabilities(self) -> ct.CapabilitiesFlow:
|
||||
return ct.CapabilitiesFlow(
|
||||
|
@ -122,7 +139,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
return (self.new_task_name, cloud_folder)
|
||||
|
||||
# No Task Selected: Return None
|
||||
if self.existing_task_id == 'NONE':
|
||||
if self.existing_task_id is None:
|
||||
return None
|
||||
|
||||
# Retrieve Cloud Task
|
||||
|
@ -135,7 +152,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
|
||||
return cloud_task
|
||||
|
||||
return None
|
||||
return ct.FlowSignal.FlowPending
|
||||
|
||||
####################
|
||||
# - Searchers
|
||||
|
@ -158,7 +175,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
return []
|
||||
|
||||
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 []
|
||||
|
||||
# Get Cloud Folder
|
||||
|
@ -221,10 +238,6 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
def on_prepare_new_task(self):
|
||||
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
|
||||
####################
|
||||
|
|
|
@ -13,6 +13,7 @@ def prefix_values_with(prefix: str) -> type[enum.Enum]:
|
|||
Returns:
|
||||
A new StrEnum class with altered member values.
|
||||
"""
|
||||
## TODO: DO NOT USE FOR ENUMS WITH METHODS
|
||||
|
||||
def _decorator(cls: enum.StrEnum):
|
||||
new_members = {
|
||||
|
|
|
@ -547,6 +547,9 @@ class BLField:
|
|||
self._str_cb = str_cb
|
||||
self._enum_cb = enum_cb
|
||||
|
||||
## Type Coercion
|
||||
self._coerce_output_to = None
|
||||
|
||||
## Vector/Matrix Identity
|
||||
## -> Matrix Shape assists in the workaround for Matrix Display Bug
|
||||
self._is_vector = False
|
||||
|
@ -797,7 +800,11 @@ class BLField:
|
|||
}
|
||||
|
||||
## 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
|
||||
BLProp = bpy.props.EnumProperty
|
||||
kwargs_prop |= {
|
||||
|
@ -814,9 +821,14 @@ class BLField:
|
|||
}
|
||||
if self._enum_many:
|
||||
kwargs_prop['options'].add('ENUM_FLAG')
|
||||
self._coerce_output_to = AttrType
|
||||
|
||||
## 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:
|
||||
msg = 'When using dynamic enum, default value must be None'
|
||||
raise ValueError(msg)
|
||||
|
@ -828,6 +840,8 @@ class BLField:
|
|||
}
|
||||
if self._enum_many:
|
||||
kwargs_prop['options'].add('ENUM_FLAG')
|
||||
if AttrType is not enum.Enum:
|
||||
self._coerce_output_to = AttrType
|
||||
|
||||
## BL Reference
|
||||
elif AttrType in typ.get_args(ct.BLIDStruct):
|
||||
|
@ -888,6 +902,9 @@ class BLField:
|
|||
def __get__(
|
||||
self, bl_instance: BLInstance | None, owner: type[BLInstance]
|
||||
) -> typ.Any:
|
||||
if bl_instance is None:
|
||||
return None
|
||||
|
||||
value = self._cached_bl_property.__get__(bl_instance, owner)
|
||||
|
||||
# enum.Enum: Cast Auto-Injected Dynamic Enum 'NONE' -> None
|
||||
|
@ -913,7 +930,7 @@ class BLField:
|
|||
## -> Reject modernity. Return to tuple[].
|
||||
if self._is_vector:
|
||||
## -> tuple()ify the np.array to respect tuple[] type annotation.
|
||||
return tuple(np.array(value))
|
||||
return tuple(value)
|
||||
|
||||
if self._is_matrix:
|
||||
# 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))
|
||||
)
|
||||
|
||||
# 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
|
||||
|
||||
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 . import logger
|
||||
|
||||
log = logger.get(__name__)
|
||||
|
||||
SympyType = (
|
||||
sp.Basic
|
||||
| sp.Expr
|
||||
|
@ -47,21 +51,42 @@ class MathType(enum.StrEnum):
|
|||
Real = enum.auto()
|
||||
Complex = enum.auto()
|
||||
|
||||
@staticmethod
|
||||
def combine(*mathtypes: list[typ.Self]) -> typ.Self:
|
||||
if MathType.Complex in mathtypes:
|
||||
return MathType.Complex
|
||||
elif MathType.Real in mathtypes:
|
||||
if MathType.Real in mathtypes:
|
||||
return MathType.Real
|
||||
elif MathType.Rational in mathtypes:
|
||||
if MathType.Rational in mathtypes:
|
||||
return MathType.Rational
|
||||
elif MathType.Integer in mathtypes:
|
||||
if MathType.Integer in mathtypes:
|
||||
return MathType.Integer
|
||||
elif MathType.Bool in mathtypes:
|
||||
if MathType.Bool in mathtypes:
|
||||
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
|
||||
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):
|
||||
return MathType.Bool
|
||||
if sp_obj.is_integer:
|
||||
|
@ -172,7 +197,7 @@ class NumberSize1D(enum.StrEnum):
|
|||
None: NS.Scalar,
|
||||
(2,): NS.Vec2,
|
||||
(3,): NS.Vec3,
|
||||
(4,): NS.Vec3,
|
||||
(4,): NS.Vec4,
|
||||
}[shape]
|
||||
|
||||
@property
|
||||
|
@ -182,7 +207,7 @@ class NumberSize1D(enum.StrEnum):
|
|||
NS.Scalar: None,
|
||||
NS.Vec2: (2,),
|
||||
NS.Vec3: (3,),
|
||||
NS.Vec3: (4,),
|
||||
NS.Vec4: (4,),
|
||||
}[self]
|
||||
|
||||
|
||||
|
@ -702,7 +727,6 @@ def scale_to_unit(sp_obj: SympyType, unit: spu.Quantity) -> Number:
|
|||
Raises:
|
||||
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
|
||||
if not uses_units(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:
|
||||
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)
|
||||
|
||||
|
||||
|
@ -802,7 +826,7 @@ class PhysicalType(enum.StrEnum):
|
|||
# Global
|
||||
PT.Time: Dims.time,
|
||||
PT.Angle: Dims.angle,
|
||||
PT.SolidAngle: Dims.steradian, ## MISSING
|
||||
PT.SolidAngle: spu.steradian.dimension, ## MISSING
|
||||
PT.Freq: Dims.frequency,
|
||||
PT.AngFreq: Dims.angle * Dims.frequency,
|
||||
# Cartesian
|
||||
|
@ -836,7 +860,7 @@ class PhysicalType(enum.StrEnum):
|
|||
PT.HField: Dims.current / Dims.length,
|
||||
# Luminal
|
||||
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,
|
||||
# Optics
|
||||
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))
|
||||
|
||||
|
||||
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(
|
||||
sp_obj: SympyExpr, unit_system: UnitSystem, use_jax_array: bool = False
|
||||
) -> 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.
|
||||
|
||||
Unit stripping is "dumb": Substitute any `sympy` object in `unit_system.values()` with `1`.
|
||||
Afterwards, it is converted to an appropriate Python type.
|
||||
|
||||
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`.
|
||||
"""
|
||||
return sympy_to_python(
|
||||
convert_to_unit_system(sp_obj, unit_system).subs(
|
||||
{unit: 1 for unit in unit_system.values()}
|
||||
),
|
||||
strip_unit_system(convert_to_unit_system(sp_obj, unit_system), unit_system),
|
||||
use_jax_array=use_jax_array,
|
||||
)
|
||||
|
|
|
@ -81,6 +81,7 @@ _NaivelyEncodableTypeSet = frozenset(typ.get_args(NaivelyEncodableType))
|
|||
class TypeID(enum.StrEnum):
|
||||
Complex: str = '!type=complex'
|
||||
SympyType: str = '!type=sympytype'
|
||||
SympyExpr: str = '!type=sympyexpr'
|
||||
SocketDef: str = '!type=socketdef'
|
||||
ManagedObj: str = '!type=managedobj'
|
||||
|
||||
|
|
Loading…
Reference in New Issue