Compare commits
2 Commits
14b98d219e
...
22daa6c603
Author | SHA1 | Date |
---|---|---|
Sofus Albert Høgsbro Rose | 22daa6c603 | |
Sofus Albert Høgsbro Rose | 9df0d20c68 |
23
TODO.md
23
TODO.md
|
@ -1,7 +1,7 @@
|
||||||
# Working TODO
|
# Working TODO
|
||||||
- [x] Wave Constant
|
- [x] Wave Constant
|
||||||
- Sources
|
- Sources
|
||||||
- [ ] Temporal Shapes / Continuous Wave Temporal Shape
|
- [x] Temporal Shapes / Continuous Wave Temporal Shape
|
||||||
- [ ] Temporal Shapes / Symbolic Temporal Shape
|
- [ ] Temporal Shapes / Symbolic Temporal Shape
|
||||||
- [ ] Plane Wave Source
|
- [ ] Plane Wave Source
|
||||||
- [ ] TFSF Source
|
- [ ] TFSF Source
|
||||||
|
@ -57,6 +57,11 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# VALIDATE
|
||||||
|
- [ ] Does the imaginary part of a complex phasor scale with the real part? Ex. when doing `V/m -> V/um` conversion, does the phase also scale by 1 million?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Nodes
|
# Nodes
|
||||||
## Analysis
|
## Analysis
|
||||||
|
@ -144,9 +149,10 @@
|
||||||
## Sources
|
## Sources
|
||||||
- [x] Temporal Shapes / Gaussian Pulse Temporal Shape
|
- [x] Temporal Shapes / Gaussian Pulse Temporal Shape
|
||||||
- [x] Temporal Shapes / Continuous Wave Temporal Shape
|
- [x] Temporal Shapes / Continuous Wave Temporal Shape
|
||||||
- [ ] Temporal Shapes / Symbolic Temporal Shape
|
- [ ] Merge Gaussian Pulse and Continuous Wave w/a socket set thing, since the I/O is effectively identical.
|
||||||
- [ ] Specify a Sympy function to generate appropriate array based on
|
- [ ] Temporal Shapes / Expr Temporal Shape
|
||||||
- [ ] Temporal Shapes / Data Temporal Shape
|
- [ ] Specify a Sympy function / data to generate envelope data.
|
||||||
|
- [ ] Merge with the above.
|
||||||
|
|
||||||
- [x] Point Dipole Source
|
- [x] Point Dipole Source
|
||||||
- [ ] Use a viz mesh, not empty (empty doesn't play well with alpha hashing).
|
- [ ] Use a viz mesh, not empty (empty doesn't play well with alpha hashing).
|
||||||
|
@ -585,4 +591,11 @@ Unreported:
|
||||||
|
|
||||||
- [ ] Shader visualizations approximated from medium `nk` into a shader node graph, aka. a generic BSDF.
|
- [ ] Shader visualizations approximated from medium `nk` into a shader node graph, aka. a generic BSDF.
|
||||||
|
|
||||||
- [ ] Web importer that gets material data from refractiveindex.info.
|
|
||||||
|
|
||||||
|
- [ ] Easy conversion of lazyarrayrange to mu/sigma frequency for easy computation of pulse fits from data.
|
||||||
|
|
||||||
|
|
||||||
|
- [ ] IDEA: Hand-craft a faster `spu.convert_to`. <https://github.com/sympy/sympy/blob/a44299273eeb4838beaee9af3b688f2f44d7702f/sympy/physics/units/util.py#L51-L129>
|
||||||
|
|
||||||
|
- [ ] We should probably communicate with the `sympy` upstream about our deep usage of unit systems. They might be interested in the various workarounds :)
|
||||||
|
|
BIN
src/blender_maxwell/assets/internal/source/_source_point_dipole.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/source/_source_point_dipole.blend (Stored with Git LFS)
100644
Binary file not shown.
|
@ -24,3 +24,10 @@ class OperatorType(enum.StrEnum):
|
||||||
|
|
||||||
# Node: Tidy3DWebImporter
|
# Node: Tidy3DWebImporter
|
||||||
NodeLoadCloudSim = enum.auto()
|
NodeLoadCloudSim = enum.auto()
|
||||||
|
|
||||||
|
# Node: Tidy3DWebExporter
|
||||||
|
NodeUploadSimulation = enum.auto()
|
||||||
|
NodeRunSimulation = enum.auto()
|
||||||
|
NodeReloadTrackedTask = enum.auto()
|
||||||
|
NodeEstCostTrackedTask = enum.auto()
|
||||||
|
ReleaseTrackedTask = enum.auto()
|
||||||
|
|
|
@ -40,7 +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, SimSpaceAxis
|
from .sim_types import BoundCondType, SimSpaceAxis, manual_amp_time
|
||||||
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
|
||||||
|
@ -80,6 +80,7 @@ __all__ = [
|
||||||
'NodeType',
|
'NodeType',
|
||||||
'BoundCondType',
|
'BoundCondType',
|
||||||
'SimSpaceAxis',
|
'SimSpaceAxis',
|
||||||
|
'manual_amp_time',
|
||||||
'NodeCategory',
|
'NodeCategory',
|
||||||
'NODE_CAT_LABELS',
|
'NODE_CAT_LABELS',
|
||||||
'ManagedObjType',
|
'ManagedObjType',
|
||||||
|
|
|
@ -5,11 +5,13 @@ import typing as typ
|
||||||
import bpy
|
import bpy
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
|
||||||
from blender_maxwell.utils import blender_type_enum
|
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from .socket_types import SocketType
|
from .socket_types import SocketType
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
BL_SOCKET_DESCR_ANNOT_STRING = ':: '
|
BL_SOCKET_DESCR_ANNOT_STRING = ':: '
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,13 +192,14 @@ class BLSocketType(enum.StrEnum):
|
||||||
return {
|
return {
|
||||||
BLST.Color: S.Vec4,
|
BLST.Color: S.Vec4,
|
||||||
BLST.Rotation: S.Vec3,
|
BLST.Rotation: S.Vec3,
|
||||||
|
BLST.Vector: S.Vec3,
|
||||||
BLST.VectorAcceleration: S.Vec3,
|
BLST.VectorAcceleration: S.Vec3,
|
||||||
BLST.VectorDirection: S.Vec3,
|
BLST.VectorDirection: S.Vec3,
|
||||||
BLST.VectorEuler: S.Vec3,
|
BLST.VectorEuler: S.Vec3,
|
||||||
BLST.VectorTranslation: S.Vec3,
|
BLST.VectorTranslation: S.Vec3,
|
||||||
BLST.VectorVelocity: S.Vec3,
|
BLST.VectorVelocity: S.Vec3,
|
||||||
BLST.VectorXYZ: S.Vec3,
|
BLST.VectorXYZ: S.Vec3,
|
||||||
}.get(self, {S.Scalar})
|
}.get(self, S.Scalar)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unambiguous_physical_type(self) -> spux.PhysicalType | None:
|
def unambiguous_physical_type(self) -> spux.PhysicalType | None:
|
||||||
|
|
|
@ -111,7 +111,10 @@ class CapabilitiesFlow:
|
||||||
for name in self.must_match
|
for name in self.must_match
|
||||||
)
|
)
|
||||||
# ∀b (b ∈ A) Constraint
|
# ∀b (b ∈ A) Constraint
|
||||||
and self.present_any.issubset(other.allow_any)
|
and (
|
||||||
|
self.present_any & other.allow_any
|
||||||
|
or (not self.present_any and not self.allow_any)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -485,13 +488,13 @@ class LazyArrayRangeFlow:
|
||||||
if isinstance(self.start, spux.SympyType):
|
if isinstance(self.start, spux.SympyType):
|
||||||
start_mathtype = spux.MathType.from_expr(self.start)
|
start_mathtype = spux.MathType.from_expr(self.start)
|
||||||
else:
|
else:
|
||||||
start_mathtype = spux.MathType.from_pytype(self.start)
|
start_mathtype = spux.MathType.from_pytype(type(self.start))
|
||||||
|
|
||||||
# Get Stop Mathtype
|
# Get Stop Mathtype
|
||||||
if isinstance(self.stop, spux.SympyType):
|
if isinstance(self.stop, spux.SympyType):
|
||||||
stop_mathtype = spux.MathType.from_expr(self.stop)
|
stop_mathtype = spux.MathType.from_expr(self.stop)
|
||||||
else:
|
else:
|
||||||
stop_mathtype = spux.MathType.from_pytype(self.stop)
|
stop_mathtype = spux.MathType.from_pytype(type(self.stop))
|
||||||
|
|
||||||
# Check Equal
|
# Check Equal
|
||||||
if start_mathtype != stop_mathtype:
|
if start_mathtype != stop_mathtype:
|
||||||
|
@ -739,6 +742,10 @@ class LazyArrayRangeFlow:
|
||||||
msg = f'Invalid kind: {kind}'
|
msg = f'Invalid kind: {kind}'
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
@functools.cached_property
|
||||||
|
def realize_array(self) -> ArrayFlow:
|
||||||
|
return self.realize()
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Params
|
# - Params
|
||||||
|
@ -748,21 +755,52 @@ class ParamsFlow:
|
||||||
func_args: list[spux.SympyExpr] = dataclasses.field(default_factory=list)
|
func_args: list[spux.SympyExpr] = dataclasses.field(default_factory=list)
|
||||||
func_kwargs: dict[str, spux.SympyExpr] = dataclasses.field(default_factory=dict)
|
func_kwargs: dict[str, spux.SympyExpr] = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
|
symbols: frozenset[spux.Symbol] = frozenset()
|
||||||
|
|
||||||
|
@functools.cached_property
|
||||||
|
def sorted_symbols(self) -> list[sp.Symbol]:
|
||||||
|
"""Retrieves all symbols by concatenating int, real, and complex symbols, and sorting them by name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
All symbols valid for use in the expression.
|
||||||
|
"""
|
||||||
|
return sorted(self.symbols, key=lambda sym: sym.name)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Scaled Func Args
|
# - Scaled Func Args
|
||||||
####################
|
####################
|
||||||
def scaled_func_args(self, unit_system: spux.UnitSystem):
|
def scaled_func_args(
|
||||||
|
self,
|
||||||
|
unit_system: spux.UnitSystem,
|
||||||
|
symbol_values: dict[spux.Symbol, spux.SympyExpr] = MappingProxyType({}),
|
||||||
|
):
|
||||||
"""Return the function arguments, scaled to the unit system, stripped of units, and cast to jax-compatible arguments."""
|
"""Return the function arguments, scaled to the unit system, stripped of units, and cast to jax-compatible arguments."""
|
||||||
|
if not all(sym in self.symbols for sym in symbol_values):
|
||||||
|
msg = f"Symbols in {symbol_values} don't perfectly match the ParamsFlow symbols {self.symbols}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
spux.convert_to_unit_system(func_arg, unit_system, use_jax_array=True)
|
spux.scale_to_unit_system(arg, unit_system, use_jax_array=True)
|
||||||
for func_arg in self.func_args
|
if arg not in symbol_values
|
||||||
|
else symbol_values[arg]
|
||||||
|
for arg in self.func_args
|
||||||
]
|
]
|
||||||
|
|
||||||
def scaled_func_kwargs(self, unit_system: spux.UnitSystem):
|
def scaled_func_kwargs(
|
||||||
|
self,
|
||||||
|
unit_system: spux.UnitSystem,
|
||||||
|
symbol_values: dict[spux.Symbol, spux.SympyExpr] = MappingProxyType({}),
|
||||||
|
):
|
||||||
"""Return the function arguments, scaled to the unit system, stripped of units, and cast to jax-compatible arguments."""
|
"""Return the function arguments, scaled to the unit system, stripped of units, and cast to jax-compatible arguments."""
|
||||||
|
if not all(sym in self.symbols for sym in symbol_values):
|
||||||
|
msg = f"Symbols in {symbol_values} don't perfectly match the ParamsFlow symbols {self.symbols}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
arg_name: spux.convert_to_unit_system(arg, unit_system, use_jax_array=True)
|
arg_name: spux.convert_to_unit_system(arg, unit_system, use_jax_array=True)
|
||||||
for arg_name, arg in self.func_args
|
if arg not in symbol_values
|
||||||
|
else symbol_values[arg]
|
||||||
|
for arg_name, arg in self.func_kwargs.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -780,16 +818,19 @@ class ParamsFlow:
|
||||||
return ParamsFlow(
|
return ParamsFlow(
|
||||||
func_args=self.func_args + other.func_args,
|
func_args=self.func_args + other.func_args,
|
||||||
func_kwargs=self.func_kwargs | other.func_kwargs,
|
func_kwargs=self.func_kwargs | other.func_kwargs,
|
||||||
|
symbols=self.symbols | other.symbols,
|
||||||
)
|
)
|
||||||
|
|
||||||
def compose_within(
|
def compose_within(
|
||||||
self,
|
self,
|
||||||
enclosing_func_args: list[spux.SympyExpr] = (),
|
enclosing_func_args: list[spux.SympyExpr] = (),
|
||||||
enclosing_func_kwargs: dict[str, spux.SympyExpr] = MappingProxyType({}),
|
enclosing_func_kwargs: dict[str, spux.SympyExpr] = MappingProxyType({}),
|
||||||
|
enclosing_symbols: frozenset[spux.Symbol] = frozenset(),
|
||||||
) -> typ.Self:
|
) -> typ.Self:
|
||||||
return ParamsFlow(
|
return ParamsFlow(
|
||||||
func_args=self.func_args + list(enclosing_func_args),
|
func_args=self.func_args + list(enclosing_func_args),
|
||||||
func_kwargs=self.func_kwargs | dict(enclosing_func_kwargs),
|
func_kwargs=self.func_kwargs | dict(enclosing_func_kwargs),
|
||||||
|
symbols=self.symbols | enclosing_symbols,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -804,8 +845,6 @@ class InfoFlow:
|
||||||
default_factory=dict
|
default_factory=dict
|
||||||
) ## TODO: Rename to dim_idxs
|
) ## TODO: Rename to dim_idxs
|
||||||
|
|
||||||
## TODO: Add PhysicalType
|
|
||||||
|
|
||||||
@functools.cached_property
|
@functools.cached_property
|
||||||
def dim_lens(self) -> dict[str, int]:
|
def dim_lens(self) -> dict[str, int]:
|
||||||
return {dim_name: len(dim_idx) for dim_name, dim_idx in self.dim_idx.items()}
|
return {dim_name: len(dim_idx) for dim_name, dim_idx in self.dim_idx.items()}
|
||||||
|
@ -820,6 +859,13 @@ class InfoFlow:
|
||||||
def dim_units(self) -> dict[str, spux.Unit]:
|
def dim_units(self) -> dict[str, spux.Unit]:
|
||||||
return {dim_name: dim_idx.unit for dim_name, dim_idx in self.dim_idx.items()}
|
return {dim_name: dim_idx.unit for dim_name, dim_idx in self.dim_idx.items()}
|
||||||
|
|
||||||
|
@functools.cached_property
|
||||||
|
def dim_physical_types(self) -> dict[str, spux.PhysicalType]:
|
||||||
|
return {
|
||||||
|
dim_name: spux.PhysicalType.from_unit(dim_idx.unit)
|
||||||
|
for dim_name, dim_idx in self.dim_idx.items()
|
||||||
|
}
|
||||||
|
|
||||||
@functools.cached_property
|
@functools.cached_property
|
||||||
def dim_idx_arrays(self) -> list[jax.Array]:
|
def dim_idx_arrays(self) -> list[jax.Array]:
|
||||||
return [
|
return [
|
||||||
|
@ -850,6 +896,21 @@ class InfoFlow:
|
||||||
####################
|
####################
|
||||||
# - Methods
|
# - Methods
|
||||||
####################
|
####################
|
||||||
|
def rescale_dim_idxs(self, new_dim_idxs: dict[str, LazyArrayRangeFlow]) -> typ.Self:
|
||||||
|
return InfoFlow(
|
||||||
|
# Dimensions
|
||||||
|
dim_names=self.dim_names,
|
||||||
|
dim_idx={
|
||||||
|
_dim_name: new_dim_idxs.get(_dim_name, dim_idx)
|
||||||
|
for _dim_name, dim_idx in self.dim_idx.items()
|
||||||
|
},
|
||||||
|
# Outputs
|
||||||
|
output_name=self.output_name,
|
||||||
|
output_shape=self.output_shape,
|
||||||
|
output_mathtype=self.output_mathtype,
|
||||||
|
output_unit=self.output_unit,
|
||||||
|
)
|
||||||
|
|
||||||
def delete_dimension(self, dim_name: str) -> typ.Self:
|
def delete_dimension(self, dim_name: str) -> typ.Self:
|
||||||
"""Delete a dimension."""
|
"""Delete a dimension."""
|
||||||
return InfoFlow(
|
return InfoFlow(
|
||||||
|
|
|
@ -43,10 +43,9 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
|
||||||
|
|
||||||
# Sources
|
# Sources
|
||||||
## Sources / Temporal Shapes
|
## Sources / Temporal Shapes
|
||||||
GaussianPulseTemporalShape = enum.auto()
|
PulseTemporalShape = enum.auto()
|
||||||
ContinuousWaveTemporalShape = enum.auto()
|
WaveTemporalShape = enum.auto()
|
||||||
SymbolicTemporalShape = enum.auto()
|
ExprTemporalShape = enum.auto()
|
||||||
DataTemporalShape = enum.auto()
|
|
||||||
## Sources /
|
## Sources /
|
||||||
PointDipoleSource = enum.auto()
|
PointDipoleSource = enum.auto()
|
||||||
PlaneWaveSource = enum.auto()
|
PlaneWaveSource = enum.auto()
|
||||||
|
|
|
@ -3,11 +3,45 @@
|
||||||
import enum
|
import enum
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
import jax.numpy as jnp
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
|
|
||||||
|
def manual_amp_time(self, time: float) -> complex:
|
||||||
|
"""Copied implementation of `pulse.amp_time` for `tidy3d` temporal shapes, which replaces use of `numpy` with `jax.numpy` for `jit`-ability.
|
||||||
|
|
||||||
|
Since the function is detached from the method, `self` is not implicitly available. It should be pre-defined from a real source time object using `functools.partial`, before `jax.jit`ing.
|
||||||
|
|
||||||
|
## License
|
||||||
|
**This function is directly copied from `tidy3d`**.
|
||||||
|
As such, it should be considered available under the `tidy3d` license (as of writing, LGPL 2.1): <https://github.com/flexcompute/tidy3d/blob/develop/LICENSE>
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
Permalink to GitHub source code: <https://github.com/flexcompute/tidy3d/blob/3ee34904eb6687a86a5fb3f4ed6d3295c228cd83/tidy3d/components/source.py#L143C1-L163C25>
|
||||||
|
"""
|
||||||
|
twidth = 1.0 / (2 * jnp.pi * self.fwidth)
|
||||||
|
omega0 = 2 * jnp.pi * self.freq0
|
||||||
|
time_shifted = time - self.offset * twidth
|
||||||
|
|
||||||
|
offset = jnp.exp(1j * self.phase)
|
||||||
|
oscillation = jnp.exp(-1j * omega0 * time)
|
||||||
|
amp = jnp.exp(-(time_shifted**2) / 2 / twidth**2) * self.amplitude
|
||||||
|
|
||||||
|
pulse_amp = offset * oscillation * amp
|
||||||
|
|
||||||
|
# subtract out DC component
|
||||||
|
if self.remove_dc_component:
|
||||||
|
pulse_amp = pulse_amp * (1j + time_shifted / twidth**2 / omega0)
|
||||||
|
else:
|
||||||
|
# 1j to make it agree in large omega0 limit
|
||||||
|
pulse_amp = pulse_amp * 1j
|
||||||
|
|
||||||
|
return pulse_amp
|
||||||
|
|
||||||
|
|
||||||
## TODO: Sim Domain type, w/pydantic checks!
|
## TODO: Sim Domain type, w/pydantic checks!
|
||||||
|
|
||||||
|
|
||||||
class SimSpaceAxis(enum.StrEnum):
|
class SimSpaceAxis(enum.StrEnum):
|
||||||
"""The axis labels of the global simulation coordinate system."""
|
"""The axis labels of the global simulation coordinate system."""
|
||||||
|
|
||||||
|
@ -61,13 +95,13 @@ class BoundCondType(enum.StrEnum):
|
||||||
Attributes:
|
Attributes:
|
||||||
Pml: "Perfectly Matched Layer" models infinite free space.
|
Pml: "Perfectly Matched Layer" models infinite free space.
|
||||||
**Should be placed sufficiently far** (ex. $\frac{\lambda}{2}) from any active structures to mitigate divergence.
|
**Should be placed sufficiently far** (ex. $\frac{\lambda}{2}) from any active structures to mitigate divergence.
|
||||||
Periodic: Denotes Bloch-basedrepetition
|
Periodic: Denotes naive Bloch boundaries (aka. periodic w/phase shift of 0).
|
||||||
Pec: "Perfect Electrical Conductor" models a surface that perfectly reflects electric fields.
|
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.
|
Pmc: "Perfect Magnetic Conductor" models a surface that perfectly reflects the magnetic fields.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Pml = enum.auto()
|
Pml = enum.auto()
|
||||||
Periodic = enum.auto()
|
NaiveBloch = enum.auto()
|
||||||
Pec = enum.auto()
|
Pec = enum.auto()
|
||||||
Pmc = enum.auto()
|
Pmc = enum.auto()
|
||||||
|
|
||||||
|
@ -86,7 +120,7 @@ class BoundCondType(enum.StrEnum):
|
||||||
BCT.Pml: 'PML',
|
BCT.Pml: 'PML',
|
||||||
BCT.Pec: 'PEC',
|
BCT.Pec: 'PEC',
|
||||||
BCT.Pmc: 'PMC',
|
BCT.Pmc: 'PMC',
|
||||||
BCT.Periodic: 'Periodic',
|
BCT.NaiveBloch: 'NaiveBloch',
|
||||||
}[v]
|
}[v]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -115,5 +149,5 @@ class BoundCondType(enum.StrEnum):
|
||||||
BCT.Pml: td.PML(),
|
BCT.Pml: td.PML(),
|
||||||
BCT.Pec: td.PECBoundary(),
|
BCT.Pec: td.PECBoundary(),
|
||||||
BCT.Pmc: td.PMCBoundary(),
|
BCT.Pmc: td.PMCBoundary(),
|
||||||
BCT.Periodic: td.Periodic(),
|
BCT.NaiveBloch: td.Periodic(),
|
||||||
}[self]
|
}[self]
|
||||||
|
|
|
@ -5,33 +5,30 @@ from . import (
|
||||||
mediums,
|
mediums,
|
||||||
monitors,
|
monitors,
|
||||||
outputs,
|
outputs,
|
||||||
# simulations,
|
simulations,
|
||||||
# sources,
|
sources,
|
||||||
# structures,
|
structures,
|
||||||
# utilities,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*analysis.BL_REGISTER,
|
*analysis.BL_REGISTER,
|
||||||
*inputs.BL_REGISTER,
|
*inputs.BL_REGISTER,
|
||||||
*outputs.BL_REGISTER,
|
*outputs.BL_REGISTER,
|
||||||
# *sources.BL_REGISTER,
|
*sources.BL_REGISTER,
|
||||||
*mediums.BL_REGISTER,
|
*mediums.BL_REGISTER,
|
||||||
# *structures.BL_REGISTER,
|
*structures.BL_REGISTER,
|
||||||
*bounds.BL_REGISTER,
|
*bounds.BL_REGISTER,
|
||||||
*monitors.BL_REGISTER,
|
*monitors.BL_REGISTER,
|
||||||
# *simulations.BL_REGISTER,
|
*simulations.BL_REGISTER,
|
||||||
# *utilities.BL_REGISTER,
|
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**analysis.BL_NODES,
|
**analysis.BL_NODES,
|
||||||
**inputs.BL_NODES,
|
**inputs.BL_NODES,
|
||||||
**outputs.BL_NODES,
|
**outputs.BL_NODES,
|
||||||
# **sources.BL_NODES,
|
**sources.BL_NODES,
|
||||||
**mediums.BL_NODES,
|
**mediums.BL_NODES,
|
||||||
# **structures.BL_NODES,
|
**structures.BL_NODES,
|
||||||
**bounds.BL_NODES,
|
**bounds.BL_NODES,
|
||||||
**monitors.BL_NODES,
|
**monitors.BL_NODES,
|
||||||
# **simulations.BL_NODES,
|
**simulations.BL_NODES,
|
||||||
# **utilities.BL_NODES,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
}
|
}
|
||||||
output_socket_sets: typ.ClassVar = {
|
output_socket_sets: typ.ClassVar = {
|
||||||
'Sim Data': {'Monitor Data': sockets.MaxwellMonitorDataSocketDef()},
|
'Sim Data': {'Monitor Data': sockets.MaxwellMonitorDataSocketDef()},
|
||||||
'Monitor Data': {'Expr': sockets.ExprSocketDef()},
|
'Monitor Data': {'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array)},
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -409,7 +409,7 @@ class ExtractDataNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
# Trigger
|
# Trigger
|
||||||
'Data',
|
'Expr',
|
||||||
kind=ct.FlowKind.Info,
|
kind=ct.FlowKind.Info,
|
||||||
# Loaded
|
# Loaded
|
||||||
props={'monitor_data_type', 'extract_filter'},
|
props={'monitor_data_type', 'extract_filter'},
|
||||||
|
|
|
@ -110,10 +110,10 @@ class FilterMathNode(base.MaxwellSimNode):
|
||||||
bl_label = 'Filter Math'
|
bl_label = 'Filter Math'
|
||||||
|
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(),
|
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(),
|
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -187,7 +187,6 @@ class FilterMathNode(base.MaxwellSimNode):
|
||||||
def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
layout.prop(self, self.blfields['operation'], text='')
|
layout.prop(self, self.blfields['operation'], text='')
|
||||||
|
|
||||||
if self.active_socket_set == 'Dimensions':
|
|
||||||
if self.operation in [FilterOperation.PinLen1, FilterOperation.Pin]:
|
if self.operation in [FilterOperation.PinLen1, FilterOperation.Pin]:
|
||||||
layout.prop(self, self.blfields['dim_0'], text='')
|
layout.prop(self, self.blfields['dim_0'], text='')
|
||||||
|
|
||||||
|
@ -292,7 +291,7 @@ class FilterMathNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
return lazy_value_func.compose_within(
|
return lazy_value_func.compose_within(
|
||||||
operation.jax_func(axis_0, axis_1),
|
operation.jax_func(axis_0, axis_1),
|
||||||
enclosing_func_args=[int] if operation == 'PIN' else [],
|
enclosing_func_args=[int] if operation == FilterOperation.Pin else [],
|
||||||
supports_jax=True,
|
supports_jax=True,
|
||||||
)
|
)
|
||||||
return ct.FlowSignal.FlowPending
|
return ct.FlowSignal.FlowPending
|
||||||
|
@ -383,7 +382,7 @@ class FilterMathNode(base.MaxwellSimNode):
|
||||||
pinned_value = input_sockets['Value']
|
pinned_value = input_sockets['Value']
|
||||||
has_pinned_value = not ct.FlowSignal.check(pinned_value)
|
has_pinned_value = not ct.FlowSignal.check(pinned_value)
|
||||||
|
|
||||||
if props['operation'] == 'PIN' and has_pinned_value:
|
if props['operation'] == FilterOperation.Pin and has_pinned_value:
|
||||||
nearest_idx_to_value = info.dim_idx[dim_0].nearest_idx_of(
|
nearest_idx_to_value = info.dim_idx[dim_0].nearest_idx_of(
|
||||||
pinned_value, require_sorted=True
|
pinned_value, require_sorted=True
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import enum
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import jax
|
|
||||||
import jax.numpy as jnp
|
import jax.numpy as jnp
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
|
||||||
|
@ -138,6 +137,9 @@ class MapOperation(enum.StrEnum):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def by_element_shape(shape: tuple[int, ...] | None) -> list[typ.Self]:
|
def by_element_shape(shape: tuple[int, ...] | None) -> list[typ.Self]:
|
||||||
MO = MapOperation
|
MO = MapOperation
|
||||||
|
if shape == 'noshape':
|
||||||
|
return []
|
||||||
|
|
||||||
# By Number
|
# By Number
|
||||||
if shape is None:
|
if shape is None:
|
||||||
return [
|
return [
|
||||||
|
@ -259,7 +261,7 @@ class MapOperation(enum.StrEnum):
|
||||||
),
|
),
|
||||||
## TODO: Matrix -> Vec
|
## TODO: Matrix -> Vec
|
||||||
## TODO: Matrix -> Matrices
|
## TODO: Matrix -> Matrices
|
||||||
}.get(self, info)
|
}.get(self, info)()
|
||||||
|
|
||||||
|
|
||||||
class MapMathNode(base.MaxwellSimNode):
|
class MapMathNode(base.MaxwellSimNode):
|
||||||
|
@ -346,10 +348,10 @@ class MapMathNode(base.MaxwellSimNode):
|
||||||
bl_label = 'Map Math'
|
bl_label = 'Map Math'
|
||||||
|
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(),
|
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(),
|
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -366,12 +368,12 @@ class MapMathNode(base.MaxwellSimNode):
|
||||||
if has_info:
|
if has_info:
|
||||||
return info.output_shape
|
return info.output_shape
|
||||||
|
|
||||||
return None
|
return 'noshape'
|
||||||
|
|
||||||
output_shape: tuple[int, ...] | None = bl_cache.BLField(None)
|
output_shape: tuple[int, ...] | None = bl_cache.BLField(None)
|
||||||
|
|
||||||
def search_operations(self) -> list[ct.BLEnumElement]:
|
def search_operations(self) -> list[ct.BLEnumElement]:
|
||||||
if self.expr_output_shape is not None:
|
if self.expr_output_shape != 'noshape':
|
||||||
return [
|
return [
|
||||||
operation.bl_enum_element(i)
|
operation.bl_enum_element(i)
|
||||||
for i, operation in enumerate(
|
for i, operation in enumerate(
|
||||||
|
@ -401,7 +403,7 @@ class MapMathNode(base.MaxwellSimNode):
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
)
|
)
|
||||||
def on_input_changed(self):
|
def on_input_changed(self):
|
||||||
# if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
||||||
self.operation = bl_cache.Signal.ResetEnumItems
|
self.operation = bl_cache.Signal.ResetEnumItems
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
|
@ -449,7 +451,7 @@ class MapMathNode(base.MaxwellSimNode):
|
||||||
mapper = input_sockets['Mapper']
|
mapper = input_sockets['Mapper']
|
||||||
|
|
||||||
has_expr = not ct.FlowSignal.check(expr)
|
has_expr = not ct.FlowSignal.check(expr)
|
||||||
has_mapper = not ct.FlowSignal.check(expr)
|
has_mapper = not ct.FlowSignal.check(mapper)
|
||||||
|
|
||||||
if has_expr and operation is not None:
|
if has_expr and operation is not None:
|
||||||
if not has_mapper:
|
if not has_mapper:
|
||||||
|
@ -494,11 +496,11 @@ class MapMathNode(base.MaxwellSimNode):
|
||||||
# - Compute Auxiliary: Info / Params
|
# - Compute Auxiliary: Info / Params
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Data',
|
'Expr',
|
||||||
kind=ct.FlowKind.Info,
|
kind=ct.FlowKind.Info,
|
||||||
props={'active_socket_set', 'operation'},
|
props={'active_socket_set', 'operation'},
|
||||||
input_sockets={'Data'},
|
input_sockets={'Expr'},
|
||||||
input_socket_kinds={'Data': ct.FlowKind.Info},
|
input_socket_kinds={'Expr': ct.FlowKind.Info},
|
||||||
)
|
)
|
||||||
def compute_data_info(self, props: dict, input_sockets: dict) -> ct.InfoFlow:
|
def compute_data_info(self, props: dict, input_sockets: dict) -> ct.InfoFlow:
|
||||||
operation = props['operation']
|
operation = props['operation']
|
||||||
|
@ -512,13 +514,13 @@ class MapMathNode(base.MaxwellSimNode):
|
||||||
return ct.FlowSignal.FlowPending
|
return ct.FlowSignal.FlowPending
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Data',
|
'Expr',
|
||||||
kind=ct.FlowKind.Params,
|
kind=ct.FlowKind.Params,
|
||||||
input_sockets={'Data'},
|
input_sockets={'Expr'},
|
||||||
input_socket_kinds={'Data': ct.FlowKind.Params},
|
input_socket_kinds={'Expr': ct.FlowKind.Params},
|
||||||
)
|
)
|
||||||
def compute_data_params(self, input_sockets: dict) -> ct.ParamsFlow | ct.FlowSignal:
|
def compute_data_params(self, input_sockets: dict) -> ct.ParamsFlow | ct.FlowSignal:
|
||||||
return input_sockets['Data']
|
return input_sockets['Expr']
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -56,11 +56,11 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
bl_label = 'Operate Math'
|
bl_label = 'Operate Math'
|
||||||
|
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Expr L': sockets.ExprSocketDef(),
|
'Expr L': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
'Expr R': sockets.ExprSocketDef(),
|
'Expr R': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(),
|
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -2,8 +2,6 @@ import enum
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import jax
|
|
||||||
import jax.numpy as jnp
|
|
||||||
import jaxtyping as jtyp
|
import jaxtyping as jtyp
|
||||||
import matplotlib.axis as mpl_ax
|
import matplotlib.axis as mpl_ax
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
@ -196,6 +194,7 @@ class VizNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(
|
'Expr': sockets.ExprSocketDef(
|
||||||
|
active_kind=ct.FlowKind.Array,
|
||||||
symbols={_x := sp.Symbol('x', real=True)},
|
symbols={_x := sp.Symbol('x', real=True)},
|
||||||
default_value=2 * _x,
|
default_value=2 * _x,
|
||||||
),
|
),
|
||||||
|
@ -284,16 +283,57 @@ class VizNode(base.MaxwellSimNode):
|
||||||
socket_name='Expr',
|
socket_name='Expr',
|
||||||
input_sockets={'Expr'},
|
input_sockets={'Expr'},
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
input_socket_kinds={'Expr': ct.FlowKind.Info},
|
input_socket_kinds={'Expr': {ct.FlowKind.Info, ct.FlowKind.Params}},
|
||||||
input_sockets_optional={'Expr': True},
|
input_sockets_optional={'Expr': True},
|
||||||
)
|
)
|
||||||
def on_any_changed(self, input_sockets: dict):
|
def on_any_changed(self, input_sockets: dict):
|
||||||
if not ct.FlowSignal.check_single(
|
info = input_sockets['Expr'][ct.FlowKind.Info]
|
||||||
input_sockets['Expr'], ct.FlowSignal.FlowPending
|
params = input_sockets['Expr'][ct.FlowKind.Params]
|
||||||
):
|
|
||||||
|
has_info = not ct.FlowSignal.check(info)
|
||||||
|
has_params = not ct.FlowSignal.check(params)
|
||||||
|
|
||||||
|
# Reset Viz Mode/Target
|
||||||
|
has_nonpending_info = not ct.FlowSignal.check_single(
|
||||||
|
info, ct.FlowSignal.FlowPending
|
||||||
|
)
|
||||||
|
if has_nonpending_info:
|
||||||
self.viz_mode = bl_cache.Signal.ResetEnumItems
|
self.viz_mode = bl_cache.Signal.ResetEnumItems
|
||||||
self.viz_target = bl_cache.Signal.ResetEnumItems
|
self.viz_target = bl_cache.Signal.ResetEnumItems
|
||||||
|
|
||||||
|
# Provide Sockets for Symbol Realization
|
||||||
|
## -> This happens if Params contains not-yet-realized symbols.
|
||||||
|
if has_info and has_params and params.symbols:
|
||||||
|
if set(self.loose_input_sockets) != {
|
||||||
|
sym.name for sym in params.symbols if sym.name in info.dim_names
|
||||||
|
}:
|
||||||
|
self.loose_input_sockets = {
|
||||||
|
sym.name: sockets.ExprSocketDef(
|
||||||
|
active_kind=ct.FlowKind.LazyArrayRange,
|
||||||
|
shape=None,
|
||||||
|
mathtype=info.dim_mathtypes[sym.name],
|
||||||
|
physical_type=info.dim_physical_types[sym.name],
|
||||||
|
default_min=(
|
||||||
|
info.dim_idx[sym.name].start
|
||||||
|
if not sp.S(info.dim_idx[sym.name].start).is_infinite
|
||||||
|
else sp.S(0)
|
||||||
|
),
|
||||||
|
default_max=(
|
||||||
|
info.dim_idx[sym.name].start
|
||||||
|
if not sp.S(info.dim_idx[sym.name].stop).is_infinite
|
||||||
|
else sp.S(1)
|
||||||
|
),
|
||||||
|
default_steps=50,
|
||||||
|
)
|
||||||
|
for sym in sorted(
|
||||||
|
params.symbols, key=lambda el: info.dim_names.index(el.name)
|
||||||
|
)
|
||||||
|
if sym.name in info.dim_names
|
||||||
|
}
|
||||||
|
|
||||||
|
elif self.loose_input_sockets:
|
||||||
|
self.loose_input_sockets = {}
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name='viz_mode',
|
prop_name='viz_mode',
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
|
@ -309,39 +349,62 @@ class VizNode(base.MaxwellSimNode):
|
||||||
props={'viz_mode', 'viz_target', 'colormap'},
|
props={'viz_mode', 'viz_target', 'colormap'},
|
||||||
input_sockets={'Expr'},
|
input_sockets={'Expr'},
|
||||||
input_socket_kinds={
|
input_socket_kinds={
|
||||||
'Expr': {ct.FlowKind.Array, ct.FlowKind.LazyValueFunc, ct.FlowKind.Info}
|
'Expr': {ct.FlowKind.LazyValueFunc, ct.FlowKind.Info, ct.FlowKind.Params}
|
||||||
},
|
},
|
||||||
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
|
all_loose_input_sockets=True,
|
||||||
stop_propagation=True,
|
stop_propagation=True,
|
||||||
)
|
)
|
||||||
def on_show_plot(
|
def on_show_plot(
|
||||||
self,
|
self, managed_objs, props, input_sockets, loose_input_sockets, unit_systems
|
||||||
managed_objs: dict,
|
|
||||||
input_sockets: dict,
|
|
||||||
props: dict,
|
|
||||||
):
|
):
|
||||||
# Retrieve Inputs
|
# Retrieve Inputs
|
||||||
array_flow = input_sockets['Expr'][ct.FlowKind.Array]
|
|
||||||
info = input_sockets['Expr'][ct.FlowKind.Info]
|
info = input_sockets['Expr'][ct.FlowKind.Info]
|
||||||
|
params = input_sockets['Expr'][ct.FlowKind.Params]
|
||||||
|
|
||||||
|
has_info = not ct.FlowSignal.check(info)
|
||||||
|
has_params = not ct.FlowSignal.check(params)
|
||||||
|
|
||||||
# Check Flow
|
|
||||||
if (
|
if (
|
||||||
any(ct.FlowSignal.check(inp) for inp in [array_flow, info])
|
not has_info
|
||||||
|
or not has_params
|
||||||
or props['viz_mode'] is None
|
or props['viz_mode'] is None
|
||||||
or props['viz_target'] is None
|
or props['viz_target'] is None
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Viz Target
|
# Compute Data
|
||||||
|
lazy_value_func = input_sockets['Expr'][ct.FlowKind.LazyValueFunc]
|
||||||
|
symbol_values = (
|
||||||
|
loose_input_sockets
|
||||||
|
if not params.symbols
|
||||||
|
else {
|
||||||
|
sym: loose_input_sockets[sym.name]
|
||||||
|
.realize_array.rescale_to_unit(info.dim_units[sym.name])
|
||||||
|
.values
|
||||||
|
for sym in params.sorted_symbols
|
||||||
|
}
|
||||||
|
)
|
||||||
|
data = lazy_value_func.func_jax(
|
||||||
|
*params.scaled_func_args(
|
||||||
|
unit_systems['BlenderUnits'], symbol_values=symbol_values
|
||||||
|
),
|
||||||
|
**params.scaled_func_kwargs(
|
||||||
|
unit_systems['BlenderUnits'], symbol_values=symbol_values
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if params.symbols:
|
||||||
|
info = info.rescale_dim_idxs(loose_input_sockets)
|
||||||
|
|
||||||
|
# Visualize by-Target
|
||||||
if props['viz_target'] == VizTarget.Plot2D:
|
if props['viz_target'] == VizTarget.Plot2D:
|
||||||
managed_objs['plot'].mpl_plot_to_image(
|
managed_objs['plot'].mpl_plot_to_image(
|
||||||
lambda ax: VizMode.to_plotter(props['viz_mode'])(
|
lambda ax: VizMode.to_plotter(props['viz_mode'])(data, info, ax),
|
||||||
array_flow.values, info, ax
|
|
||||||
),
|
|
||||||
bl_select=True,
|
bl_select=True,
|
||||||
)
|
)
|
||||||
if props['viz_target'] == VizTarget.Pixels:
|
if props['viz_target'] == VizTarget.Pixels:
|
||||||
managed_objs['plot'].map_2d_to_image(
|
managed_objs['plot'].map_2d_to_image(
|
||||||
array_flow.values,
|
data,
|
||||||
colormap=props['colormap'],
|
colormap=props['colormap'],
|
||||||
bl_select=True,
|
bl_select=True,
|
||||||
)
|
)
|
||||||
|
|
|
@ -477,6 +477,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
for socket_name, socket_def in created_sockets.items():
|
for socket_name, socket_def in created_sockets.items():
|
||||||
socket_def.preinit(all_bl_sockets[socket_name])
|
socket_def.preinit(all_bl_sockets[socket_name])
|
||||||
socket_def.init(all_bl_sockets[socket_name])
|
socket_def.init(all_bl_sockets[socket_name])
|
||||||
|
socket_def.postinit(all_bl_sockets[socket_name])
|
||||||
|
|
||||||
def _sync_sockets(self) -> None:
|
def _sync_sockets(self) -> None:
|
||||||
"""Synchronize the node's sockets with the active sockets.
|
"""Synchronize the node's sockets with the active sockets.
|
||||||
|
|
|
@ -208,7 +208,7 @@ class BlochBoundCondNode(base.MaxwellSimNode):
|
||||||
return td.Periodic()
|
return td.Periodic()
|
||||||
|
|
||||||
# Source-Derived
|
# Source-Derived
|
||||||
if props['active_socket_set'] == 'Naive':
|
if props['active_socket_set'] == 'Source-Derived':
|
||||||
sim_domain = input_sockets['Sim Domain']
|
sim_domain = input_sockets['Sim Domain']
|
||||||
valid_sim_axis = props['valid_sim_axis']
|
valid_sim_axis = props['valid_sim_axis']
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,8 @@ class PhysicalConstantNode(base.MaxwellSimNode):
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_props(self, _, col: bpy.types.UILayout) -> None:
|
def draw_props(self, _, col: bpy.types.UILayout) -> None:
|
||||||
|
col.prop(self, self.blfields['physical_type'], text='')
|
||||||
|
|
||||||
row = col.row(align=True)
|
row = col.row(align=True)
|
||||||
row.prop(self, self.blfields['mathtype'], text='')
|
row.prop(self, self.blfields['mathtype'], text='')
|
||||||
row.prop(self, self.blfields['size'], text='')
|
row.prop(self, self.blfields['size'], text='')
|
||||||
|
@ -74,25 +76,34 @@ class PhysicalConstantNode(base.MaxwellSimNode):
|
||||||
# - Events
|
# - Events
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name={'physical_type', 'mathtype', 'size'},
|
# Trigger
|
||||||
|
prop_name={'physical_type'},
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
props={'physical_type', 'mathtype', 'size'},
|
# Loaded
|
||||||
|
props={'physical_type'},
|
||||||
)
|
)
|
||||||
def on_mathtype_or_size_changed(self, props) -> None:
|
def on_physical_type_changed(self, props) -> None:
|
||||||
"""Change the input/output expression sockets to match the mathtype and size declared in the node."""
|
"""Change the input/output expression sockets to match the mathtype and size declared in the node."""
|
||||||
shape = props['size'].shape
|
|
||||||
|
|
||||||
# Set Input Socket Physical Type
|
# Set Input Socket Physical Type
|
||||||
if self.inputs['Value'].physical_type != props['physical_type']:
|
if self.inputs['Value'].physical_type != props['physical_type']:
|
||||||
self.inputs['Value'].physical_type = props['physical_type']
|
self.inputs['Value'].physical_type = props['physical_type']
|
||||||
self.search_mathtypes = bl_cache.Signal.ResetEnumItems
|
self.mathtype = bl_cache.Signal.ResetEnumItems
|
||||||
self.search_sizes = bl_cache.Signal.ResetEnumItems
|
self.size = bl_cache.Signal.ResetEnumItems
|
||||||
|
|
||||||
|
@events.on_value_changed(
|
||||||
|
# Trigger
|
||||||
|
prop_name={'mathtype', 'size'},
|
||||||
|
run_on_init=True,
|
||||||
|
# Loaded
|
||||||
|
props={'physical_type', 'mathtype', 'size'},
|
||||||
|
)
|
||||||
|
def on_mathtype_or_size_changed(self, props) -> None:
|
||||||
# Set Input Socket Math Type
|
# Set Input Socket Math Type
|
||||||
if self.inputs['Value'].mathtype != props['mathtype']:
|
if self.inputs['Value'].mathtype != props['mathtype']:
|
||||||
self.inputs['Value'].mathtype = props['mathtype']
|
self.inputs['Value'].mathtype = props['mathtype']
|
||||||
|
|
||||||
# Set Input Socket Shape
|
# Set Input Socket Shape
|
||||||
|
shape = props['size'].shape
|
||||||
if self.inputs['Value'].shape != shape:
|
if self.inputs['Value'].shape != shape:
|
||||||
self.inputs['Value'].shape = shape
|
self.inputs['Value'].shape = shape
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,9 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
if has_freq:
|
if has_freq:
|
||||||
return input_sockets['Freq']
|
return input_sockets['Freq']
|
||||||
|
|
||||||
return sci_constants.vac_speed_of_light / input_sockets['WL']
|
return spu.convert_to(
|
||||||
|
sci_constants.vac_speed_of_light / input_sockets['WL'], spux.THz
|
||||||
|
)
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'WL',
|
'WL',
|
||||||
|
|
|
@ -301,15 +301,16 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
@events.on_show_plot(
|
@events.on_show_plot(
|
||||||
managed_objs={'plot'},
|
managed_objs={'plot'},
|
||||||
props={'material'},
|
props={'medium'},
|
||||||
stop_propagation=True,
|
stop_propagation=True,
|
||||||
)
|
)
|
||||||
def on_show_plot(
|
def on_show_plot(
|
||||||
self,
|
self,
|
||||||
managed_objs: dict,
|
managed_objs,
|
||||||
|
props,
|
||||||
):
|
):
|
||||||
managed_objs['plot'].mpl_plot_to_image(
|
managed_objs['plot'].mpl_plot_to_image(
|
||||||
lambda ax: self.medium.plot(self.medium.frequency_range, ax=ax),
|
lambda ax: self.medium.plot(props['medium'].frequency_range, ax=ax),
|
||||||
bl_select=True,
|
bl_select=True,
|
||||||
)
|
)
|
||||||
## TODO: Plot based on Wl, not freq.
|
## TODO: Plot based on Wl, not freq.
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
#from . import file_exporters, viewer, web_exporters
|
# from . import file_exporters, viewer, web_exporters
|
||||||
from . import viewer
|
from . import viewer, web_exporters
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*viewer.BL_REGISTER,
|
*viewer.BL_REGISTER,
|
||||||
#*file_exporters.BL_REGISTER,
|
# *file_exporters.BL_REGISTER,
|
||||||
#*web_exporters.BL_REGISTER,
|
*web_exporters.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**viewer.BL_NODES,
|
**viewer.BL_NODES,
|
||||||
#**file_exporters.BL_NODES,
|
# **file_exporters.BL_NODES,
|
||||||
#**web_exporters.BL_NODES,
|
**web_exporters.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
import bpy
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.services import tdcloud
|
||||||
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
|
|
||||||
from ......services import tdcloud
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import sockets
|
from .... import sockets
|
||||||
from ... import base, events
|
from ... import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Web Uploader / Loader / Runner / Releaser
|
# - Web Uploader / Loader / Runner / Releaser
|
||||||
####################
|
####################
|
||||||
class UploadSimulation(bpy.types.Operator):
|
class UploadSimulation(bpy.types.Operator):
|
||||||
bl_idname = 'blender_maxwell.nodes__upload_simulation'
|
bl_idname = ct.OperatorType.NodeUploadSimulation
|
||||||
bl_label = 'Upload Tidy3D Simulation'
|
bl_label = 'Upload Tidy3D Simulation'
|
||||||
bl_description = 'Upload the attached (locked) simulation, such that it is ready to run on the Tidy3D cloud'
|
bl_description = 'Upload the attached (locked) simulation, such that it is ready to run on the Tidy3D cloud'
|
||||||
|
|
||||||
|
@ -23,7 +30,7 @@ class UploadSimulation(bpy.types.Operator):
|
||||||
and context.node.lock_tree
|
and context.node.lock_tree
|
||||||
and tdcloud.IS_AUTHENTICATED
|
and tdcloud.IS_AUTHENTICATED
|
||||||
and not context.node.tracked_task_id
|
and not context.node.tracked_task_id
|
||||||
and context.node.inputs['FDTD Sim'].is_linked
|
and context.node.inputs['Sim'].is_linked
|
||||||
)
|
)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
@ -33,7 +40,7 @@ class UploadSimulation(bpy.types.Operator):
|
||||||
|
|
||||||
|
|
||||||
class RunSimulation(bpy.types.Operator):
|
class RunSimulation(bpy.types.Operator):
|
||||||
bl_idname = 'blender_maxwell.nodes__run_simulation'
|
bl_idname = ct.OperatorType.NodeRunSimulation
|
||||||
bl_label = 'Run Tracked Tidy3D Sim'
|
bl_label = 'Run Tracked Tidy3D Sim'
|
||||||
bl_description = 'Run the currently tracked simulation task'
|
bl_description = 'Run the currently tracked simulation task'
|
||||||
|
|
||||||
|
@ -61,7 +68,7 @@ class RunSimulation(bpy.types.Operator):
|
||||||
|
|
||||||
|
|
||||||
class ReloadTrackedTask(bpy.types.Operator):
|
class ReloadTrackedTask(bpy.types.Operator):
|
||||||
bl_idname = 'blender_maxwell.nodes__reload_tracked_task'
|
bl_idname = ct.OperatorType.NodeReloadTrackedTask
|
||||||
bl_label = 'Reload Tracked Tidy3D Cloud Task'
|
bl_label = 'Reload Tracked Tidy3D Cloud Task'
|
||||||
bl_description = 'Reload the currently tracked simulation task'
|
bl_description = 'Reload the currently tracked simulation task'
|
||||||
|
|
||||||
|
@ -86,7 +93,7 @@ class ReloadTrackedTask(bpy.types.Operator):
|
||||||
|
|
||||||
|
|
||||||
class EstCostTrackedTask(bpy.types.Operator):
|
class EstCostTrackedTask(bpy.types.Operator):
|
||||||
bl_idname = 'blender_maxwell.nodes__est_cost_tracked_task'
|
bl_idname = ct.OperatorType.NodeEstCostTrackedTask
|
||||||
bl_label = 'Est Cost of Tracked Tidy3D Cloud Task'
|
bl_label = 'Est Cost of Tracked Tidy3D Cloud Task'
|
||||||
bl_description = 'Reload the currently tracked simulation task'
|
bl_description = 'Reload the currently tracked simulation task'
|
||||||
|
|
||||||
|
@ -113,7 +120,7 @@ class EstCostTrackedTask(bpy.types.Operator):
|
||||||
|
|
||||||
|
|
||||||
class ReleaseTrackedTask(bpy.types.Operator):
|
class ReleaseTrackedTask(bpy.types.Operator):
|
||||||
bl_idname = 'blender_maxwell.nodes__release_tracked_task'
|
bl_idname = ct.OperatorType.ReleaseTrackedTask
|
||||||
bl_label = 'Release Tracked Tidy3D Cloud Task'
|
bl_label = 'Release Tracked Tidy3D Cloud Task'
|
||||||
bl_description = 'Release the currently tracked simulation task'
|
bl_description = 'Release the currently tracked simulation task'
|
||||||
|
|
||||||
|
@ -140,92 +147,60 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.Tidy3DWebExporter
|
node_type = ct.NodeType.Tidy3DWebExporter
|
||||||
bl_label = 'Tidy3D Web Exporter'
|
bl_label = 'Tidy3D Web Exporter'
|
||||||
|
|
||||||
input_sockets = {
|
input_sockets: typ.ClassVar = {
|
||||||
'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(),
|
'Sim': sockets.MaxwellFDTDSimSocketDef(),
|
||||||
'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
|
'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
|
||||||
should_exist=False,
|
should_exist=False,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'Sim Data': sockets.Tidy3DCloudTaskSocketDef(
|
||||||
|
should_exist=True,
|
||||||
|
),
|
||||||
|
'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
|
||||||
|
should_exist=True,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
lock_tree: bpy.props.BoolProperty(
|
lock_tree: bool = bl_cache.BLField(False, prop_ui=True)
|
||||||
name='Whether to lock the attached tree',
|
tracked_task_id: str = bl_cache.BLField('', prop_ui=True)
|
||||||
description='Whether or not to lock the attached tree',
|
|
||||||
default=False,
|
|
||||||
update=lambda self, context: self.sync_lock_tree(context),
|
|
||||||
)
|
|
||||||
tracked_task_id: bpy.props.StringProperty(
|
|
||||||
name='Tracked Task ID',
|
|
||||||
description='The currently tracked task ID',
|
|
||||||
default='',
|
|
||||||
update=lambda self, context: self.sync_tracked_task_id(context),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cache
|
|
||||||
cache_total_monitor_data: bpy.props.FloatProperty(
|
|
||||||
name='(Cached) Total Monitor Data',
|
|
||||||
description='Required storage space by all monitors',
|
|
||||||
default=0.0,
|
|
||||||
)
|
|
||||||
cache_est_cost: bpy.props.FloatProperty(
|
|
||||||
name='(Cached) Estimated Total Cost',
|
|
||||||
description='Est. Cost in FlexCompute units',
|
|
||||||
default=-1.0,
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Sync Methods
|
# - Computed
|
||||||
####################
|
####################
|
||||||
def sync_lock_tree(self, context):
|
@bl_cache.cached_bl_property(persist=False)
|
||||||
if self.lock_tree:
|
def sim(self) -> td.Simulation | None:
|
||||||
self.trigger_event(ct.FlowEvent.EnableLock)
|
sim = self._compute_input('Sim')
|
||||||
self.locked = False
|
has_sim = not ct.FlowSignal.check(sim)
|
||||||
for bl_socket in self.inputs:
|
|
||||||
if bl_socket.name == 'FDTD Sim':
|
|
||||||
continue
|
|
||||||
bl_socket.locked = False
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.trigger_event(ct.FlowEvent.DisableLock)
|
|
||||||
|
|
||||||
self.on_prop_changed('lock_tree', context)
|
|
||||||
|
|
||||||
def sync_tracked_task_id(self, context):
|
|
||||||
# Select Tracked Task
|
|
||||||
if self.tracked_task_id:
|
|
||||||
cloud_task = tdcloud.TidyCloudTasks.task(self.tracked_task_id)
|
|
||||||
task_info = tdcloud.TidyCloudTasks.task_info(self.tracked_task_id)
|
|
||||||
|
|
||||||
self.loose_output_sockets = {
|
|
||||||
'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
|
|
||||||
should_exist=True,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
self.inputs['Cloud Task'].locked = True
|
|
||||||
|
|
||||||
# Release Tracked Task
|
|
||||||
else:
|
|
||||||
self.cache_est_cost = -1.0
|
|
||||||
self.loose_output_sockets = {}
|
|
||||||
self.inputs['Cloud Task'].on_prepare_new_task()
|
|
||||||
self.inputs['Cloud Task'].locked = False
|
|
||||||
|
|
||||||
self.on_prop_changed('tracked_task_id', context)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Output Socket Callbacks
|
|
||||||
####################
|
|
||||||
def validate_sim(self):
|
|
||||||
if (sim := self._compute_input('FDTD Sim')) is None:
|
|
||||||
msg = 'Tried to validate simulation, but none is attached'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
|
if has_sim:
|
||||||
sim.validate_pre_upload(source_required=True)
|
sim.validate_pre_upload(source_required=True)
|
||||||
|
return sim
|
||||||
|
return None
|
||||||
|
|
||||||
|
@bl_cache.cached_bl_property(persist=False)
|
||||||
|
def total_monitor_data(self) -> float:
|
||||||
|
if self.sim is not None:
|
||||||
|
return sum(self.sim.monitors_data_size.values())
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
@bl_cache.cached_bl_property(persist=False)
|
||||||
|
def est_cost(self) -> float | None:
|
||||||
|
if self.tracked_task_id != '':
|
||||||
|
task_info = tdcloud.TidyCloudTasks.task_info(self.tracked_task_id)
|
||||||
|
if task_info is not None:
|
||||||
|
return task_info.cost_est()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Methods
|
||||||
|
####################
|
||||||
def upload_sim(self):
|
def upload_sim(self):
|
||||||
if (sim := self._compute_input('FDTD Sim')) is None:
|
if self.sim is None:
|
||||||
msg = 'Tried to upload simulation, but none is attached'
|
msg = 'Tried to upload simulation, but none is attached'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
@ -242,7 +217,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
cloud_task = tdcloud.TidyCloudTasks.mk_task(
|
cloud_task = tdcloud.TidyCloudTasks.mk_task(
|
||||||
task_name=new_task[0],
|
task_name=new_task[0],
|
||||||
cloud_folder=new_task[1],
|
cloud_folder=new_task[1],
|
||||||
sim=sim,
|
sim=self.sim,
|
||||||
verbose=True,
|
verbose=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -271,22 +246,24 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
# Row: Upload Sim Buttons
|
# Row: Upload Sim Buttons
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator(
|
row.operator(
|
||||||
UploadSimulation.bl_idname,
|
ct.OperatorType.NodeUploadSimulation,
|
||||||
text='Upload',
|
text='Upload',
|
||||||
)
|
)
|
||||||
tree_lock_icon = 'LOCKED' if self.lock_tree else 'UNLOCKED'
|
tree_lock_icon = 'LOCKED' if self.lock_tree else 'UNLOCKED'
|
||||||
row.prop(self, 'lock_tree', toggle=True, icon=tree_lock_icon, text='')
|
row.prop(
|
||||||
|
self, self.blfields['lock_tree'], toggle=True, icon=tree_lock_icon, text=''
|
||||||
|
)
|
||||||
|
|
||||||
# Row: Run Sim Buttons
|
# Row: Run Sim Buttons
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator(
|
row.operator(
|
||||||
RunSimulation.bl_idname,
|
ct.OperatorType.NodeRunSimulation,
|
||||||
text='Run',
|
text='Run',
|
||||||
)
|
)
|
||||||
if self.tracked_task_id:
|
if self.tracked_task_id:
|
||||||
tree_lock_icon = 'LOOP_BACK'
|
tree_lock_icon = 'LOOP_BACK'
|
||||||
row.operator(
|
row.operator(
|
||||||
ReleaseTrackedTask.bl_idname,
|
ct.OperatorType.ReleaseTrackedTask,
|
||||||
icon='LOOP_BACK',
|
icon='LOOP_BACK',
|
||||||
text='',
|
text='',
|
||||||
)
|
)
|
||||||
|
@ -313,7 +290,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
col.label(icon=conn_icon)
|
col.label(icon=conn_icon)
|
||||||
|
|
||||||
# Simulation Info
|
# Simulation Info
|
||||||
if self.inputs['FDTD Sim'].is_linked:
|
if self.inputs['Sim'].is_linked:
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.alignment = 'CENTER'
|
row.alignment = 'CENTER'
|
||||||
row.label(text='Sim Info')
|
row.label(text='Sim Info')
|
||||||
|
@ -327,7 +304,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
## Split: Right Column
|
## Split: Right Column
|
||||||
col = split.column(align=False)
|
col = split.column(align=False)
|
||||||
col.alignment = 'RIGHT'
|
col.alignment = 'RIGHT'
|
||||||
col.label(text=f'{self.cache_total_monitor_data / 1_000_000:.2f}MB')
|
col.label(text=f'{self.total_monitor_data / 1_000_000:.2f}MB')
|
||||||
|
|
||||||
# Cloud Task Info
|
# Cloud Task Info
|
||||||
if self.tracked_task_id and tdcloud.IS_AUTHENTICATED:
|
if self.tracked_task_id and tdcloud.IS_AUTHENTICATED:
|
||||||
|
@ -348,12 +325,12 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
text=f'Status: {task_info.status.capitalize()}',
|
text=f'Status: {task_info.status.capitalize()}',
|
||||||
)
|
)
|
||||||
row.operator(
|
row.operator(
|
||||||
ReloadTrackedTask.bl_idname,
|
ct.OperatorType.NodeReloadTrackedTask,
|
||||||
text='',
|
text='',
|
||||||
icon='FILE_REFRESH',
|
icon='FILE_REFRESH',
|
||||||
)
|
)
|
||||||
row.operator(
|
row.operator(
|
||||||
EstCostTrackedTask.bl_idname,
|
ct.OperatorType.NodeEstCostTrackedTask,
|
||||||
text='',
|
text='',
|
||||||
icon='SORTTIME',
|
icon='SORTTIME',
|
||||||
)
|
)
|
||||||
|
@ -369,9 +346,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
col.label(text='Real Cost')
|
col.label(text='Real Cost')
|
||||||
|
|
||||||
## Split: Right Column
|
## Split: Right Column
|
||||||
cost_est = (
|
cost_est = f'{self.est_cost:.2f}' if self.est_cost >= 0 else 'TBD'
|
||||||
f'{self.cache_est_cost:.2f}' if self.cache_est_cost >= 0 else 'TBD'
|
|
||||||
)
|
|
||||||
cost_real = (
|
cost_real = (
|
||||||
f'{task_info.cost_real:.2f}'
|
f'{task_info.cost_real:.2f}'
|
||||||
if task_info.cost_real is not None
|
if task_info.cost_real is not None
|
||||||
|
@ -386,9 +361,37 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
# Connection Information
|
# Connection Information
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Events
|
||||||
|
####################
|
||||||
|
@events.on_value_changed(prop_name='lock_tree', props={'lock_tree'})
|
||||||
|
def on_lock_tree_changed(self, props):
|
||||||
|
if props['lock_tree']:
|
||||||
|
self.trigger_event(ct.FlowEvent.EnableLock)
|
||||||
|
self.locked = False
|
||||||
|
for bl_socket in self.inputs:
|
||||||
|
if bl_socket.name == 'Sim':
|
||||||
|
continue
|
||||||
|
bl_socket.locked = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.trigger_event(ct.FlowEvent.DisableLock)
|
||||||
|
|
||||||
|
@events.on_value_changed(prop_name='tracked_task_id', props={'tracked_task_id'})
|
||||||
|
def on_tracked_task_id_changed(self, props):
|
||||||
|
if props['tracked_task_id']:
|
||||||
|
self.inputs['Cloud Task'].locked = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.total_monitor_data = bl_cache.Signal.InvalidateCache
|
||||||
|
self.est_cost = bl_cache.Signal.InvalidateCache
|
||||||
|
self.inputs['Cloud Task'].on_prepare_new_task()
|
||||||
|
self.inputs['Cloud Task'].locked = False
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output Methods
|
# - Output Methods
|
||||||
####################
|
####################
|
||||||
|
## TODO: Retrieve simulation data if/when the simulation is done
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Cloud Task',
|
'Cloud Task',
|
||||||
input_sockets={'Cloud Task'},
|
input_sockets={'Cloud Task'},
|
||||||
|
@ -399,21 +402,6 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
####################
|
|
||||||
# - Output Methods
|
|
||||||
####################
|
|
||||||
@events.on_value_changed(
|
|
||||||
socket_name='FDTD Sim',
|
|
||||||
input_sockets={'FDTD Sim'},
|
|
||||||
)
|
|
||||||
def on_value_changed__fdtd_sim(self, input_sockets):
|
|
||||||
if (sim := self._compute_input('FDTD Sim')) is None:
|
|
||||||
self.cache_total_monitor_data = 0
|
|
||||||
return
|
|
||||||
|
|
||||||
sim.validate_pre_upload(source_required=True)
|
|
||||||
self.cache_total_monitor_data = sum(sim.monitors_data_size.values())
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Blender Registration
|
# - Blender Registration
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
# from . import sim_grid
|
# from . import sim_grid
|
||||||
# from . import sim_grid_axes
|
# from . import sim_grid_axes
|
||||||
from . import fdtd_sim, sim_domain
|
from . import fdtd_sim, sim_domain, combine
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
|
*combine.BL_REGISTER,
|
||||||
*sim_domain.BL_REGISTER,
|
*sim_domain.BL_REGISTER,
|
||||||
# *sim_grid.BL_REGISTER,
|
# *sim_grid.BL_REGISTER,
|
||||||
# *sim_grid_axes.BL_REGISTER,
|
# *sim_grid_axes.BL_REGISTER,
|
||||||
*fdtd_sim.BL_REGISTER,
|
*fdtd_sim.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
|
**combine.BL_NODES,
|
||||||
**sim_domain.BL_NODES,
|
**sim_domain.BL_NODES,
|
||||||
# **sim_grid.BL_NODES,
|
# **sim_grid.BL_NODES,
|
||||||
# **sim_grid_axes.BL_NODES,
|
# **sim_grid_axes.BL_NODES,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
|
||||||
|
from blender_maxwell.utils import bl_cache
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base, events
|
||||||
|
@ -19,24 +20,8 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
'Maxwell Sources': {},
|
'Maxwell Sources': {},
|
||||||
'Maxwell Structures': {},
|
'Maxwell Structures': {},
|
||||||
'Maxwell Monitors': {},
|
'Maxwell Monitors': {},
|
||||||
'Real 3D Vector': {f'x_{i}': sockets.RealNumberSocketDef() for i in range(3)},
|
|
||||||
# "Point 3D": {
|
|
||||||
# axis: sockets.PhysicalLengthSocketDef()
|
|
||||||
# for i, axis in zip(
|
|
||||||
# range(3),
|
|
||||||
# ["x", "y", "z"]
|
|
||||||
# )
|
|
||||||
# },
|
|
||||||
# "Size 3D": {
|
|
||||||
# axis_key: sockets.PhysicalLengthSocketDef()
|
|
||||||
# for i, axis_key, axis_label in zip(
|
|
||||||
# range(3),
|
|
||||||
# ["x_size", "y_size", "z_size"],
|
|
||||||
# ["X Size", "Y Size", "Z Size"],
|
|
||||||
# )
|
|
||||||
# },
|
|
||||||
}
|
}
|
||||||
output_socket_sets = {
|
output_socket_sets: typ.ClassVar = {
|
||||||
'Maxwell Sources': {
|
'Maxwell Sources': {
|
||||||
'Sources': sockets.MaxwellSourceSocketDef(
|
'Sources': sockets.MaxwellSourceSocketDef(
|
||||||
is_list=True,
|
is_list=True,
|
||||||
|
@ -52,43 +37,69 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
is_list=True,
|
is_list=True,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'Real 3D Vector': {
|
|
||||||
'Real 3D Vector': sockets.Real3DVectorSocketDef(),
|
|
||||||
},
|
|
||||||
# "Point 3D": {
|
|
||||||
# "3D Point": sockets.PhysicalPoint3DSocketDef(),
|
|
||||||
# },
|
|
||||||
# "Size 3D": {
|
|
||||||
# "3D Size": sockets.PhysicalSize3DSocketDef(),
|
|
||||||
# },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
amount: bpy.props.IntProperty(
|
####################
|
||||||
name='# Objects to Combine',
|
# - Draw
|
||||||
description='Amount of Objects to Combine',
|
####################
|
||||||
default=1,
|
amount: int = bl_cache.BLField(2, abs_min=1, prop_ui=True)
|
||||||
min=1,
|
|
||||||
# max=MAX_AMOUNT,
|
|
||||||
update=lambda self, context: self.on_prop_changed('amount', context),
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Draw
|
# - Draw
|
||||||
####################
|
####################
|
||||||
def draw_props(self, context, layout):
|
def draw_props(self, context, layout):
|
||||||
layout.prop(self, 'amount', text='#')
|
layout.prop(self, self.blfields['amount'], text='')
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Events
|
||||||
|
####################
|
||||||
|
@events.on_value_changed(
|
||||||
|
# Trigger
|
||||||
|
prop_name={'active_socket_set', 'amount'},
|
||||||
|
props={'active_socket_set', 'amount'},
|
||||||
|
run_on_init=True,
|
||||||
|
)
|
||||||
|
def on_inputs_changed(self, props):
|
||||||
|
if props['active_socket_set'] == 'Maxwell Sources':
|
||||||
|
if (
|
||||||
|
not self.loose_input_sockets
|
||||||
|
or not next(iter(self.loose_input_sockets)).startswith('Source')
|
||||||
|
or len(self.loose_input_sockets) != props['amount']
|
||||||
|
):
|
||||||
|
self.loose_input_sockets = {
|
||||||
|
f'Source #{i}': sockets.MaxwellSourceSocketDef()
|
||||||
|
for i in range(props['amount'])
|
||||||
|
}
|
||||||
|
|
||||||
|
elif props['active_socket_set'] == 'Maxwell Structures':
|
||||||
|
if (
|
||||||
|
not self.loose_input_sockets
|
||||||
|
or not next(iter(self.loose_input_sockets)).startswith('Structure')
|
||||||
|
or len(self.loose_input_sockets) != props['amount']
|
||||||
|
):
|
||||||
|
self.loose_input_sockets = {
|
||||||
|
f'Structure #{i}': sockets.MaxwellStructureSocketDef()
|
||||||
|
for i in range(props['amount'])
|
||||||
|
}
|
||||||
|
elif props['active_socket_set'] == 'Maxwell Monitors':
|
||||||
|
if (
|
||||||
|
not self.loose_input_sockets
|
||||||
|
or not next(iter(self.loose_input_sockets)).startswith('Monitor')
|
||||||
|
or len(self.loose_input_sockets) != props['amount']
|
||||||
|
):
|
||||||
|
self.loose_input_sockets = {
|
||||||
|
f'Monitor #{i}': sockets.MaxwellMonitorSocketDef()
|
||||||
|
for i in range(props['amount'])
|
||||||
|
}
|
||||||
|
elif self.loose_input_sockets:
|
||||||
|
self.loose_input_sockets = {}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
|
||||||
'Real 3D Vector', input_sockets={'x_0', 'x_1', 'x_2'}
|
|
||||||
)
|
|
||||||
def compute_real_3d_vector(self, input_sockets) -> sp.Expr:
|
|
||||||
return sp.Matrix([input_sockets[f'x_{i}'] for i in range(3)])
|
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Sources',
|
'Sources',
|
||||||
|
kind=ct.FlowKind.Array,
|
||||||
all_loose_input_sockets=True,
|
all_loose_input_sockets=True,
|
||||||
props={'amount'},
|
props={'amount'},
|
||||||
)
|
)
|
||||||
|
@ -97,6 +108,7 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Structures',
|
'Structures',
|
||||||
|
kind=ct.FlowKind.Array,
|
||||||
all_loose_input_sockets=True,
|
all_loose_input_sockets=True,
|
||||||
props={'amount'},
|
props={'amount'},
|
||||||
)
|
)
|
||||||
|
@ -105,45 +117,13 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Monitors',
|
'Monitors',
|
||||||
|
kind=ct.FlowKind.Array,
|
||||||
all_loose_input_sockets=True,
|
all_loose_input_sockets=True,
|
||||||
props={'amount'},
|
props={'amount'},
|
||||||
)
|
)
|
||||||
def compute_monitors(self, loose_input_sockets, props) -> sp.Expr:
|
def compute_monitors(self, loose_input_sockets, props) -> sp.Expr:
|
||||||
return [loose_input_sockets[f'Monitor #{i}'] for i in range(props['amount'])]
|
return [loose_input_sockets[f'Monitor #{i}'] for i in range(props['amount'])]
|
||||||
|
|
||||||
####################
|
|
||||||
# - Input Socket Compilation
|
|
||||||
####################
|
|
||||||
@events.on_value_changed(
|
|
||||||
prop_name='active_socket_set',
|
|
||||||
props={'active_socket_set', 'amount'},
|
|
||||||
run_on_init=True,
|
|
||||||
)
|
|
||||||
def on_value_changed__active_socket_set(self, props):
|
|
||||||
if props['active_socket_set'] == 'Maxwell Sources':
|
|
||||||
self.loose_input_sockets = {
|
|
||||||
f'Source #{i}': sockets.MaxwellSourceSocketDef()
|
|
||||||
for i in range(props['amount'])
|
|
||||||
}
|
|
||||||
elif props['active_socket_set'] == 'Maxwell Structures':
|
|
||||||
self.loose_input_sockets = {
|
|
||||||
f'Structure #{i}': sockets.MaxwellStructureSocketDef()
|
|
||||||
for i in range(props['amount'])
|
|
||||||
}
|
|
||||||
elif props['active_socket_set'] == 'Maxwell Monitors':
|
|
||||||
self.loose_input_sockets = {
|
|
||||||
f'Monitor #{i}': sockets.MaxwellMonitorSocketDef()
|
|
||||||
for i in range(props['amount'])
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
self.loose_input_sockets = {}
|
|
||||||
|
|
||||||
@events.on_value_changed(
|
|
||||||
prop_name='amount',
|
|
||||||
)
|
|
||||||
def on_value_changed__amount(self):
|
|
||||||
self.on_value_changed__active_socket_set()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Blender Registration
|
# - Blender Registration
|
||||||
|
@ -151,4 +131,4 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
CombineNode,
|
CombineNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {ct.NodeType.Combine: (ct.NodeCategory.MAXWELLSIM_UTILITIES)}
|
BL_NODES = {ct.NodeType.Combine: (ct.NodeCategory.MAXWELLSIM_SIMS)}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
|
@ -13,9 +15,9 @@ class FDTDSimNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Domain': sockets.MaxwellSimDomainSocketDef(),
|
|
||||||
'BCs': sockets.MaxwellBoundCondsSocketDef(),
|
'BCs': sockets.MaxwellBoundCondsSocketDef(),
|
||||||
|
'Domain': sockets.MaxwellSimDomainSocketDef(),
|
||||||
'Sources': sockets.MaxwellSourceSocketDef(
|
'Sources': sockets.MaxwellSourceSocketDef(
|
||||||
is_list=True,
|
is_list=True,
|
||||||
),
|
),
|
||||||
|
@ -26,32 +28,33 @@ class FDTDSimNode(base.MaxwellSimNode):
|
||||||
is_list=True,
|
is_list=True,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
output_sockets = {
|
output_sockets: typ.ClassVar = {
|
||||||
'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(),
|
'Sim': sockets.MaxwellFDTDSimSocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'FDTD Sim',
|
'Sim',
|
||||||
kind=ct.FlowKind.Value,
|
kind=ct.FlowKind.Value,
|
||||||
input_sockets={'Sources', 'Structures', 'Domain', 'BCs', 'Monitors'},
|
input_sockets={'Sources', 'Structures', 'Domain', 'BCs', 'Monitors'},
|
||||||
|
input_socket_kinds={
|
||||||
|
'Sources': ct.FlowKind.Array,
|
||||||
|
'Structures': ct.FlowKind.Array,
|
||||||
|
'Domain': ct.FlowKind.Value,
|
||||||
|
'BCs': ct.FlowKind.Value,
|
||||||
|
'Monitors': ct.FlowKind.Array,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
def compute_fdtd_sim(self, input_sockets: dict) -> sp.Expr:
|
def compute_fdtd_sim(self, input_sockets: dict) -> sp.Expr:
|
||||||
|
## TODO: Visualize the boundary conditions on top of the sim domain
|
||||||
sim_domain = input_sockets['Domain']
|
sim_domain = input_sockets['Domain']
|
||||||
sources = input_sockets['Sources']
|
sources = input_sockets['Sources']
|
||||||
structures = input_sockets['Structures']
|
structures = input_sockets['Structures']
|
||||||
bounds = input_sockets['BCs']
|
bounds = input_sockets['BCs']
|
||||||
monitors = input_sockets['Monitors']
|
monitors = input_sockets['Monitors']
|
||||||
|
|
||||||
# if not isinstance(sources, list):
|
|
||||||
# sources = [sources]
|
|
||||||
# if not isinstance(structures, list):
|
|
||||||
# structures = [structures]
|
|
||||||
# if not isinstance(monitors, list):
|
|
||||||
# monitors = [monitors]
|
|
||||||
|
|
||||||
return td.Simulation(
|
return td.Simulation(
|
||||||
**sim_domain, ## run_time=, size=, grid=, medium=
|
**sim_domain, ## run_time=, size=, grid=, medium=
|
||||||
structures=structures,
|
structures=structures,
|
||||||
|
|
|
@ -4,11 +4,15 @@ import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import managed_objs, sockets
|
||||||
from .. import base, events
|
from .. import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SimDomainNode(base.MaxwellSimNode):
|
class SimDomainNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.SimDomain
|
node_type = ct.NodeType.SimDomain
|
||||||
|
@ -16,12 +20,27 @@ class SimDomainNode(base.MaxwellSimNode):
|
||||||
use_sim_node_name = True
|
use_sim_node_name = True
|
||||||
|
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Duration': sockets.PhysicalTimeSocketDef(
|
'Duration': sockets.ExprSocketDef(
|
||||||
default_value=5 * spu.ps,
|
physical_type=spux.PhysicalType.Time,
|
||||||
default_unit=spu.ps,
|
default_unit=spu.picosecond,
|
||||||
|
default_value=5,
|
||||||
|
abs_min=0,
|
||||||
|
),
|
||||||
|
'Center': sockets.ExprSocketDef(
|
||||||
|
shape=(3,),
|
||||||
|
mathtype=spux.MathType.Real,
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_unit=spu.micrometer,
|
||||||
|
default_value=sp.Matrix([0, 0, 0]),
|
||||||
|
),
|
||||||
|
'Size': sockets.ExprSocketDef(
|
||||||
|
shape=(3,),
|
||||||
|
mathtype=spux.MathType.Real,
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_unit=spu.micrometer,
|
||||||
|
default_value=sp.Matrix([1, 1, 1]),
|
||||||
|
abs_min=0.001,
|
||||||
),
|
),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
|
||||||
'Size': sockets.PhysicalSize3DSocketDef(),
|
|
||||||
'Grid': sockets.MaxwellSimGridSocketDef(),
|
'Grid': sockets.MaxwellSimGridSocketDef(),
|
||||||
'Ambient Medium': sockets.MaxwellMediumSocketDef(),
|
'Ambient Medium': sockets.MaxwellMediumSocketDef(),
|
||||||
}
|
}
|
||||||
|
@ -34,44 +53,6 @@ class SimDomainNode(base.MaxwellSimNode):
|
||||||
'modifier': managed_objs.ManagedBLModifier,
|
'modifier': managed_objs.ManagedBLModifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
|
||||||
# - Events
|
|
||||||
####################
|
|
||||||
@events.on_value_changed(
|
|
||||||
socket_name={'Center', 'Size'},
|
|
||||||
prop_name='preview_active',
|
|
||||||
run_on_init=True,
|
|
||||||
props={'preview_active'},
|
|
||||||
input_sockets={'Center', 'Size'},
|
|
||||||
managed_objs={'mesh', 'modifier'},
|
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'BlenderUnits',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
def on_input_changed(
|
|
||||||
self,
|
|
||||||
props: dict,
|
|
||||||
managed_objs: dict,
|
|
||||||
input_sockets: dict,
|
|
||||||
unit_systems: dict,
|
|
||||||
):
|
|
||||||
# Push Input Values to GeoNodes Modifier
|
|
||||||
managed_objs['modifier'].bl_modifier(
|
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
|
||||||
'NODES',
|
|
||||||
{
|
|
||||||
'node_group': import_geonodes(GeoNodes.SimulationSimDomain),
|
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
|
||||||
'inputs': {
|
|
||||||
'Size': input_sockets['Size'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Outputs
|
# - Outputs
|
||||||
####################
|
####################
|
||||||
|
@ -94,6 +75,59 @@ class SimDomainNode(base.MaxwellSimNode):
|
||||||
'medium': input_sockets['Ambient Medium'],
|
'medium': input_sockets['Ambient Medium'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Preview
|
||||||
|
####################
|
||||||
|
@events.on_value_changed(
|
||||||
|
prop_name='preview_active',
|
||||||
|
run_on_init=True,
|
||||||
|
props={'preview_active'},
|
||||||
|
managed_objs={'mesh'},
|
||||||
|
)
|
||||||
|
def on_preview_changed(self, props, managed_objs) -> None:
|
||||||
|
mesh = managed_objs['mesh']
|
||||||
|
|
||||||
|
# Push Preview State to Managed Mesh
|
||||||
|
if props['preview_active']:
|
||||||
|
mesh.show_preview()
|
||||||
|
else:
|
||||||
|
mesh.hide_preview()
|
||||||
|
|
||||||
|
@events.on_value_changed(
|
||||||
|
## Trigger
|
||||||
|
socket_name={'Center', 'Size'},
|
||||||
|
run_on_init=True,
|
||||||
|
# Loaded
|
||||||
|
input_sockets={'Center', 'Size'},
|
||||||
|
managed_objs={'mesh', 'modifier'},
|
||||||
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
|
scale_input_sockets={
|
||||||
|
'Center': 'BlenderUnits',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def on_input_changed(
|
||||||
|
self,
|
||||||
|
managed_objs,
|
||||||
|
input_sockets,
|
||||||
|
unit_systems,
|
||||||
|
):
|
||||||
|
mesh = managed_objs['mesh']
|
||||||
|
modifier = managed_objs['modifier']
|
||||||
|
center = input_sockets['Center']
|
||||||
|
size = input_sockets['Size']
|
||||||
|
unit_system = unit_systems['BlenderUnits']
|
||||||
|
|
||||||
|
# Push Loose Input Values to GeoNodes Modifier
|
||||||
|
modifier.bl_modifier(
|
||||||
|
mesh.bl_object(location=center),
|
||||||
|
'NODES',
|
||||||
|
{
|
||||||
|
'node_group': import_geonodes(GeoNodes.SimulationSimDomain),
|
||||||
|
'inputs': {'Size': size},
|
||||||
|
'unit_system': unit_system,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Blender Registration
|
# - Blender Registration
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
# from . import uniform_current_source
|
from . import (
|
||||||
from . import plane_wave_source, point_dipole_source, temporal_shapes
|
# astigmatic_gaussian_beam_source,
|
||||||
|
# gaussian_beam_source,
|
||||||
# from . import gaussian_beam_source
|
# plane_wave_source,
|
||||||
# from . import astigmatic_gaussian_beam_source
|
point_dipole_source,
|
||||||
# from . import tfsf_source
|
temporal_shapes,
|
||||||
|
# tfsf_source,
|
||||||
|
# uniform_current_source,
|
||||||
|
)
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*temporal_shapes.BL_REGISTER,
|
*temporal_shapes.BL_REGISTER,
|
||||||
*point_dipole_source.BL_REGISTER,
|
*point_dipole_source.BL_REGISTER,
|
||||||
# *uniform_current_source.BL_REGISTER,
|
# *uniform_current_source.BL_REGISTER,
|
||||||
*plane_wave_source.BL_REGISTER,
|
# *plane_wave_source.BL_REGISTER,
|
||||||
# *gaussian_beam_source.BL_REGISTER,
|
# *gaussian_beam_source.BL_REGISTER,
|
||||||
# *astigmatic_gaussian_beam_source.BL_REGISTER,
|
# *astigmatic_gaussian_beam_source.BL_REGISTER,
|
||||||
# *tfsf_source.BL_REGISTER,
|
# *tfsf_source.BL_REGISTER,
|
||||||
|
@ -18,7 +21,7 @@ BL_NODES = {
|
||||||
**temporal_shapes.BL_NODES,
|
**temporal_shapes.BL_NODES,
|
||||||
**point_dipole_source.BL_NODES,
|
**point_dipole_source.BL_NODES,
|
||||||
# **uniform_current_source.BL_NODES,
|
# **uniform_current_source.BL_NODES,
|
||||||
**plane_wave_source.BL_NODES,
|
# **plane_wave_source.BL_NODES,
|
||||||
# **gaussian_beam_source.BL_NODES,
|
# **gaussian_beam_source.BL_NODES,
|
||||||
# **astigmatic_gaussian_beam_source.BL_NODES,
|
# **astigmatic_gaussian_beam_source.BL_NODES,
|
||||||
# **tfsf_source.BL_NODES,
|
# **tfsf_source.BL_NODES,
|
||||||
|
|
|
@ -1,64 +1,62 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import managed_objs, sockets
|
||||||
from .. import base, events
|
from .. import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PointDipoleSourceNode(base.MaxwellSimNode):
|
class PointDipoleSourceNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.PointDipoleSource
|
node_type = ct.NodeType.PointDipoleSource
|
||||||
bl_label = 'Point Dipole Source'
|
bl_label = 'Point Dipole Source'
|
||||||
|
use_sim_node_name = True
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.ExprSocketDef(
|
||||||
|
shape=(3,),
|
||||||
|
mathtype=spux.MathType.Real,
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_value=sp.Matrix([0, 0, 0]),
|
||||||
|
),
|
||||||
'Interpolate': sockets.BoolSocketDef(
|
'Interpolate': sockets.BoolSocketDef(
|
||||||
default_value=True,
|
default_value=True,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
output_sockets = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Source': sockets.MaxwellSourceSocketDef(),
|
'Source': sockets.MaxwellSourceSocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_obj_types = {
|
managed_obj_types: typ.ClassVar = {
|
||||||
'mesh': managed_objs.ManagedBLMesh,
|
'mesh': managed_objs.ManagedBLMesh,
|
||||||
|
'modifier': managed_objs.ManagedBLModifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
pol_axis: bpy.props.EnumProperty(
|
pol_axis: ct.SimSpaceAxis = bl_cache.BLField(ct.SimSpaceAxis.X, prop_ui=True)
|
||||||
name='Polarization Axis',
|
|
||||||
description='Polarization Axis',
|
|
||||||
items=[
|
|
||||||
('EX', 'Ex', 'Electric field in x-dir'),
|
|
||||||
('EY', 'Ey', 'Electric field in y-dir'),
|
|
||||||
('EZ', 'Ez', 'Electric field in z-dir'),
|
|
||||||
],
|
|
||||||
default='EX',
|
|
||||||
update=(lambda self, context: self.on_prop_changed('pol_axis', context)),
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_props(self, context, layout):
|
def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout):
|
||||||
split = layout.split(factor=0.6)
|
layout.prop(self, self.blfields['pol_axis'], expand=True)
|
||||||
|
|
||||||
col = split.column()
|
|
||||||
col.label(text='Pol Axis')
|
|
||||||
|
|
||||||
col = split.column()
|
|
||||||
col.prop(self, 'pol_axis', text='')
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Outputs
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Source',
|
'Source',
|
||||||
|
@ -76,9 +74,9 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
|
||||||
unit_systems: dict,
|
unit_systems: dict,
|
||||||
) -> td.PointDipole:
|
) -> td.PointDipole:
|
||||||
pol_axis = {
|
pol_axis = {
|
||||||
'EX': 'Ex',
|
ct.SimSpaceAxis.X: 'Ex',
|
||||||
'EY': 'Ey',
|
ct.SimSpaceAxis.Y: 'Ey',
|
||||||
'EZ': 'Ez',
|
ct.SimSpaceAxis.Z: 'Ez',
|
||||||
}[props['pol_axis']]
|
}[props['pol_axis']]
|
||||||
|
|
||||||
return td.PointDipole(
|
return td.PointDipole(
|
||||||
|
@ -88,39 +86,59 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
|
||||||
polarization=pol_axis,
|
polarization=pol_axis,
|
||||||
)
|
)
|
||||||
|
|
||||||
#####################
|
####################
|
||||||
## - Preview
|
# - Preview
|
||||||
#####################
|
####################
|
||||||
# @events.on_value_changed(
|
@events.on_value_changed(
|
||||||
# socket_name='Center',
|
prop_name='preview_active',
|
||||||
# input_sockets={'Center'},
|
run_on_init=True,
|
||||||
# managed_objs={'sphere_empty'},
|
props={'preview_active'},
|
||||||
# )
|
managed_objs={'mesh'},
|
||||||
# def on_value_changed__center(
|
)
|
||||||
# self,
|
def on_preview_changed(self, props, managed_objs) -> None:
|
||||||
# input_sockets: dict,
|
"""Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen."""
|
||||||
# managed_objs: dict[str, ct.schemas.ManagedObj],
|
mesh = managed_objs['mesh']
|
||||||
# ):
|
|
||||||
# _center = input_sockets['Center']
|
|
||||||
# center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
|
||||||
# ## TODO: Preview unit system?? Presume um for now
|
|
||||||
|
|
||||||
# mobj = managed_objs['sphere_empty']
|
# Push Preview State to Managed Mesh
|
||||||
# bl_object = mobj.bl_object('EMPTY')
|
if props['preview_active']:
|
||||||
# bl_object.location = center # tuple([float(el) for el in center])
|
mesh.show_preview()
|
||||||
|
else:
|
||||||
|
mesh.hide_preview()
|
||||||
|
|
||||||
# @events.on_show_preview(
|
@events.on_value_changed(
|
||||||
# managed_objs={'sphere_empty'},
|
socket_name={'Center'},
|
||||||
# )
|
prop_name='pol_axis',
|
||||||
# def on_show_preview(
|
run_on_init=True,
|
||||||
# self,
|
# Pass Data
|
||||||
# managed_objs: dict[str, ct.schemas.ManagedObj],
|
managed_objs={'mesh', 'modifier'},
|
||||||
# ):
|
props={'pol_axis'},
|
||||||
# managed_objs['sphere_empty'].show_preview(
|
input_sockets={'Center'},
|
||||||
# 'EMPTY',
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
# empty_display_type='SPHERE',
|
scale_input_sockets={'Center': 'BlenderUnits'},
|
||||||
# )
|
)
|
||||||
# managed_objs['sphere_empty'].bl_object('EMPTY').empty_display_size = 0.2
|
def on_inputs_changed(
|
||||||
|
self, managed_objs, props, input_sockets, unit_systems
|
||||||
|
) -> None:
|
||||||
|
mesh = managed_objs['mesh']
|
||||||
|
modifier = managed_objs['modifier']
|
||||||
|
center = input_sockets['Center']
|
||||||
|
unit_system = unit_systems['BlenderUnits']
|
||||||
|
axis = {
|
||||||
|
ct.SimSpaceAxis.X: 0,
|
||||||
|
ct.SimSpaceAxis.Y: 1,
|
||||||
|
ct.SimSpaceAxis.Z: 2,
|
||||||
|
}[props['pol_axis']]
|
||||||
|
|
||||||
|
# Push Loose Input Values to GeoNodes Modifier
|
||||||
|
modifier.bl_modifier(
|
||||||
|
mesh.bl_object(location=center),
|
||||||
|
'NODES',
|
||||||
|
{
|
||||||
|
'node_group': import_geonodes(GeoNodes.SourcePointDipole),
|
||||||
|
'inputs': {'Axis': axis},
|
||||||
|
'unit_system': unit_system,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
from . import gaussian_pulse_temporal_shape
|
# from . import expr_temporal_shape, pulse_temporal_shape, wave_temporal_shape
|
||||||
|
from . import pulse_temporal_shape, wave_temporal_shape
|
||||||
# from . import continuous_wave_temporal_shape
|
|
||||||
# from . import array_temporal_shape
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*gaussian_pulse_temporal_shape.BL_REGISTER,
|
*pulse_temporal_shape.BL_REGISTER,
|
||||||
# *continuous_wave_temporal_shape.BL_REGISTER,
|
*wave_temporal_shape.BL_REGISTER,
|
||||||
# *array_temporal_shape.BL_REGISTER,
|
# *expr_temporal_shape.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**gaussian_pulse_temporal_shape.BL_NODES,
|
**pulse_temporal_shape.BL_NODES,
|
||||||
# **continuous_wave_temporal_shape.BL_NODES,
|
**wave_temporal_shape.BL_NODES,
|
||||||
# **array_temporal_shape.BL_NODES,
|
# **expr_temporal_shape.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
import sympy.physics.units as spu
|
|
||||||
import tidy3d as td
|
|
||||||
|
|
||||||
from .... import contracts, sockets
|
|
||||||
from ... import base, events
|
|
||||||
|
|
||||||
|
|
||||||
class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode):
|
|
||||||
node_type = contracts.NodeType.ContinuousWaveTemporalShape
|
|
||||||
|
|
||||||
bl_label = 'Continuous Wave Temporal Shape'
|
|
||||||
# bl_icon = ...
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Sockets
|
|
||||||
####################
|
|
||||||
input_sockets = {
|
|
||||||
# "amplitude": sockets.RealNumberSocketDef(
|
|
||||||
# label="Temporal Shape",
|
|
||||||
# ), ## Should have a unit of some kind...
|
|
||||||
'phase': sockets.PhysicalAngleSocketDef(
|
|
||||||
label='Phase',
|
|
||||||
),
|
|
||||||
'freq_center': sockets.PhysicalFreqSocketDef(
|
|
||||||
label='Freq Center',
|
|
||||||
),
|
|
||||||
'freq_std': sockets.PhysicalFreqSocketDef(
|
|
||||||
label='Freq STD',
|
|
||||||
),
|
|
||||||
'time_delay_rel_ang_freq': sockets.RealNumberSocketDef(
|
|
||||||
label='Time Delay rel. Ang. Freq',
|
|
||||||
default_value=5.0,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
output_sockets = {
|
|
||||||
'temporal_shape': sockets.MaxwellTemporalShapeSocketDef(
|
|
||||||
label='Temporal Shape',
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Output Socket Computation
|
|
||||||
####################
|
|
||||||
@events.computes_output_socket('temporal_shape')
|
|
||||||
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
|
|
||||||
_phase = self.compute_input('phase')
|
|
||||||
_freq_center = self.compute_input('freq_center')
|
|
||||||
_freq_std = self.compute_input('freq_std')
|
|
||||||
time_delay_rel_ang_freq = self.compute_input('time_delay_rel_ang_freq')
|
|
||||||
|
|
||||||
cheating_amplitude = 1.0
|
|
||||||
phase = spu.convert_to(_phase, spu.radian) / spu.radian
|
|
||||||
freq_center = spu.convert_to(_freq_center, spu.hertz) / spu.hertz
|
|
||||||
freq_std = spu.convert_to(_freq_std, spu.hertz) / spu.hertz
|
|
||||||
|
|
||||||
return td.ContinuousWave(
|
|
||||||
amplitude=cheating_amplitude,
|
|
||||||
phase=phase,
|
|
||||||
freq0=freq_center,
|
|
||||||
fwidth=freq_std,
|
|
||||||
offset=time_delay_rel_ang_freq,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
ContinuousWaveTemporalShapeNode,
|
|
||||||
]
|
|
||||||
BL_NODES = {
|
|
||||||
contracts.NodeType.ContinuousWaveTemporalShape: (
|
|
||||||
contracts.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
|
|
||||||
import bpy
|
|
||||||
import numpy as np
|
|
||||||
import sympy.physics.units as spu
|
|
||||||
import tidy3d as td
|
|
||||||
|
|
||||||
from blender_maxwell.utils import extra_sympy_units as spuex
|
|
||||||
|
|
||||||
from .... import contracts as ct
|
|
||||||
from .... import managed_objs, sockets
|
|
||||||
from ... import base, events
|
|
||||||
|
|
||||||
|
|
||||||
class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
|
|
||||||
node_type = ct.NodeType.GaussianPulseTemporalShape
|
|
||||||
bl_label = 'Gaussian Pulse Temporal Shape'
|
|
||||||
# bl_icon = ...
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Sockets
|
|
||||||
####################
|
|
||||||
input_sockets = {
|
|
||||||
# "amplitude": sockets.RealNumberSocketDef(
|
|
||||||
# label="Temporal Shape",
|
|
||||||
# ), ## Should have a unit of some kind...
|
|
||||||
'Freq Center': sockets.PhysicalFreqSocketDef(
|
|
||||||
default_value=500 * spuex.terahertz,
|
|
||||||
),
|
|
||||||
'Freq Std.': sockets.PhysicalFreqSocketDef(
|
|
||||||
default_value=200 * spuex.terahertz,
|
|
||||||
),
|
|
||||||
'Phase': sockets.PhysicalAngleSocketDef(),
|
|
||||||
'Delay rel. AngFreq': sockets.RealNumberSocketDef(
|
|
||||||
default_value=5.0,
|
|
||||||
),
|
|
||||||
'Remove DC': sockets.BoolSocketDef(
|
|
||||||
default_value=True,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
output_sockets = {
|
|
||||||
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
|
||||||
}
|
|
||||||
|
|
||||||
managed_obj_types = {
|
|
||||||
'amp_time': managed_objs.ManagedBLImage,
|
|
||||||
}
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Properties
|
|
||||||
####################
|
|
||||||
plot_time_start: bpy.props.FloatProperty(
|
|
||||||
name='Plot Time Start (ps)',
|
|
||||||
description='The instance ID of a particular MaxwellSimNode instance, used to index caches',
|
|
||||||
default=0.0,
|
|
||||||
update=(lambda self, context: self.on_prop_changed('plot_time_start', context)),
|
|
||||||
)
|
|
||||||
plot_time_end: bpy.props.FloatProperty(
|
|
||||||
name='Plot Time End (ps)',
|
|
||||||
description='The instance ID of a particular MaxwellSimNode instance, used to index caches',
|
|
||||||
default=5,
|
|
||||||
update=(lambda self, context: self.on_prop_changed('plot_time_start', context)),
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - UI
|
|
||||||
####################
|
|
||||||
def draw_props(self, _, layout):
|
|
||||||
layout.label(text='Plot Settings')
|
|
||||||
split = layout.split(factor=0.6)
|
|
||||||
|
|
||||||
col = split.column()
|
|
||||||
col.label(text='t-Range (ps)')
|
|
||||||
|
|
||||||
col = split.column()
|
|
||||||
col.prop(self, 'plot_time_start', text='')
|
|
||||||
col.prop(self, 'plot_time_end', text='')
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Output Socket Computation
|
|
||||||
####################
|
|
||||||
@events.computes_output_socket(
|
|
||||||
'Temporal Shape',
|
|
||||||
input_sockets={
|
|
||||||
'Freq Center',
|
|
||||||
'Freq Std.',
|
|
||||||
'Phase',
|
|
||||||
'Delay rel. AngFreq',
|
|
||||||
'Remove DC',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
def compute_source(self, input_sockets: dict) -> td.GaussianPulse:
|
|
||||||
if (
|
|
||||||
(_freq_center := input_sockets['Freq Center']) is None
|
|
||||||
or (_freq_std := input_sockets['Freq Std.']) is None
|
|
||||||
or (_phase := input_sockets['Phase']) is None
|
|
||||||
or (time_delay_rel_ang_freq := input_sockets['Delay rel. AngFreq']) is None
|
|
||||||
or (remove_dc_component := input_sockets['Remove DC']) is None
|
|
||||||
):
|
|
||||||
raise ValueError('Inputs not defined')
|
|
||||||
|
|
||||||
cheating_amplitude = 1.0
|
|
||||||
freq_center = spu.convert_to(_freq_center, spu.hertz) / spu.hertz
|
|
||||||
freq_std = spu.convert_to(_freq_std, spu.hertz) / spu.hertz
|
|
||||||
phase = spu.convert_to(_phase, spu.radian) / spu.radian
|
|
||||||
|
|
||||||
return td.GaussianPulse(
|
|
||||||
amplitude=cheating_amplitude,
|
|
||||||
phase=phase,
|
|
||||||
freq0=freq_center,
|
|
||||||
fwidth=freq_std,
|
|
||||||
offset=time_delay_rel_ang_freq,
|
|
||||||
remove_dc_component=remove_dc_component,
|
|
||||||
)
|
|
||||||
|
|
||||||
@events.on_show_plot(
|
|
||||||
managed_objs={'amp_time'},
|
|
||||||
props={'plot_time_start', 'plot_time_end'},
|
|
||||||
output_sockets={'Temporal Shape'},
|
|
||||||
stop_propagation=True,
|
|
||||||
)
|
|
||||||
def on_show_plot(
|
|
||||||
self,
|
|
||||||
managed_objs: dict,
|
|
||||||
output_sockets: dict,
|
|
||||||
props: dict,
|
|
||||||
):
|
|
||||||
temporal_shape = output_sockets['Temporal Shape']
|
|
||||||
plot_time_start = props['plot_time_start'] * 1e-15
|
|
||||||
plot_time_end = props['plot_time_end'] * 1e-15
|
|
||||||
|
|
||||||
times = np.linspace(plot_time_start, plot_time_end)
|
|
||||||
|
|
||||||
managed_objs['amp_time'].mpl_plot_to_image(
|
|
||||||
lambda ax: temporal_shape.plot_spectrum(times, ax=ax),
|
|
||||||
bl_select=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
GaussianPulseTemporalShapeNode,
|
|
||||||
]
|
|
||||||
BL_NODES = {
|
|
||||||
ct.NodeType.GaussianPulseTemporalShape: (
|
|
||||||
ct.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
"""Implements the `PulseTemporalShapeNode`."""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
|
||||||
|
from .... import contracts as ct
|
||||||
|
from .... import managed_objs, sockets
|
||||||
|
from ... import base, events
|
||||||
|
|
||||||
|
|
||||||
|
class PulseTemporalShapeNode(base.MaxwellSimNode):
|
||||||
|
node_type = ct.NodeType.PulseTemporalShape
|
||||||
|
bl_label = 'Gaussian Pulse Temporal Shape'
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_sockets: typ.ClassVar = {
|
||||||
|
'max E': sockets.ExprSocketDef(
|
||||||
|
mathtype=spux.MathType.Complex,
|
||||||
|
physical_type=spux.PhysicalType.EField,
|
||||||
|
default_value=1 + 0j,
|
||||||
|
),
|
||||||
|
'μ Freq': sockets.ExprSocketDef(
|
||||||
|
physical_type=spux.PhysicalType.Freq,
|
||||||
|
default_unit=spux.THz,
|
||||||
|
default_value=500,
|
||||||
|
),
|
||||||
|
'σ Freq': sockets.ExprSocketDef(
|
||||||
|
physical_type=spux.PhysicalType.Freq,
|
||||||
|
default_unit=spux.THz,
|
||||||
|
default_value=200,
|
||||||
|
),
|
||||||
|
'Offset Time': sockets.ExprSocketDef(default_value=5, abs_min=2.5),
|
||||||
|
'Remove DC': sockets.BoolSocketDef(
|
||||||
|
default_value=True,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
||||||
|
'E(t)': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
|
}
|
||||||
|
|
||||||
|
managed_obj_types: typ.ClassVar = {
|
||||||
|
'plot': managed_objs.ManagedBLImage,
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
|
box = layout.box()
|
||||||
|
row = box.row()
|
||||||
|
row.alignment = 'CENTER'
|
||||||
|
row.label(text='Parameter Scale')
|
||||||
|
|
||||||
|
# Split
|
||||||
|
split = box.split(factor=0.3, align=False)
|
||||||
|
|
||||||
|
## LHS: Parameter Names
|
||||||
|
col = split.column()
|
||||||
|
col.alignment = 'RIGHT'
|
||||||
|
col.label(text='Off t:')
|
||||||
|
|
||||||
|
## RHS: Parameter Units
|
||||||
|
col = split.column()
|
||||||
|
col.label(text='1 / 2π·σ(𝑓)')
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - FlowKind: Value
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'Temporal Shape',
|
||||||
|
input_sockets={
|
||||||
|
'max E',
|
||||||
|
'μ Freq',
|
||||||
|
'σ Freq',
|
||||||
|
'Offset Time',
|
||||||
|
'Remove DC',
|
||||||
|
},
|
||||||
|
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
||||||
|
scale_input_sockets={
|
||||||
|
'max E': 'Tidy3DUnits',
|
||||||
|
'μ Freq': 'Tidy3DUnits',
|
||||||
|
'σ Freq': 'Tidy3DUnits',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def compute_temporal_shape(self, input_sockets, unit_systems) -> td.GaussianPulse:
|
||||||
|
return td.GaussianPulse(
|
||||||
|
amplitude=sp.re(input_sockets['max E']),
|
||||||
|
phase=sp.im(input_sockets['max E']),
|
||||||
|
freq0=input_sockets['μ Freq'],
|
||||||
|
fwidth=input_sockets['σ Freq'],
|
||||||
|
offset=input_sockets['Offset Time'],
|
||||||
|
remove_dc_component=input_sockets['Remove DC'],
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - FlowKind: LazyValueFunc / Info / Params
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'E(t)',
|
||||||
|
kind=ct.FlowKind.LazyValueFunc,
|
||||||
|
output_sockets={'Temporal Shape'},
|
||||||
|
)
|
||||||
|
def compute_time_to_efield_lazy(self, output_sockets) -> td.GaussianPulse:
|
||||||
|
temporal_shape = output_sockets['Temporal Shape']
|
||||||
|
jax_amp_time = functools.partial(ct.manual_amp_time, temporal_shape)
|
||||||
|
|
||||||
|
## TODO: Don't just partial() it up, do it property in the ParamsFlow!
|
||||||
|
## -> Right now it's recompiled every time.
|
||||||
|
|
||||||
|
return ct.LazyValueFuncFlow(
|
||||||
|
func=jax_amp_time,
|
||||||
|
func_args=[spux.PhysicalType.Time],
|
||||||
|
supports_jax=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'E(t)',
|
||||||
|
kind=ct.FlowKind.Info,
|
||||||
|
)
|
||||||
|
def compute_time_to_efield_info(self) -> td.GaussianPulse:
|
||||||
|
return ct.InfoFlow(
|
||||||
|
dim_names=['t'],
|
||||||
|
dim_idx={
|
||||||
|
't': ct.LazyArrayRangeFlow(
|
||||||
|
start=sp.S(0), stop=sp.oo, steps=0, unit=spu.second
|
||||||
|
)
|
||||||
|
},
|
||||||
|
output_name='E',
|
||||||
|
output_shape=None,
|
||||||
|
output_mathtype=spux.MathType.Complex,
|
||||||
|
output_unit=spu.volt / spu.um,
|
||||||
|
)
|
||||||
|
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'E(t)',
|
||||||
|
kind=ct.FlowKind.Params,
|
||||||
|
)
|
||||||
|
def compute_time_to_efield_params(self) -> td.GaussianPulse:
|
||||||
|
sym_time = sp.Symbol('t', real=True, nonnegative=True)
|
||||||
|
return ct.ParamsFlow(func_args=[sym_time], symbols={sym_time})
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
PulseTemporalShapeNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
ct.NodeType.PulseTemporalShape: (ct.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES)
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
"""Implements the `WaveTemporalShapeNode`."""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
|
||||||
|
from .... import contracts as ct
|
||||||
|
from .... import managed_objs, sockets
|
||||||
|
from ... import base, events
|
||||||
|
|
||||||
|
|
||||||
|
class WaveTemporalShapeNode(base.MaxwellSimNode):
|
||||||
|
node_type = ct.NodeType.WaveTemporalShape
|
||||||
|
bl_label = 'Continuous Wave Temporal Shape'
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_sockets: typ.ClassVar = {
|
||||||
|
'max E': sockets.ExprSocketDef(
|
||||||
|
mathtype=spux.MathType.Complex,
|
||||||
|
physical_type=spux.PhysicalType.EField,
|
||||||
|
default_value=1 + 0j,
|
||||||
|
),
|
||||||
|
'μ Freq': sockets.ExprSocketDef(
|
||||||
|
physical_type=spux.PhysicalType.Freq,
|
||||||
|
default_unit=spux.THz,
|
||||||
|
default_value=500,
|
||||||
|
),
|
||||||
|
'σ Freq': sockets.ExprSocketDef(
|
||||||
|
physical_type=spux.PhysicalType.Freq,
|
||||||
|
default_unit=spux.THz,
|
||||||
|
default_value=200,
|
||||||
|
),
|
||||||
|
'Offset Time': sockets.ExprSocketDef(default_value=5, abs_min=2.5),
|
||||||
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
||||||
|
'E(t)': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
|
}
|
||||||
|
|
||||||
|
managed_obj_types: typ.ClassVar = {
|
||||||
|
'plot': managed_objs.ManagedBLImage,
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
|
box = layout.box()
|
||||||
|
row = box.row()
|
||||||
|
row.alignment = 'CENTER'
|
||||||
|
row.label(text='Parameter Scale')
|
||||||
|
|
||||||
|
# Split
|
||||||
|
split = box.split(factor=0.3, align=False)
|
||||||
|
|
||||||
|
## LHS: Parameter Names
|
||||||
|
col = split.column()
|
||||||
|
col.alignment = 'RIGHT'
|
||||||
|
col.label(text='Off t:')
|
||||||
|
|
||||||
|
## RHS: Parameter Units
|
||||||
|
col = split.column()
|
||||||
|
col.label(text='1 / 2π·σ(𝑓)')
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - FlowKind: Value
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'Temporal Shape',
|
||||||
|
input_sockets={
|
||||||
|
'max E',
|
||||||
|
'μ Freq',
|
||||||
|
'σ Freq',
|
||||||
|
'Offset Time',
|
||||||
|
},
|
||||||
|
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
||||||
|
scale_input_sockets={
|
||||||
|
'max E': 'Tidy3DUnits',
|
||||||
|
'μ Freq': 'Tidy3DUnits',
|
||||||
|
'σ Freq': 'Tidy3DUnits',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def compute_temporal_shape(self, input_sockets, unit_systems) -> td.GaussianPulse:
|
||||||
|
return td.ContinuousWave(
|
||||||
|
amplitude=sp.re(input_sockets['max E']),
|
||||||
|
phase=sp.im(input_sockets['max E']),
|
||||||
|
freq0=input_sockets['μ Freq'],
|
||||||
|
fwidth=input_sockets['σ Freq'],
|
||||||
|
offset=input_sockets['Offset Time'],
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - FlowKind: LazyValueFunc / Info / Params
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'E(t)',
|
||||||
|
kind=ct.FlowKind.LazyValueFunc,
|
||||||
|
output_sockets={'Temporal Shape'},
|
||||||
|
)
|
||||||
|
def compute_time_to_efield_lazy(self, output_sockets) -> td.GaussianPulse:
|
||||||
|
temporal_shape = output_sockets['Temporal Shape']
|
||||||
|
jax_amp_time = functools.partial(ct.manual_amp_time, temporal_shape)
|
||||||
|
|
||||||
|
## TODO: Don't just partial() it up, do it property in the ParamsFlow!
|
||||||
|
## -> Right now it's recompiled every time.
|
||||||
|
|
||||||
|
return ct.LazyValueFuncFlow(
|
||||||
|
func=jax_amp_time,
|
||||||
|
func_args=[spux.PhysicalType.Time],
|
||||||
|
supports_jax=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'E(t)',
|
||||||
|
kind=ct.FlowKind.Info,
|
||||||
|
)
|
||||||
|
def compute_time_to_efield_info(self) -> td.GaussianPulse:
|
||||||
|
return ct.InfoFlow(
|
||||||
|
dim_names=['t'],
|
||||||
|
dim_idx={
|
||||||
|
't': ct.LazyArrayRangeFlow(
|
||||||
|
start=sp.S(0), stop=sp.oo, steps=0, unit=spu.second
|
||||||
|
)
|
||||||
|
},
|
||||||
|
output_name='E',
|
||||||
|
output_shape=None,
|
||||||
|
output_mathtype=spux.MathType.Complex,
|
||||||
|
output_unit=spu.volt / spu.um,
|
||||||
|
)
|
||||||
|
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'E(t)',
|
||||||
|
kind=ct.FlowKind.Params,
|
||||||
|
)
|
||||||
|
def compute_time_to_efield_params(self) -> td.GaussianPulse:
|
||||||
|
sym_time = sp.Symbol('t', real=True, nonnegative=True)
|
||||||
|
return ct.ParamsFlow(func_args=[sym_time], symbols={sym_time})
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
WaveTemporalShapeNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
ct.NodeType.WaveTemporalShape: (ct.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES)
|
||||||
|
}
|
|
@ -1,8 +1,12 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
from blender_maxwell.utils import bl_cache, logger
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from ... import bl_socket_map, managed_objs, sockets
|
from ... import bl_socket_map, managed_objs, sockets
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
|
@ -22,7 +26,13 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'GeoNodes': sockets.BlenderGeoNodesSocketDef(),
|
'GeoNodes': sockets.BlenderGeoNodesSocketDef(),
|
||||||
'Medium': sockets.MaxwellMediumSocketDef(),
|
'Medium': sockets.MaxwellMediumSocketDef(),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.ExprSocketDef(
|
||||||
|
shape=(3,),
|
||||||
|
mathtype=spux.MathType.Real,
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_unit=spu.micrometer,
|
||||||
|
default_value=sp.Matrix([0, 0, 0]),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Structure': sockets.MaxwellStructureSocketDef(),
|
'Structure': sockets.MaxwellStructureSocketDef(),
|
||||||
|
@ -34,7 +44,7 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output
|
# - Outputs
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Structure',
|
'Structure',
|
||||||
|
@ -43,14 +53,10 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_structure(
|
def compute_structure(
|
||||||
self,
|
self,
|
||||||
input_sockets: dict,
|
input_sockets,
|
||||||
managed_objs: dict,
|
managed_objs,
|
||||||
) -> td.Structure:
|
) -> td.Structure:
|
||||||
"""Computes a triangle-mesh based Tidy3D structure, by manually copying mesh data from Blender to a `td.TriangleMesh`."""
|
"""Computes a triangle-mesh based Tidy3D structure, by manually copying mesh data from Blender to a `td.TriangleMesh`."""
|
||||||
# Simulate Input Value Change
|
|
||||||
## This ensures that the mesh has been re-computed.
|
|
||||||
self.on_input_socket_changed()
|
|
||||||
|
|
||||||
## TODO: mesh_as_arrays might not take the Center into account.
|
## TODO: mesh_as_arrays might not take the Center into account.
|
||||||
## - Alternatively, Tidy3D might have a way to transform?
|
## - Alternatively, Tidy3D might have a way to transform?
|
||||||
mesh_as_arrays = managed_objs['mesh'].mesh_as_arrays
|
mesh_as_arrays = managed_objs['mesh'].mesh_as_arrays
|
||||||
|
@ -68,20 +74,12 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
input_sockets={'Center'},
|
|
||||||
managed_objs={'mesh'},
|
managed_objs={'mesh'},
|
||||||
)
|
)
|
||||||
def on_preview_changed(self, props, input_sockets) -> None:
|
def on_preview_changed(self, props) -> None:
|
||||||
"""Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen."""
|
"""Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen."""
|
||||||
mesh = managed_objs['mesh']
|
mesh = managed_objs['mesh']
|
||||||
|
|
||||||
# No Mesh: Create Empty Object
|
|
||||||
## Ensures that when there is mesh data, it'll be correctly previewed.
|
|
||||||
## Bit of a workaround - the idea is usually to make the MObj as needed.
|
|
||||||
if not mesh.exists:
|
|
||||||
center = input_sockets['Center']
|
|
||||||
_ = mesh.bl_object(location=center)
|
|
||||||
|
|
||||||
# Push Preview State to Managed Mesh
|
# Push Preview State to Managed Mesh
|
||||||
if props['preview_active']:
|
if props['preview_active']:
|
||||||
mesh.show_preview()
|
mesh.show_preview()
|
||||||
|
@ -139,8 +137,8 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def on_input_changed(
|
def on_input_changed(
|
||||||
self,
|
self,
|
||||||
managed_objs: dict,
|
managed_objs,
|
||||||
input_sockets: dict,
|
input_sockets,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Declares new loose input sockets in response to a new GeoNodes tree (if any)."""
|
"""Declares new loose input sockets in response to a new GeoNodes tree (if any)."""
|
||||||
geonodes = input_sockets['GeoNodes']
|
geonodes = input_sockets['GeoNodes']
|
||||||
|
|
|
@ -5,11 +5,15 @@ import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import managed_objs, sockets
|
from .... import managed_objs, sockets
|
||||||
from ... import base, events
|
from ... import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BoxStructureNode(base.MaxwellSimNode):
|
class BoxStructureNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.BoxStructure
|
node_type = ct.NodeType.BoxStructure
|
||||||
|
@ -21,9 +25,20 @@ class BoxStructureNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Medium': sockets.MaxwellMediumSocketDef(),
|
'Medium': sockets.MaxwellMediumSocketDef(),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.ExprSocketDef(
|
||||||
'Size': sockets.PhysicalSize3DSocketDef(
|
shape=(3,),
|
||||||
default_value=sp.Matrix([500, 500, 500]) * spu.nm
|
mathtype=spux.MathType.Real,
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_unit=spu.micrometer,
|
||||||
|
default_value=sp.Matrix([0, 0, 0]),
|
||||||
|
),
|
||||||
|
'Size': sockets.ExprSocketDef(
|
||||||
|
shape=(3,),
|
||||||
|
mathtype=spux.MathType.Real,
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_unit=spu.nanometer,
|
||||||
|
default_value=sp.Matrix([500, 500, 500]),
|
||||||
|
abs_min=0.001,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
|
@ -36,7 +51,7 @@ class BoxStructureNode(base.MaxwellSimNode):
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods
|
# - Outputs
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Structure',
|
'Structure',
|
||||||
|
@ -47,7 +62,7 @@ class BoxStructureNode(base.MaxwellSimNode):
|
||||||
'Size': 'Tidy3DUnits',
|
'Size': 'Tidy3DUnits',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def compute_structure(self, input_sockets: dict, unit_systems: dict) -> td.Box:
|
def compute_structure(self, input_sockets, unit_systems) -> td.Box:
|
||||||
return td.Structure(
|
return td.Structure(
|
||||||
geometry=td.Box(
|
geometry=td.Box(
|
||||||
center=input_sockets['Center'],
|
center=input_sockets['Center'],
|
||||||
|
@ -56,11 +71,27 @@ class BoxStructureNode(base.MaxwellSimNode):
|
||||||
medium=input_sockets['Medium'],
|
medium=input_sockets['Medium'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Preview
|
||||||
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
socket_name={'Center', 'Size'},
|
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
|
managed_objs={'mesh'},
|
||||||
|
)
|
||||||
|
def on_preview_changed(self, props, managed_objs) -> None:
|
||||||
|
mesh = managed_objs['mesh']
|
||||||
|
|
||||||
|
# Push Preview State to Managed Mesh
|
||||||
|
if props['preview_active']:
|
||||||
|
mesh.show_preview()
|
||||||
|
else:
|
||||||
|
mesh.hide_preview()
|
||||||
|
|
||||||
|
@events.on_value_changed(
|
||||||
|
socket_name={'Center', 'Size'},
|
||||||
|
run_on_init=True,
|
||||||
input_sockets={'Center', 'Size'},
|
input_sockets={'Center', 'Size'},
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'mesh', 'modifier'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
|
@ -70,26 +101,26 @@ class BoxStructureNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_inputs_changed(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
managed_objs,
|
||||||
managed_objs: dict,
|
input_sockets,
|
||||||
input_sockets: dict,
|
unit_systems,
|
||||||
unit_systems: dict,
|
|
||||||
):
|
):
|
||||||
# Push Input Values to GeoNodes Modifier
|
mesh = managed_objs['mesh']
|
||||||
managed_objs['modifier'].bl_modifier(
|
modifier = managed_objs['modifier']
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
center = input_sockets['Center']
|
||||||
|
size = input_sockets['Size']
|
||||||
|
unit_system = unit_systems['BlenderUnits']
|
||||||
|
|
||||||
|
# Push Loose Input Values to GeoNodes Modifier
|
||||||
|
modifier.bl_modifier(
|
||||||
|
mesh.bl_object(location=center),
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.StructurePrimitiveBox),
|
'node_group': import_geonodes(GeoNodes.StructurePrimitiveBox),
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
'inputs': {'Size': size},
|
||||||
'inputs': {
|
'unit_system': unit_system,
|
||||||
'Size': input_sockets['Size'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import managed_objs, sockets
|
from .... import managed_objs, sockets
|
||||||
from ... import base, events
|
from ... import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SphereStructureNode(base.MaxwellSimNode):
|
class SphereStructureNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.SphereStructure
|
node_type = ct.NodeType.SphereStructure
|
||||||
|
@ -20,9 +25,17 @@ class SphereStructureNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Medium': sockets.MaxwellMediumSocketDef(),
|
'Medium': sockets.MaxwellMediumSocketDef(),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.ExprSocketDef(
|
||||||
'Radius': sockets.PhysicalLengthSocketDef(
|
shape=(3,),
|
||||||
default_value=150 * spu.nm,
|
mathtype=spux.MathType.Real,
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_unit=spu.micrometer,
|
||||||
|
default_value=sp.Matrix([0, 0, 0]),
|
||||||
|
),
|
||||||
|
'Radius': sockets.ExprSocketDef(
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_unit=spu.nanometer,
|
||||||
|
default_value=150,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
|
@ -35,7 +48,7 @@ class SphereStructureNode(base.MaxwellSimNode):
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Outputs
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Structure',
|
'Structure',
|
||||||
|
@ -46,7 +59,7 @@ class SphereStructureNode(base.MaxwellSimNode):
|
||||||
'Radius': 'Tidy3DUnits',
|
'Radius': 'Tidy3DUnits',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def compute_structure(self, input_sockets: dict) -> td.Box:
|
def compute_structure(self, input_sockets, unit_systems) -> td.Box:
|
||||||
return td.Structure(
|
return td.Structure(
|
||||||
geometry=td.Sphere(
|
geometry=td.Sphere(
|
||||||
radius=input_sockets['Radius'],
|
radius=input_sockets['Radius'],
|
||||||
|
@ -56,43 +69,55 @@ class SphereStructureNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Preview - Changes to Input Sockets
|
# - Preview
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
socket_name={'Center', 'Radius'},
|
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
|
managed_objs={'mesh'},
|
||||||
|
)
|
||||||
|
def on_preview_changed(self, props, managed_objs) -> None:
|
||||||
|
mesh = managed_objs['mesh']
|
||||||
|
|
||||||
|
# Push Preview State to Managed Mesh
|
||||||
|
if props['preview_active']:
|
||||||
|
mesh.show_preview()
|
||||||
|
else:
|
||||||
|
mesh.hide_preview()
|
||||||
|
|
||||||
|
@events.on_value_changed(
|
||||||
|
socket_name={'Center', 'Radius'},
|
||||||
|
run_on_init=True,
|
||||||
input_sockets={'Center', 'Radius'},
|
input_sockets={'Center', 'Radius'},
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'mesh', 'modifier'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
scale_input_sockets={
|
scale_input_sockets={
|
||||||
'Center': 'Tidy3DUnits',
|
'Center': 'BlenderUnits',
|
||||||
'Radius': 'Tidy3DUnits',
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_inputs_changed(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
managed_objs,
|
||||||
managed_objs: dict,
|
input_sockets,
|
||||||
input_sockets: dict,
|
unit_systems,
|
||||||
unit_systems: dict,
|
|
||||||
):
|
):
|
||||||
# Push Input Values to GeoNodes Modifier
|
mesh = managed_objs['mesh']
|
||||||
managed_objs['modifier'].bl_modifier(
|
modifier = managed_objs['modifier']
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
center = input_sockets['Center']
|
||||||
|
radius = input_sockets['Radius']
|
||||||
|
unit_system = unit_systems['BlenderUnits']
|
||||||
|
|
||||||
|
# Push Loose Input Values to GeoNodes Modifier
|
||||||
|
modifier.bl_modifier(
|
||||||
|
mesh.bl_object(location=center),
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.StructurePrimitiveSphere),
|
'node_group': import_geonodes(GeoNodes.StructurePrimitiveSphere),
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
'inputs': {'Radius': radius},
|
||||||
'inputs': {
|
'unit_system': unit_system,
|
||||||
'Radius': input_sockets['Radius'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = []
|
|
||||||
BL_NODES = {}
|
|
|
@ -1,80 +0,0 @@
|
||||||
import scipy as sc
|
|
||||||
import sympy as sp
|
|
||||||
import sympy.physics.units as spu
|
|
||||||
|
|
||||||
from .... import contracts, sockets
|
|
||||||
from ... import base, events
|
|
||||||
|
|
||||||
|
|
||||||
class WaveConverterNode(base.MaxwellSimTreeNode):
|
|
||||||
node_type = contracts.NodeType.WaveConverter
|
|
||||||
bl_label = 'Wave Converter'
|
|
||||||
# bl_icon = ...
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Sockets
|
|
||||||
####################
|
|
||||||
input_sockets = {}
|
|
||||||
input_socket_sets = {
|
|
||||||
'freq_to_vacwl': {
|
|
||||||
'freq': sockets.PhysicalFreqSocketDef(
|
|
||||||
label='Freq',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'vacwl_to_freq': {
|
|
||||||
'vacwl': sockets.PhysicalVacWLSocketDef(
|
|
||||||
label='Vac WL',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
output_sockets = {}
|
|
||||||
output_socket_sets = {
|
|
||||||
'freq_to_vacwl': {
|
|
||||||
'vacwl': sockets.PhysicalVacWLSocketDef(
|
|
||||||
label='Vac WL',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'vacwl_to_freq': {
|
|
||||||
'freq': sockets.PhysicalFreqSocketDef(
|
|
||||||
label='Freq',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Output Socket Computation
|
|
||||||
####################
|
|
||||||
@events.computes_output_socket('freq')
|
|
||||||
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
|
||||||
vac_speed_of_light = sc.constants.speed_of_light * spu.meter / spu.second
|
|
||||||
|
|
||||||
vacwl = self.compute_input('vacwl')
|
|
||||||
|
|
||||||
return spu.convert_to(
|
|
||||||
vac_speed_of_light / vacwl,
|
|
||||||
spu.hertz,
|
|
||||||
)
|
|
||||||
|
|
||||||
@events.computes_output_socket('vacwl')
|
|
||||||
def compute_vacwl(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
|
||||||
vac_speed_of_light = sc.constants.speed_of_light * spu.meter / spu.second
|
|
||||||
|
|
||||||
freq = self.compute_input('freq')
|
|
||||||
|
|
||||||
return spu.convert_to(
|
|
||||||
vac_speed_of_light / freq,
|
|
||||||
spu.meter,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
WaveConverterNode,
|
|
||||||
]
|
|
||||||
BL_NODES = {
|
|
||||||
contracts.NodeType.WaveConverter: (
|
|
||||||
contracts.NodeCategory.MAXWELLSIM_UTILITIES_CONVERTERS
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -38,6 +38,14 @@ class SocketDef(pyd.BaseModel, abc.ABC):
|
||||||
"""
|
"""
|
||||||
bl_socket.reset_instance_id()
|
bl_socket.reset_instance_id()
|
||||||
|
|
||||||
|
def postinit(self, bl_socket: bpy.types.NodeSocket) -> None:
|
||||||
|
"""Pre-initialize a real Blender node socket from this socket definition.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
bl_socket: The Blender node socket to alter using data from this SocketDef.
|
||||||
|
"""
|
||||||
|
bl_socket.initializing = False
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
|
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
|
||||||
"""Initializes a real Blender node socket from this socket definition.
|
"""Initializes a real Blender node socket from this socket definition.
|
||||||
|
@ -195,6 +203,9 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
## Identifiers
|
## Identifiers
|
||||||
cls.bl_idname: str = str(cls.socket_type.value)
|
cls.bl_idname: str = str(cls.socket_type.value)
|
||||||
cls.set_prop('instance_id', bpy.props.StringProperty, no_update=True)
|
cls.set_prop('instance_id', bpy.props.StringProperty, no_update=True)
|
||||||
|
cls.set_prop(
|
||||||
|
'initializing', bpy.props.BoolProperty, default=True, no_update=True
|
||||||
|
)
|
||||||
|
|
||||||
## Special States
|
## Special States
|
||||||
cls.set_prop('locked', bpy.props.BoolProperty, no_update=True, default=False)
|
cls.set_prop('locked', bpy.props.BoolProperty, no_update=True, default=False)
|
||||||
|
@ -217,9 +228,14 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
Called by `self.on_prop_changed()` when `self.active_kind` was changed.
|
Called by `self.on_prop_changed()` when `self.active_kind` was changed.
|
||||||
"""
|
"""
|
||||||
self.display_shape = (
|
self.display_shape = (
|
||||||
'SQUARE' if self.active_kind == ct.FlowKind.LazyArrayRange else 'CIRCLE'
|
'SQUARE'
|
||||||
) # + ('_DOT' if self.use_units else '')
|
if self.active_kind == ct.FlowKind.LazyArrayRange
|
||||||
## TODO: Valid Active Kinds should be a subset/subenum(?) of FlowKind
|
else ('DIAMOND' if self.active_kind == ct.FlowKind.Array else 'CIRCLE')
|
||||||
|
) + (
|
||||||
|
'_DOT'
|
||||||
|
if hasattr(self, 'physical_type') and self.physical_type is not None
|
||||||
|
else ''
|
||||||
|
)
|
||||||
|
|
||||||
def on_socket_prop_changed(self, prop_name: str) -> None:
|
def on_socket_prop_changed(self, prop_name: str) -> None:
|
||||||
"""Called when a property has been updated.
|
"""Called when a property has been updated.
|
||||||
|
@ -244,6 +260,10 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
Attributes:
|
Attributes:
|
||||||
prop_name: The name of the property that was changed.
|
prop_name: The name of the property that was changed.
|
||||||
"""
|
"""
|
||||||
|
## TODO: Evaluate this properly
|
||||||
|
if self.initializing:
|
||||||
|
return
|
||||||
|
|
||||||
if hasattr(self, prop_name):
|
if hasattr(self, prop_name):
|
||||||
# Invalidate UI BLField Caches
|
# Invalidate UI BLField Caches
|
||||||
if prop_name in self.ui_blfields:
|
if prop_name in self.ui_blfields:
|
||||||
|
@ -532,7 +552,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
msg = f'Socket {self.bl_label} {self.socket_type}): Tried to set "ct.FlowKind.Value", but socket does not define it'
|
msg = f'Socket {self.bl_label} {self.socket_type}): Tried to set "ct.FlowKind.Value", but socket does not define it'
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
# ValueArray
|
# Array
|
||||||
@property
|
@property
|
||||||
def array(self) -> ct.ArrayFlow:
|
def array(self) -> ct.ArrayFlow:
|
||||||
"""Throws a descriptive error.
|
"""Throws a descriptive error.
|
||||||
|
@ -811,6 +831,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
{
|
{
|
||||||
ct.FlowKind.Value: self.draw_value,
|
ct.FlowKind.Value: self.draw_value,
|
||||||
ct.FlowKind.LazyArrayRange: self.draw_lazy_array_range,
|
ct.FlowKind.LazyArrayRange: self.draw_lazy_array_range,
|
||||||
|
ct.FlowKind.Array: self.draw_array,
|
||||||
}[self.active_kind](col)
|
}[self.active_kind](col)
|
||||||
|
|
||||||
# Info Drawing
|
# Info Drawing
|
||||||
|
@ -930,6 +951,16 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
col: Target for defining UI elements.
|
col: Target for defining UI elements.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def draw_array(self, col: bpy.types.UILayout) -> None:
|
||||||
|
"""Draws the socket array UI on its own line.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Should be overriden by individual socket classes, if they have an editable `FlowKind.Array`.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
col: Target for defining UI elements.
|
||||||
|
"""
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI Methods: Auxilliary
|
# - UI Methods: Auxilliary
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -194,10 +194,19 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
current_value = self.value
|
current_value = self.value
|
||||||
current_lazy_array_range = self.lazy_array_range
|
current_lazy_array_range = self.lazy_array_range
|
||||||
|
|
||||||
|
# Old Unit Not in Physical Type
|
||||||
|
## -> This happens when dynamically altering self.physical_type
|
||||||
|
if self.unit in self.physical_type.valid_units:
|
||||||
self.unit = bl_cache.Signal.InvalidateCache
|
self.unit = bl_cache.Signal.InvalidateCache
|
||||||
|
|
||||||
self.value = current_value
|
self.value = current_value
|
||||||
self.lazy_array_range = current_lazy_array_range
|
self.lazy_array_range = current_lazy_array_range
|
||||||
|
else:
|
||||||
|
self.unit = bl_cache.Signal.InvalidateCache
|
||||||
|
|
||||||
|
# Workaround: Manually Jiggle FlowKind Invalidation
|
||||||
|
self.value = self.value
|
||||||
|
self.lazy_value_range = self.lazy_value_range
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Property Callback
|
# - Property Callback
|
||||||
|
@ -238,10 +247,25 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
|
|
||||||
return mathtype, shape
|
return mathtype, shape
|
||||||
|
|
||||||
def _to_raw_value(self, expr: spux.SympyExpr):
|
def _to_raw_value(self, expr: spux.SympyExpr, force_complex: bool = False):
|
||||||
if self.unit is not None:
|
if self.unit is not None:
|
||||||
return spux.sympy_to_python(spux.scale_to_unit(expr, self.unit))
|
pyvalue = spux.sympy_to_python(spux.scale_to_unit(expr, self.unit))
|
||||||
return spux.sympy_to_python(expr)
|
else:
|
||||||
|
pyvalue = spux.sympy_to_python(expr)
|
||||||
|
|
||||||
|
# Cast complex -> tuple[float, float]
|
||||||
|
if isinstance(pyvalue, complex) or (
|
||||||
|
isinstance(pyvalue, int | float) and force_complex
|
||||||
|
):
|
||||||
|
return (pyvalue.real, pyvalue.imag)
|
||||||
|
if isinstance(pyvalue, tuple) and all(
|
||||||
|
isinstance(v, complex)
|
||||||
|
or (isinstance(pyvalue, int | float) and force_complex)
|
||||||
|
for v in pyvalue
|
||||||
|
):
|
||||||
|
return tuple([(v.real, v.imag) for v in pyvalue])
|
||||||
|
|
||||||
|
return pyvalue
|
||||||
|
|
||||||
def _parse_expr_str(self, expr_spstr: str) -> None:
|
def _parse_expr_str(self, expr_spstr: str) -> None:
|
||||||
expr = sp.sympify(
|
expr = sp.sympify(
|
||||||
|
@ -346,7 +370,9 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
elif self.mathtype == MT_R:
|
elif self.mathtype == MT_R:
|
||||||
self.raw_value_float = self._to_raw_value(expr)
|
self.raw_value_float = self._to_raw_value(expr)
|
||||||
elif self.mathtype == MT_C:
|
elif self.mathtype == MT_C:
|
||||||
self.raw_value_complex = self._to_raw_value(expr)
|
self.raw_value_complex = self._to_raw_value(
|
||||||
|
expr, force_complex=True
|
||||||
|
)
|
||||||
elif self.shape == (2,):
|
elif self.shape == (2,):
|
||||||
if self.mathtype == MT_Z:
|
if self.mathtype == MT_Z:
|
||||||
self.raw_value_int2 = self._to_raw_value(expr)
|
self.raw_value_int2 = self._to_raw_value(expr)
|
||||||
|
@ -355,9 +381,10 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
elif self.mathtype == MT_R:
|
elif self.mathtype == MT_R:
|
||||||
self.raw_value_float2 = self._to_raw_value(expr)
|
self.raw_value_float2 = self._to_raw_value(expr)
|
||||||
elif self.mathtype == MT_C:
|
elif self.mathtype == MT_C:
|
||||||
self.raw_value_complex2 = self._to_raw_value(expr)
|
self.raw_value_complex2 = self._to_raw_value(
|
||||||
|
expr, force_complex=True
|
||||||
|
)
|
||||||
elif self.shape == (3,):
|
elif self.shape == (3,):
|
||||||
log.critical(expr)
|
|
||||||
if self.mathtype == MT_Z:
|
if self.mathtype == MT_Z:
|
||||||
self.raw_value_int3 = self._to_raw_value(expr)
|
self.raw_value_int3 = self._to_raw_value(expr)
|
||||||
elif self.mathtype == MT_Q:
|
elif self.mathtype == MT_Q:
|
||||||
|
@ -365,7 +392,9 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
elif self.mathtype == MT_R:
|
elif self.mathtype == MT_R:
|
||||||
self.raw_value_float3 = self._to_raw_value(expr)
|
self.raw_value_float3 = self._to_raw_value(expr)
|
||||||
elif self.mathtype == MT_C:
|
elif self.mathtype == MT_C:
|
||||||
self.raw_value_complex3 = self._to_raw_value(expr)
|
self.raw_value_complex3 = self._to_raw_value(
|
||||||
|
expr, force_complex=True
|
||||||
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - FlowKind: LazyArrayRange
|
# - FlowKind: LazyArrayRange
|
||||||
|
@ -451,7 +480,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
]
|
]
|
||||||
elif value.mathtype == MT_C:
|
elif value.mathtype == MT_C:
|
||||||
self.raw_range_complex = [
|
self.raw_range_complex = [
|
||||||
self._to_raw_value(bound * unit)
|
self._to_raw_value(bound * unit, force_complex=True)
|
||||||
for bound in [value.start, value.stop]
|
for bound in [value.start, value.stop]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -674,7 +703,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
_row.label(text=text)
|
_row.label(text=text)
|
||||||
|
|
||||||
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
|
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
|
||||||
if self.show_info_columns:
|
if self.active_kind == ct.FlowKind.Array and self.show_info_columns:
|
||||||
row = col.row()
|
row = col.row()
|
||||||
box = row.box()
|
box = row.box()
|
||||||
grid = box.grid_flow(
|
grid = box.grid_flow(
|
||||||
|
@ -725,9 +754,9 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
class ExprSocketDef(base.SocketDef):
|
class ExprSocketDef(base.SocketDef):
|
||||||
socket_type: ct.SocketType = ct.SocketType.Expr
|
socket_type: ct.SocketType = ct.SocketType.Expr
|
||||||
active_kind: typ.Literal[ct.FlowKind.Value, ct.FlowKind.LazyArrayRange] = (
|
active_kind: typ.Literal[
|
||||||
ct.FlowKind.Value
|
ct.FlowKind.Value, ct.FlowKind.LazyArrayRange, ct.FlowKind.Array
|
||||||
)
|
] = ct.FlowKind.Value
|
||||||
|
|
||||||
# Socket Interface
|
# Socket Interface
|
||||||
## TODO: __hash__ like socket method based on these?
|
## TODO: __hash__ like socket method based on these?
|
||||||
|
@ -740,15 +769,15 @@ class ExprSocketDef(base.SocketDef):
|
||||||
default_unit: spux.Unit | None = None
|
default_unit: spux.Unit | None = None
|
||||||
|
|
||||||
# FlowKind: Value
|
# FlowKind: Value
|
||||||
default_value: spux.SympyExpr = sp.RealNumber(0)
|
default_value: spux.SympyExpr = sp.S(0)
|
||||||
abs_min: spux.SympyExpr | None = None ## TODO: Not used (yet)
|
abs_min: spux.SympyExpr | None = None ## TODO: Not used (yet)
|
||||||
abs_max: 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: Idea is to use this scalar uniformly for all shape elements
|
||||||
## TODO: -> But we may want to **allow** using same-shape for diff. bounds.
|
## 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.S(0)
|
||||||
default_max: spux.SympyExpr = sp.RealNumber(1)
|
default_max: spux.SympyExpr = sp.S(1)
|
||||||
default_steps: int = 2
|
default_steps: int = 2
|
||||||
## TODO: Configure lin/log/... scaling (w/enumprop in UI)
|
## TODO: Configure lin/log/... scaling (w/enumprop in UI)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from .. import base
|
from .. import base
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from .. import base
|
from .. import base
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@ class MaxwellStructureSocketDef(base.SocketDef):
|
||||||
|
|
||||||
def init(self, bl_socket: MaxwellStructureBLSocket) -> None:
|
def init(self, bl_socket: MaxwellStructureBLSocket) -> None:
|
||||||
if self.is_list:
|
if self.is_list:
|
||||||
bl_socket.active_kind = ct.FlowKind.ValueArray
|
bl_socket.active_kind = ct.FlowKind.Array
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -98,6 +98,12 @@ class MathType(enum.StrEnum):
|
||||||
if sp_obj.is_complex:
|
if sp_obj.is_complex:
|
||||||
return MathType.Complex
|
return MathType.Complex
|
||||||
|
|
||||||
|
# Infinities
|
||||||
|
if sp_obj in [sp.oo, -sp.oo]:
|
||||||
|
return MathType.Real ## TODO: Strictly, could be ex. integer...
|
||||||
|
if sp_obj in [sp.zoo, -sp.zoo]:
|
||||||
|
return MathType.Complex
|
||||||
|
|
||||||
msg = f"Can't determine MathType from sympy object: {sp_obj}"
|
msg = f"Can't determine MathType from sympy object: {sp_obj}"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
@ -755,13 +761,11 @@ def scaling_factor(unit_from: spu.Quantity, unit_to: spu.Quantity) -> Number:
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
||||||
_UNIT_STR_MAP = {sym.name: unit for sym, unit in UNIT_BY_SYMBOL.items()}
|
|
||||||
|
|
||||||
|
|
||||||
@functools.cache
|
@functools.cache
|
||||||
def unit_str_to_unit(unit_str: str) -> Unit | None:
|
def unit_str_to_unit(unit_str: str) -> Unit | None:
|
||||||
if unit_str in _UNIT_STR_MAP:
|
expr = sp.sympify(unit_str).subs(UNIT_BY_SYMBOL)
|
||||||
return _UNIT_STR_MAP[unit_str]
|
if expr.has(spu.Quantity):
|
||||||
|
return expr
|
||||||
|
|
||||||
msg = f'No valid unit for unit string {unit_str}'
|
msg = f'No valid unit for unit string {unit_str}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
@ -812,7 +816,6 @@ class PhysicalType(enum.StrEnum):
|
||||||
# Luminal
|
# Luminal
|
||||||
LumIntensity = enum.auto()
|
LumIntensity = enum.auto()
|
||||||
LumFlux = enum.auto()
|
LumFlux = enum.auto()
|
||||||
Luminance = enum.auto()
|
|
||||||
Illuminance = enum.auto()
|
Illuminance = enum.auto()
|
||||||
# Optics
|
# Optics
|
||||||
OrdinaryWaveVector = enum.auto()
|
OrdinaryWaveVector = enum.auto()
|
||||||
|
@ -866,7 +869,7 @@ class PhysicalType(enum.StrEnum):
|
||||||
PT.OrdinaryWaveVector: Dims.frequency,
|
PT.OrdinaryWaveVector: Dims.frequency,
|
||||||
PT.AngularWaveVector: Dims.angle * Dims.frequency,
|
PT.AngularWaveVector: Dims.angle * Dims.frequency,
|
||||||
PT.PoyntingVector: Dims.power / Dims.length**2,
|
PT.PoyntingVector: Dims.power / Dims.length**2,
|
||||||
}
|
}[self]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_unit(self) -> list[Unit]:
|
def default_unit(self) -> list[Unit]:
|
||||||
|
@ -1072,7 +1075,7 @@ class PhysicalType(enum.StrEnum):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_unit(unit: Unit) -> list[Unit]:
|
def from_unit(unit: Unit) -> list[Unit]:
|
||||||
for physical_type in list[PhysicalType]:
|
for physical_type in list(PhysicalType):
|
||||||
if unit in physical_type.valid_units:
|
if unit in physical_type.valid_units:
|
||||||
return physical_type
|
return physical_type
|
||||||
|
|
||||||
|
@ -1161,7 +1164,7 @@ class PhysicalType(enum.StrEnum):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_name(value: typ.Self) -> str:
|
def to_name(value: typ.Self) -> str:
|
||||||
return sp_to_str(value.unit_dim)
|
return PhysicalType(value).name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_icon(value: typ.Self) -> str:
|
def to_icon(value: typ.Self) -> str:
|
||||||
|
@ -1208,6 +1211,7 @@ UNITS_SI: UnitSystem = {
|
||||||
# Electrodynamics
|
# Electrodynamics
|
||||||
_PT.Current: spu.ampere,
|
_PT.Current: spu.ampere,
|
||||||
_PT.CurrentDensity: spu.ampere / spu.meter**2,
|
_PT.CurrentDensity: spu.ampere / spu.meter**2,
|
||||||
|
_PT.Voltage: spu.volt,
|
||||||
_PT.Capacitance: spu.farad,
|
_PT.Capacitance: spu.farad,
|
||||||
_PT.Impedance: spu.ohm,
|
_PT.Impedance: spu.ohm,
|
||||||
_PT.Conductance: spu.siemens,
|
_PT.Conductance: spu.siemens,
|
||||||
|
@ -1278,13 +1282,12 @@ def sympy_to_python(
|
||||||
####################
|
####################
|
||||||
# - Convert to Unit System
|
# - Convert to Unit System
|
||||||
####################
|
####################
|
||||||
def _flat_unit_system_units(unit_system: UnitSystem) -> SympyExpr:
|
|
||||||
return list(unit_system.values())
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_unit_system(sp_obj: SympyExpr, unit_system: UnitSystem) -> SympyExpr:
|
def convert_to_unit_system(sp_obj: SympyExpr, unit_system: UnitSystem) -> SympyExpr:
|
||||||
"""Convert an expression to the units of a given unit system, with appropriate scaling."""
|
"""Convert an expression to the units of a given unit system, with appropriate scaling."""
|
||||||
return spu.convert_to(sp_obj, _flat_unit_system_units(unit_system))
|
return spu.convert_to(
|
||||||
|
sp_obj,
|
||||||
|
{unit_system[PhysicalType.from_unit(unit)] for unit in get_units(sp_obj)},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def strip_unit_system(sp_obj: SympyExpr, unit_system: UnitSystem) -> SympyExpr:
|
def strip_unit_system(sp_obj: SympyExpr, unit_system: UnitSystem) -> SympyExpr:
|
||||||
|
|
Loading…
Reference in New Issue