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
Sofus Albert Høgsbro Rose 2024-05-01 16:06:23 +02:00
parent 7263d585e5
commit f60b736584
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
20 changed files with 545 additions and 238 deletions

18
TODO.md
View File

@ -1,10 +1,10 @@
# Working TODO # Working TODO
- [x] Wave Constant - [x] Wave Constant
- Bounds - Bounds
- [ ] Boundary Conds - [x] Boundary Conds
- [ ] PML - [x] PML
- [ ] PEC - [x] PEC
- [ ] PMC - [x] PMC
- [ ] Bloch - [ ] Bloch
- [ ] Absorbing - [ ] Absorbing
- Sources - Sources
@ -198,13 +198,11 @@
## Bounds ## Bounds
- [x] Boundary Conds - [x] Boundary Conds
- [ ] Boundary Cond / PML Bound Face - [x] Boundary Cond / PML Bound Cond
- [ ] Dropdown for "Normal" and "Stable" - [ ] 1D plot visualizing the effect of parameters on a 1D wave function
- [ ] Boundary Cond / PEC Bound Face - [ ] Boundary Cond / Bloch Bound Cond
- [ ] Boundary Cond / PMC Bound Face
- [ ] Boundary Cond / Bloch Bound Face
- [ ] Implement "simple" mode aka "periodic" mode in Tidy3D - [ ] Implement "simple" mode aka "periodic" mode in Tidy3D
- [ ] Boundary Cond / Absorbing Bound Face - [ ] Boundary Cond / Absorbing Bound Cond
## Monitors ## Monitors
- [x] EH Field Monitor - [x] EH Field Monitor

View File

