diff --git a/TODO.md b/TODO.md index 1ae57a8..e5cb7b3 100644 --- a/TODO.md +++ b/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 diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py index 4121f6f..0f57a80 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py @@ -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', diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/node_types.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/node_types.py index 323b1a5..e165e5d 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/node_types.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/node_types.py @@ -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() diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py new file mode 100644 index 0000000..08a7d3d --- /dev/null +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py @@ -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] diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py index 480eba6..aa89f84 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py @@ -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, diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/__init__.py index 014227b..55ded94 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/__init__.py @@ -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, } diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_box.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_box.py deleted file mode 100644 index ba8d6ff..0000000 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_box.py +++ /dev/null @@ -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)} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/__init__.py new file mode 100644 index 0000000..5d8e1c1 --- /dev/null +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/__init__.py @@ -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, +} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/absorbing_bound_face.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/absorbing_bound_cond.py similarity index 100% rename from src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/absorbing_bound_face.py rename to src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/absorbing_bound_cond.py diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/bloch_bound_face.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py similarity index 100% rename from src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/bloch_bound_face.py rename to src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/pml_bound_cond.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/pml_bound_cond.py new file mode 100644 index 0000000..8d80a43 --- /dev/null +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/pml_bound_cond.py @@ -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: + + 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)} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_conds.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_conds.py new file mode 100644 index 0000000..fe47b01 --- /dev/null +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_conds.py @@ -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)} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/__init__.py deleted file mode 100644 index c33323c..0000000 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/__init__.py +++ /dev/null @@ -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, -} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/pec_bound_face.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/pec_bound_face.py deleted file mode 100644 index 41fac16..0000000 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/pec_bound_face.py +++ /dev/null @@ -1,5 +0,0 @@ -#################### -# - Blender Registration -#################### -BL_REGISTER = [] -BL_NODES = {} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/periodic_bound_face.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/periodic_bound_face.py deleted file mode 100644 index 41fac16..0000000 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/periodic_bound_face.py +++ /dev/null @@ -1,5 +0,0 @@ -#################### -# - Blender Registration -#################### -BL_REGISTER = [] -BL_NODES = {} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/pmc_bound_face.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/pmc_bound_face.py deleted file mode 100644 index 41fac16..0000000 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/pmc_bound_face.py +++ /dev/null @@ -1,5 +0,0 @@ -#################### -# - Blender Registration -#################### -BL_REGISTER = [] -BL_NODES = {} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/pml_bound_face.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/pml_bound_face.py deleted file mode 100644 index 41fac16..0000000 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_faces/pml_bound_face.py +++ /dev/null @@ -1,5 +0,0 @@ -#################### -# - Blender Registration -#################### -BL_REGISTER = [] -BL_NODES = {} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py index e1110bd..fd63b3f 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py @@ -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 diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/bound_cond.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/bound_cond.py index d3c0e3c..64d426b 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/bound_cond.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/bound_cond.py @@ -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 #################### diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/bound_conds.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/bound_conds.py index 9705a28..ddec91e 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/bound_conds.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/bound_conds.py @@ -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 ####################