feat: Feature-parity with past.
We've added enough nodes to run simulations, and there is now only an "air gap" remaining between the math nodes and the sim design nodes. Of particular importance right now are: - Finalizing the exporter w/Sim Data output. - Finishing the plane wave, as well as other key source nodes (TFSF, Gaussian Beam, Plane Wave stand out in particular). - Reduce node, as we need to be able to reconstruct total flux through a field monitor, as well as generally do statistics. - Transform node, particularly w/pure-InfoFlow reindexing (we need to be able to reindex frequency -> vacwl), fourier transform (we need to be able to cast a time-domain field recording to frequency domain). - Finish debugging the basic structures, in particular the Cylinder primitive, but also the generic GeoNodes node, so we can build the membrane properly. - Ah yes, also, the grid definition. To satisfy the naysayers!
parent
9df0d20c68
commit
22daa6c603
7
TODO.md
7
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
|
||||||
|
@ -149,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
|
||||||
|
- [ ] Merge Gaussian Pulse and Continuous Wave w/a socket set thing, since the I/O is effectively identical.
|
||||||
- [ ] Temporal Shapes / Expr Temporal Shape
|
- [ ] Temporal Shapes / Expr Temporal Shape
|
||||||
- [ ] Specify a Sympy function / data to generate appropriate array based on
|
- [ ] Specify a Sympy function / data to generate envelope data.
|
||||||
- [ ] Temporal Shapes / Data Temporal Shape
|
- [ ] 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).
|
||||||
|
|
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)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -777,7 +780,7 @@ class ParamsFlow:
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
spux.convert_to_unit_system(arg, unit_system, use_jax_array=True)
|
spux.scale_to_unit_system(arg, unit_system, use_jax_array=True)
|
||||||
if arg not in symbol_values
|
if arg not in symbol_values
|
||||||
else symbol_values[arg]
|
else symbol_values[arg]
|
||||||
for arg in self.func_args
|
for arg in self.func_args
|
||||||
|
|
|
@ -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,10 +5,9 @@ from . import (
|
||||||
mediums,
|
mediums,
|
||||||
monitors,
|
monitors,
|
||||||
outputs,
|
outputs,
|
||||||
# simulations,
|
simulations,
|
||||||
sources,
|
sources,
|
||||||
# structures,
|
structures,
|
||||||
# utilities,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
|
@ -17,11 +16,10 @@ 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,
|
||||||
|
@ -29,9 +27,8 @@ 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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -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']
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -2,7 +2,7 @@ from . import (
|
||||||
# astigmatic_gaussian_beam_source,
|
# astigmatic_gaussian_beam_source,
|
||||||
# gaussian_beam_source,
|
# gaussian_beam_source,
|
||||||
# plane_wave_source,
|
# plane_wave_source,
|
||||||
# point_dipole_source,
|
point_dipole_source,
|
||||||
temporal_shapes,
|
temporal_shapes,
|
||||||
# tfsf_source,
|
# tfsf_source,
|
||||||
# uniform_current_source,
|
# uniform_current_source,
|
||||||
|
@ -10,18 +10,18 @@ from . import (
|
||||||
|
|
||||||
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,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
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,13 +1,13 @@
|
||||||
# from . import expr_temporal_shape, pulse_temporal_shape, wave_temporal_shape
|
# from . import expr_temporal_shape, pulse_temporal_shape, wave_temporal_shape
|
||||||
from . import pulse_temporal_shape
|
from . import pulse_temporal_shape, wave_temporal_shape
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*pulse_temporal_shape.BL_REGISTER,
|
*pulse_temporal_shape.BL_REGISTER,
|
||||||
# *wave_temporal_shape.BL_REGISTER,
|
*wave_temporal_shape.BL_REGISTER,
|
||||||
# *expr_temporal_shape.BL_REGISTER,
|
# *expr_temporal_shape.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**pulse_temporal_shape.BL_NODES,
|
**pulse_temporal_shape.BL_NODES,
|
||||||
# **wave_temporal_shape.BL_NODES,
|
**wave_temporal_shape.BL_NODES,
|
||||||
# **expr_temporal_shape.BL_NODES,
|
# **expr_temporal_shape.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import functools
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import jax.numpy as jnp
|
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
@ -16,38 +15,6 @@ from .... import managed_objs, sockets
|
||||||
from ... import base, events
|
from ... import base, events
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class PulseTemporalShapeNode(base.MaxwellSimNode):
|
class PulseTemporalShapeNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.PulseTemporalShape
|
node_type = ct.NodeType.PulseTemporalShape
|
||||||
bl_label = 'Gaussian Pulse Temporal Shape'
|
bl_label = 'Gaussian Pulse Temporal Shape'
|
||||||
|
@ -145,7 +112,7 @@ class PulseTemporalShapeNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_time_to_efield_lazy(self, output_sockets) -> td.GaussianPulse:
|
def compute_time_to_efield_lazy(self, output_sockets) -> td.GaussianPulse:
|
||||||
temporal_shape = output_sockets['Temporal Shape']
|
temporal_shape = output_sockets['Temporal Shape']
|
||||||
jax_amp_time = functools.partial(_manual_amp_time, 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!
|
## TODO: Don't just partial() it up, do it property in the ParamsFlow!
|
||||||
## -> Right now it's recompiled every time.
|
## -> Right now it's recompiled every time.
|
||||||
|
|
|
@ -1,75 +1,156 @@
|
||||||
|
"""Implements the `WaveTemporalShapeNode`."""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
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 .... import contracts, sockets
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
|
||||||
|
from .... import contracts as ct
|
||||||
|
from .... import managed_objs, sockets
|
||||||
from ... import base, events
|
from ... import base, events
|
||||||
|
|
||||||
|
|
||||||
class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode):
|
class WaveTemporalShapeNode(base.MaxwellSimNode):
|
||||||
node_type = contracts.NodeType.ContinuousWaveTemporalShape
|
node_type = ct.NodeType.WaveTemporalShape
|
||||||
|
|
||||||
bl_label = 'Continuous Wave Temporal Shape'
|
bl_label = 'Continuous Wave Temporal Shape'
|
||||||
# bl_icon = ...
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets = {
|
input_sockets: typ.ClassVar = {
|
||||||
# "amplitude": sockets.RealNumberSocketDef(
|
'max E': sockets.ExprSocketDef(
|
||||||
# label="Temporal Shape",
|
mathtype=spux.MathType.Complex,
|
||||||
# ), ## Should have a unit of some kind...
|
physical_type=spux.PhysicalType.EField,
|
||||||
'phase': sockets.PhysicalAngleSocketDef(
|
default_value=1 + 0j,
|
||||||
label='Phase',
|
|
||||||
),
|
),
|
||||||
'freq_center': sockets.PhysicalFreqSocketDef(
|
'μ Freq': sockets.ExprSocketDef(
|
||||||
label='Freq Center',
|
physical_type=spux.PhysicalType.Freq,
|
||||||
|
default_unit=spux.THz,
|
||||||
|
default_value=500,
|
||||||
),
|
),
|
||||||
'freq_std': sockets.PhysicalFreqSocketDef(
|
'σ Freq': sockets.ExprSocketDef(
|
||||||
label='Freq STD',
|
physical_type=spux.PhysicalType.Freq,
|
||||||
),
|
default_unit=spux.THz,
|
||||||
'time_delay_rel_ang_freq': sockets.RealNumberSocketDef(
|
default_value=200,
|
||||||
label='Time Delay rel. Ang. Freq',
|
|
||||||
default_value=5.0,
|
|
||||||
),
|
),
|
||||||
|
'Offset Time': sockets.ExprSocketDef(default_value=5, abs_min=2.5),
|
||||||
}
|
}
|
||||||
output_sockets = {
|
output_sockets: typ.ClassVar = {
|
||||||
'temporal_shape': sockets.MaxwellTemporalShapeSocketDef(
|
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
||||||
label='Temporal Shape',
|
'E(t)': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||||
),
|
}
|
||||||
|
|
||||||
|
managed_obj_types: typ.ClassVar = {
|
||||||
|
'plot': managed_objs.ManagedBLImage,
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - UI
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('temporal_shape')
|
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
|
box = layout.box()
|
||||||
_phase = self.compute_input('phase')
|
row = box.row()
|
||||||
_freq_center = self.compute_input('freq_center')
|
row.alignment = 'CENTER'
|
||||||
_freq_std = self.compute_input('freq_std')
|
row.label(text='Parameter Scale')
|
||||||
time_delay_rel_ang_freq = self.compute_input('time_delay_rel_ang_freq')
|
|
||||||
|
|
||||||
cheating_amplitude = 1.0
|
# Split
|
||||||
phase = spu.convert_to(_phase, spu.radian) / spu.radian
|
split = box.split(factor=0.3, align=False)
|
||||||
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(
|
## LHS: Parameter Names
|
||||||
amplitude=cheating_amplitude,
|
col = split.column()
|
||||||
phase=phase,
|
col.alignment = 'RIGHT'
|
||||||
freq0=freq_center,
|
col.label(text='Off t:')
|
||||||
fwidth=freq_std,
|
|
||||||
offset=time_delay_rel_ang_freq,
|
## 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
|
# - Blender Registration
|
||||||
####################
|
####################
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
ContinuousWaveTemporalShapeNode,
|
WaveTemporalShapeNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
contracts.NodeType.ContinuousWaveTemporalShape: (
|
ct.NodeType.WaveTemporalShape: (ct.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES)
|
||||||
contracts.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)
|
||||||
|
@ -249,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:
|
||||||
|
@ -537,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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
Loading…
Reference in New Issue