@ -40,6 +40,7 @@ from .flow_signals import FlowSignal
from .icons import Icon from .icons import Icon
from .mobj_types import ManagedObjType from .mobj_types import ManagedObjType
from .node_types import NodeType from .node_types import NodeType
from .sim_types import BoundCondType
from .socket_colors import SOCKET_COLORS from .socket_colors import SOCKET_COLORS
from .socket_types import SocketType from .socket_types import SocketType
from .tree_types import TreeType from .tree_types import TreeType
@ -77,6 +78,7 @@ __all__ = [
'BLSocketInfo', 'BLSocketInfo',
'BLSocketType', 'BLSocketType',
'NodeType', 'NodeType',
'BoundCondType',
'NodeCategory', 'NodeCategory',
'NODE_CAT_LABELS', 'NODE_CAT_LABELS',
'ManagedObjType', 'ManagedObjType',

View File

@ -90,8 +90,6 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
BoundConds = enum.auto() BoundConds = enum.auto()
## Bounds / Bound Conds ## Bounds / Bound Conds
PMLBoundCond = enum.auto() PMLBoundCond = enum.auto()
PECBoundCond = enum.auto()
PMCBoundCond = enum.auto()
BlochBoundCond = enum.auto() BlochBoundCond = enum.auto()
AbsorbingBoundCond = enum.auto() AbsorbingBoundCond = enum.auto()

View File

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

View File

@ -1,6 +1,6 @@
from . import ( from . import (
analysis, analysis,
# bounds, bounds,
inputs, inputs,
# mediums, # mediums,
monitors, monitors,
@ -18,7 +18,7 @@ BL_REGISTER = [
# *sources.BL_REGISTER, # *sources.BL_REGISTER,
# *mediums.BL_REGISTER, # *mediums.BL_REGISTER,
# *structures.BL_REGISTER, # *structures.BL_REGISTER,
# *bounds.BL_REGISTER, *bounds.BL_REGISTER,
*monitors.BL_REGISTER, *monitors.BL_REGISTER,
# *simulations.BL_REGISTER, # *simulations.BL_REGISTER,
# *utilities.BL_REGISTER, # *utilities.BL_REGISTER,
@ -30,7 +30,7 @@ BL_NODES = {
# **sources.BL_NODES, # **sources.BL_NODES,
# **mediums.BL_NODES, # **mediums.BL_NODES,
# **structures.BL_NODES, # **structures.BL_NODES,
# **bounds.BL_NODES, **bounds.BL_NODES,
**monitors.BL_NODES, **monitors.BL_NODES,
# **simulations.BL_NODES, # **simulations.BL_NODES,
# **utilities.BL_NODES, # **utilities.BL_NODES,

View File

@ -1,10 +1,10 @@
from . import bound_box, bound_faces from . import bound_cond_nodes, bound_conds
BL_REGISTER = [ BL_REGISTER = [
*bound_box.BL_REGISTER, *bound_conds.BL_REGISTER,
*bound_faces.BL_REGISTER, *bound_cond_nodes.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**bound_box.BL_NODES, **bound_conds.BL_NODES,
**bound_faces.BL_NODES, **bound_cond_nodes.BL_NODES,
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -1,5 +0,0 @@
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -1,5 +0,0 @@
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -1,5 +0,0 @@
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -741,6 +741,10 @@ class ExprSocketDef(base.SocketDef):
# FlowKind: Value # FlowKind: Value
default_value: spux.SympyExpr = sp.RealNumber(0) default_value: spux.SympyExpr = sp.RealNumber(0)
abs_min: spux.SympyExpr | None = None ## TODO: Not used (yet)
abs_max: spux.SympyExpr | None = None ## TODO: Not used (yet)
## TODO: Idea is to use this scalar uniformly for all shape elements
## TODO: -> But we may want to **allow** using same-shape for diff. bounds.
# FlowKind: LazyArrayRange # FlowKind: LazyArrayRange
default_min: spux.SympyExpr = sp.RealNumber(0) default_min: spux.SympyExpr = sp.RealNumber(0)
@ -831,7 +835,6 @@ class ExprSocketDef(base.SocketDef):
bl_socket.symbols = self.symbols bl_socket.symbols = self.symbols
# Socket Units & FlowKind.Value # Socket Units & FlowKind.Value
log.critical(self)
if self.physical_type is not None: if self.physical_type is not None:
bl_socket.unit = self.default_unit bl_socket.unit = self.default_unit
bl_socket.value = self.default_value * self.default_unit bl_socket.value = self.default_value * self.default_unit

View File

@ -3,51 +3,45 @@ import typing as typ
import bpy import bpy
import tidy3d as td import tidy3d as td
from blender_maxwell.utils import bl_cache, logger
from ... import contracts as ct from ... import contracts as ct
from .. import base from .. import base
log = logger.get(__name__)
class MaxwellBoundCondBLSocket(base.MaxwellSimSocket): class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
"""Describes a single of boundary condition to apply to the half-axis of a simulation domain.
Attributes:
default: The default boundary condition type.
"""
socket_type = ct.SocketType.MaxwellBoundCond socket_type = ct.SocketType.MaxwellBoundCond
bl_label = 'Maxwell Bound Face' bl_label = 'Maxwell Bound Cond'
#################### ####################
# - Properties # - Properties
#################### ####################
default_choice: bpy.props.EnumProperty( default: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
name='Bound Face',
description='A choice of default boundary face',
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)),
)
#################### ####################
# - UI # - UI
#################### ####################
def draw_value(self, col: bpy.types.UILayout) -> None: def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'default_choice', text='') col.prop(self, self.blfields['default'], text='')
#################### ####################
# - Computation of Default Value # - Computation of Default Value
#################### ####################
@property @property
def value(self) -> td.BoundarySpec: def value(self) -> td.BoundaryEdge:
return { return self.default.tidy3d_boundary_edge
'PML': td.PML(num_layers=12),
'PEC': td.PECBoundary(),
'PMC': td.PMCBoundary(),
'PERIODIC': td.Periodic(),
}[self.default_choice]
@value.setter @value.setter
def value(self, value: typ.Literal['PML', 'PEC', 'PMC', 'PERIODIC']) -> None: def value(self, value: ct.BoundCondType) -> None:
self.default_choice = value self.default = value
#################### ####################
@ -56,10 +50,10 @@ class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
class MaxwellBoundCondSocketDef(base.SocketDef): class MaxwellBoundCondSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundCond socket_type: ct.SocketType = ct.SocketType.MaxwellBoundCond
default_choice: typ.Literal['PML', 'PEC', 'PMC', 'PERIODIC'] = 'PML' default: ct.BoundCondType = ct.BoundCondType.Pml
def init(self, bl_socket: MaxwellBoundCondBLSocket) -> None: def init(self, bl_socket: MaxwellBoundCondBLSocket) -> None:
bl_socket.value = self.default_choice bl_socket.default = self.default
#################### ####################

View File

@ -1,91 +1,59 @@
"""Implements the `MaxwellBoundCondsBLSocket` socket."""
import bpy import bpy
import tidy3d as td import tidy3d as td
from blender_maxwell.utils import bl_cache, logger
from ... import contracts as ct from ... import contracts as ct
from .. import base from .. import base
BOUND_FACE_ITEMS = [ log = logger.get(__name__)
('PML', 'PML', 'Perfectly matched layer'),
('PEC', 'PEC', 'Perfect electrical conductor'),
('PMC', 'PMC', 'Perfect magnetic conductor'),
('PERIODIC', 'Periodic', 'Infinitely periodic layer'),
]
BOUND_MAP = {
'PML': td.PML(),
'PEC': td.PECBoundary(),
'PMC': td.PMCBoundary(),
'PERIODIC': td.Periodic(),
}
class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket): class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
"""Describes a set of boundary conditions to apply to a simulation domain.
Attributes:
show_definition: Toggle to show/hide default per-axis boundary conditions.
x_pos: Default boundary condition to apply at the boundary of the sim domain's positive x-axis.
x_neg: Default boundary condition to apply at the boundary of the sim domain's negative x-axis.
y_pos: Default boundary condition to apply at the boundary of the sim domain's positive y-axis.
y_neg: Default boundary condition to apply at the boundary of the sim domain's negative y-axis.
z_pos: Default boundary condition to apply at the boundary of the sim domain's positive z-axis.
z_neg: Default boundary condition to apply at the boundary of the sim domain's negative z-axis.
"""
socket_type = ct.SocketType.MaxwellBoundConds socket_type = ct.SocketType.MaxwellBoundConds
bl_label = 'Maxwell Bound Box' bl_label = 'Maxwell Bound Box'
#################### ####################
# - Properties # - Properties
#################### ####################
show_definition: bpy.props.BoolProperty( show_definition: bool = bl_cache.BLField(False, prop_ui=True)
name='Show Bounds Definition',
description='Toggle to show bound faces',
default=False,
update=(lambda self, context: self.on_prop_changed('show_definition', context)),
)
x_pos: bpy.props.EnumProperty( x_pos: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
name='+x Bound Face', x_neg: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
description='+x choice of default boundary face', y_pos: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
items=BOUND_FACE_ITEMS, y_neg: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
default='PML', z_pos: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
update=(lambda self, context: self.on_prop_changed('x_pos', context)), z_neg: ct.BoundCondType = bl_cache.BLField(ct.BoundCondType.Pml, prop_ui=True)
)
x_neg: bpy.props.EnumProperty(
name='-x Bound Face',
description='-x choice of default boundary face',
items=BOUND_FACE_ITEMS,
default='PML',
update=(lambda self, context: self.on_prop_changed('x_neg', context)),
)
y_pos: bpy.props.EnumProperty(
name='+y Bound Face',
description='+y choice of default boundary face',
items=BOUND_FACE_ITEMS,
default='PML',
update=(lambda self, context: self.on_prop_changed('y_pos', context)),
)
y_neg: bpy.props.EnumProperty(
name='-y Bound Face',
description='-y choice of default boundary face',
items=BOUND_FACE_ITEMS,
default='PML',
update=(lambda self, context: self.on_prop_changed('y_neg', context)),
)
z_pos: bpy.props.EnumProperty(
name='+z Bound Face',
description='+z choice of default boundary face',
items=BOUND_FACE_ITEMS,
default='PML',
update=(lambda self, context: self.on_prop_changed('z_pos', context)),
)
z_neg: bpy.props.EnumProperty(
name='-z Bound Face',
description='-z choice of default boundary face',
items=BOUND_FACE_ITEMS,
default='PML',
update=(lambda self, context: self.on_prop_changed('z_neg', context)),
)
#################### ####################
# - UI # - UI
#################### ####################
def draw_label_row(self, row: bpy.types.UILayout, text) -> None: def draw_label_row(self, row: bpy.types.UILayout, text) -> None:
row.label(text=text) row.label(text=text)
row.prop(self, 'show_definition', toggle=True, text='', icon='MOD_LENGTH') row.prop(
self,
self.blfields['show_definition'],
toggle=True,
text='',
icon=ct.Icon.ToggleSocketInfo,
)
def draw_value(self, col: bpy.types.UILayout) -> None: def draw_value(self, col: bpy.types.UILayout) -> None:
if not self.show_definition: if self.show_definition:
return
for axis in ['x', 'y', 'z']: for axis in ['x', 'y', 'z']:
row = col.row(align=False) row = col.row(align=False)
split = row.split(factor=0.2, align=False) split = row.split(factor=0.2, align=False)
@ -96,28 +64,38 @@ class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
_col.label(text=' +') _col.label(text=' +')
_col = split.column(align=True) _col = split.column(align=True)
_col.prop(self, axis + '_neg', text='') _col.prop(self, self.blfields[axis + '_neg'], text='')
_col.prop(self, axis + '_pos', text='') _col.prop(self, self.blfields[axis + '_pos'], text='')
draw_value_array = draw_value
#################### ####################
# - Computation of Default Value # - Computation of Default Value
#################### ####################
@property @property
def value(self) -> td.BoundarySpec: def value(self) -> td.BoundarySpec:
"""Compute a user-defined default value for simulation boundary conditions, from certain common/sensible options.
Each half-axis has a selection pulled from `ct.BoundCondType`.
Returns:
A usable `tidy3d` boundary specification.
"""
log.debug(
'%s|%s: Computing default value for Boundary Conditions',
self.node.sim_node_name,
self.bl_label,
)
return td.BoundarySpec( return td.BoundarySpec(
x=td.Boundary( x=td.Boundary(
plus=BOUND_MAP[self.x_pos], plus=self.x_pos.tidy3d_boundary_edge,
minus=BOUND_MAP[self.x_neg], minus=self.x_neg.tidy3d_boundary_edge,
), ),
y=td.Boundary( y=td.Boundary(
plus=BOUND_MAP[self.y_pos], plus=self.y_pos.tidy3d_boundary_edge,
minus=BOUND_MAP[self.y_neg], minus=self.y_neg.tidy3d_boundary_edge,
), ),
z=td.Boundary( z=td.Boundary(
plus=BOUND_MAP[self.z_pos], plus=self.z_pos.tidy3d_boundary_edge,
minus=BOUND_MAP[self.z_neg], minus=self.z_neg.tidy3d_boundary_edge,
), ),
) )
@ -128,8 +106,20 @@ class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
class MaxwellBoundCondsSocketDef(base.SocketDef): class MaxwellBoundCondsSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundConds socket_type: ct.SocketType = ct.SocketType.MaxwellBoundConds
default_x_pos: ct.BoundCondType = ct.BoundCondType.Pml
default_x_neg: ct.BoundCondType = ct.BoundCondType.Pml
default_y_pos: ct.BoundCondType = ct.BoundCondType.Pml
default_y_neg: ct.BoundCondType = ct.BoundCondType.Pml
default_z_pos: ct.BoundCondType = ct.BoundCondType.Pml
default_z_neg: ct.BoundCondType = ct.BoundCondType.Pml
def init(self, bl_socket: MaxwellBoundCondsBLSocket) -> None: def init(self, bl_socket: MaxwellBoundCondsBLSocket) -> None:
pass bl_socket.x_pos = self.default_x_pos
bl_socket.x_neg = self.default_x_neg
bl_socket.y_pos = self.default_y_pos
bl_socket.y_neg = self.default_y_neg
bl_socket.z_pos = self.default_z_pos
bl_socket.z_neg = self.default_z_neg
#################### ####################