feat: Added BoundConds Node & Fancy PML Node
We now have a solid category (w/accompanying sockets) for defining boundary conditions. We also have a single-boundary-condition node for fully configuring the all-important PML condition. Some insights: - PEC/PMC are so dead-simple that giving them their own nodes doesn't even make sense. - StablePML and PML are the same, just with differing layers. Chose to require the user add layers to the PML node for the same effect. - "Periodic" is a special case of "Bloch", so we only need "Bloch". - "Absorber" vs "PML" is an important choice for the user, which we must ensure shines through.main
parent
7263d585e5
commit
f60b736584
18
TODO.md
18
TODO.md
|
@ -1,10 +1,10 @@
|
|||
# Working TODO
|
||||
- [x] Wave Constant
|
||||
- Bounds
|
||||
- [ ] Boundary Conds
|
||||
- [ ] PML
|
||||
- [ ] PEC
|
||||
- [ ] PMC
|
||||
- [x] Boundary Conds
|
||||
- [x] PML
|
||||
- [x] PEC
|
||||
- [x] PMC
|
||||
- [ ] Bloch
|
||||
- [ ] Absorbing
|
||||
- Sources
|
||||
|
@ -198,13 +198,11 @@
|
|||
|
||||
## 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
|
||||
- [x] Boundary Cond / PML Bound Cond
|
||||
- [ ] 1D plot visualizing the effect of parameters on a 1D wave function
|
||||
- [ ] Boundary Cond / Bloch Bound Cond
|
||||
- [ ] Implement "simple" mode aka "periodic" mode in Tidy3D
|
||||
- [ ] Boundary Cond / Absorbing Bound Face
|
||||
- [ ] Boundary Cond / Absorbing Bound Cond
|
||||
|
||||
## Monitors
|
||||
- [x] EH Field Monitor
|
||||
|
|
|
@ -40,6 +40,7 @@ from .flow_signals import FlowSignal
|
|||
from .icons import Icon
|
||||
from .mobj_types import ManagedObjType
|
||||
from .node_types import NodeType
|
||||
from .sim_types import BoundCondType
|
||||
from .socket_colors import SOCKET_COLORS
|
||||
from .socket_types import SocketType
|
||||
from .tree_types import TreeType
|
||||
|
@ -77,6 +78,7 @@ __all__ = [
|
|||
'BLSocketInfo',
|
||||
'BLSocketType',
|
||||
'NodeType',
|
||||
'BoundCondType',
|
||||
'NodeCategory',
|
||||
'NODE_CAT_LABELS',
|
||||
'ManagedObjType',
|
||||
|
|
|
@ -90,8 +90,6 @@ 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()
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
"""Declares various simulation types for use by nodes and sockets."""
|
||||
|
||||
import enum
|
||||
import typing as typ
|
||||
|
||||
import tidy3d as td
|
||||
|
||||
|
||||
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]
|
|
@ -1,6 +1,6 @@
|
|||
from . import (
|
||||
analysis,
|
||||
# bounds,
|
||||
bounds,
|
||||
inputs,
|
||||
# mediums,
|
||||
monitors,
|
||||
|
@ -18,7 +18,7 @@ BL_REGISTER = [
|
|||
# *sources.BL_REGISTER,
|
||||
# *mediums.BL_REGISTER,
|
||||
# *structures.BL_REGISTER,
|
||||
# *bounds.BL_REGISTER,
|
||||
*bounds.BL_REGISTER,
|
||||
*monitors.BL_REGISTER,
|
||||
# *simulations.BL_REGISTER,
|
||||
# *utilities.BL_REGISTER,
|
||||
|
@ -30,7 +30,7 @@ BL_NODES = {
|
|||
# **sources.BL_NODES,
|
||||
# **mediums.BL_NODES,
|
||||
# **structures.BL_NODES,
|
||||
# **bounds.BL_NODES,
|
||||
**bounds.BL_NODES,
|
||||
**monitors.BL_NODES,
|
||||
# **simulations.BL_NODES,
|
||||
# **utilities.BL_NODES,
|
||||
|
|
|
@ -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,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.BoundarySpec:
|
||||
r"""Computes the PML boundary condition based on the active socket set.
|
||||
|
||||
- **Simple**: Use `tidy3d`'s default parameters for defining the PML conductor.
|
||||
- **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_BOUNDCONDS)}
|
|
@ -0,0 +1,155 @@
|
|||
"""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__)
|
||||
|
||||
|
||||
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(),
|
||||
'Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
},
|
||||
'±X | YZ': {
|
||||
'+X': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-X': sockets.MaxwellBoundCondSocketDef(),
|
||||
'Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
},
|
||||
'X | ±Y | Z': {
|
||||
'X': sockets.MaxwellBoundCondSocketDef(),
|
||||
'+Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
},
|
||||
'XY | ±Z': {
|
||||
'X': sockets.MaxwellBoundCondSocketDef(),
|
||||
'Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'+Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
},
|
||||
'±XY | Z': {
|
||||
'+X': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-X': sockets.MaxwellBoundCondSocketDef(),
|
||||
'+Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
},
|
||||
'X | ±YZ': {
|
||||
'X': sockets.MaxwellBoundCondSocketDef(),
|
||||
'+Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'+Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
},
|
||||
'±XYZ': {
|
||||
'+X': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-X': sockets.MaxwellBoundCondSocketDef(),
|
||||
'+Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-Y': sockets.MaxwellBoundCondSocketDef(),
|
||||
'+Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
'-Z': sockets.MaxwellBoundCondSocketDef(),
|
||||
},
|
||||
}
|
||||
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 = {}
|
|
@ -741,6 +741,10 @@ class ExprSocketDef(base.SocketDef):
|
|||
|
||||
# FlowKind: Value
|
||||
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.RealNumber(0)
|
||||
|
@ -831,7 +835,6 @@ class ExprSocketDef(base.SocketDef):
|
|||
bl_socket.symbols = self.symbols
|
||||
|
||||
# Socket Units & FlowKind.Value
|
||||
log.critical(self)
|
||||
if self.physical_type is not None:
|
||||
bl_socket.unit = self.default_unit
|
||||
bl_socket.value = self.default_value * self.default_unit
|
||||
|
|
|
@ -3,51 +3,45 @@ 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)
|
||||
|
||||
####################
|
||||
# - 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
|
||||
####################
|
||||
@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 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 +50,10 @@ 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
|
||||
|
||||
def init(self, bl_socket: MaxwellBoundCondBLSocket) -> None:
|
||||
bl_socket.value = self.default_choice
|
||||
bl_socket.default = self.default
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
####################
|
||||
|
|
Loading…
Reference in New Issue