feat: Continue to add features.
See README.md.
parent
2dc871bbb0
commit
c6c21c78be
10
README.md
10
README.md
|
@ -38,6 +38,10 @@
|
|||
- [ ] Pop-up multiline string print as alternative to console print.
|
||||
- [x] Toggleable auto-plot, auto-3D-preview, auto-value-view, (?)auto-text-view.
|
||||
|
||||
[x] Web Export / Tidy3D Web Exporter
|
||||
- [ ] We need better ways of doing checks before uploading, like for monitor data size. Maybe a SimInfo node?
|
||||
- [ ] We need to be able to "delete and re-upload" (or maybe just delete from the interface).
|
||||
|
||||
[x] File Export / JSON File Export
|
||||
[ ] File Import / Tidy3D File Export
|
||||
- [ ] Implement HDF-based export of Tidy3D-exported object (which includes ex. mesh data and such)
|
||||
|
@ -47,6 +51,9 @@
|
|||
- [ ] Standardize 1D and 2D array loading/saving on numpy's savetxt with gzip enabled.
|
||||
|
||||
## Viz
|
||||
[ ] Sim Info
|
||||
- [ ] Implement estimation of monitor storage
|
||||
- [ ] Implement cost estimation
|
||||
[ ] Monitor Data Viz
|
||||
- [ ] Implement dropdown to choose which monitor in the SimulationData should be visualized (based on which are available in the SimulationData), and implement visualization based on every kind of monitor-adjascent output data type (<https://docs.flexcompute.com/projects/tidy3d/en/latest/api/output_data.html>)
|
||||
- [ ] Project field values onto a plane object (managed)
|
||||
|
@ -61,7 +68,8 @@
|
|||
[x] Point Dipole Source
|
||||
- [ ] Consider a "real" mesh - the empty kind of gets stuck inside of the sim domain.
|
||||
[-] Plane Wave Source
|
||||
- [ ] Implement an oriented vector input with 3D preview.
|
||||
- [ ] **IMPORTANT**: Fix the math so that an actually valid construction emerges!!
|
||||
- [x] Implement an oriented vector input with 3D preview.
|
||||
[ ] Uniform Current Source
|
||||
[ ] TFSF Source
|
||||
|
||||
|
|
|
@ -18,8 +18,10 @@ SOCKET_COLORS = {
|
|||
ST.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue
|
||||
|
||||
# Vector
|
||||
ST.Integer2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
|
||||
ST.Real2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
|
||||
ST.Complex2DVector: (0.4, 0.9, 0.4, 1.0), # Medium Light Green
|
||||
ST.Integer3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
|
||||
ST.Real3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
|
||||
ST.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green
|
||||
|
||||
|
|
|
@ -14,8 +14,10 @@ SOCKET_SHAPES = {
|
|||
ST.ComplexNumber: "CIRCLE_DOT",
|
||||
|
||||
# Vector
|
||||
ST.Integer2DVector: "SQUARE_DOT",
|
||||
ST.Real2DVector: "SQUARE_DOT",
|
||||
ST.Complex2DVector: "DIAMOND_DOT",
|
||||
ST.Integer3DVector: "SQUARE_DOT",
|
||||
ST.Real3DVector: "SQUARE_DOT",
|
||||
ST.Complex3DVector: "DIAMOND_DOT",
|
||||
|
||||
|
|
|
@ -20,10 +20,12 @@ class SocketType(BlenderTypeEnum):
|
|||
ComplexNumber = enum.auto()
|
||||
|
||||
# Vector
|
||||
Integer2DVector = enum.auto()
|
||||
Real2DVector = enum.auto()
|
||||
Real2DVectorDir = enum.auto()
|
||||
Complex2DVector = enum.auto()
|
||||
|
||||
Integer3DVector = enum.auto()
|
||||
Real3DVector = enum.auto()
|
||||
Real3DVectorDir = enum.auto()
|
||||
Complex3DVector = enum.auto()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sympy.physics.units as spu
|
||||
from ....utils import extra_sympy_units as spuex
|
||||
from ....utils import extra_sympy_units as spux
|
||||
|
||||
from .socket_types import SocketType as ST
|
||||
|
||||
|
@ -7,6 +7,7 @@ SOCKET_UNITS = {
|
|||
ST.PhysicalTime: {
|
||||
"default": "PS",
|
||||
"values": {
|
||||
"FS": spux.femtosecond,
|
||||
"PS": spu.picosecond,
|
||||
"NS": spu.nanosecond,
|
||||
"MS": spu.microsecond,
|
||||
|
@ -188,9 +189,9 @@ SOCKET_UNITS = {
|
|||
"default": "UNEWT",
|
||||
"values": {
|
||||
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
|
||||
"NNEWT": spuex.nanonewton,
|
||||
"UNEWT": spuex.micronewton,
|
||||
"MNEWT": spuex.millinewton,
|
||||
"NNEWT": spux.nanonewton,
|
||||
"UNEWT": spux.micronewton,
|
||||
"MNEWT": spux.millinewton,
|
||||
"NEWT": spu.newton,
|
||||
},
|
||||
},
|
||||
|
@ -210,9 +211,9 @@ SOCKET_UNITS = {
|
|||
"default": "UNEWT",
|
||||
"values": {
|
||||
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
|
||||
"NNEWT": spuex.nanonewton,
|
||||
"UNEWT": spuex.micronewton,
|
||||
"MNEWT": spuex.millinewton,
|
||||
"NNEWT": spux.nanonewton,
|
||||
"UNEWT": spux.micronewton,
|
||||
"MNEWT": spux.millinewton,
|
||||
"NEWT": spu.newton,
|
||||
},
|
||||
},
|
||||
|
@ -221,12 +222,12 @@ SOCKET_UNITS = {
|
|||
"default": "THZ",
|
||||
"values": {
|
||||
"HZ": spu.hertz,
|
||||
"KHZ": spuex.kilohertz,
|
||||
"MHZ": spuex.megahertz,
|
||||
"GHZ": spuex.gigahertz,
|
||||
"THZ": spuex.terahertz,
|
||||
"PHZ": spuex.petahertz,
|
||||
"EHZ": spuex.exahertz,
|
||||
"KHZ": spux.kilohertz,
|
||||
"MHZ": spux.megahertz,
|
||||
"GHZ": spux.gigahertz,
|
||||
"THZ": spux.terahertz,
|
||||
"PHZ": spux.petahertz,
|
||||
"EHZ": spux.exahertz,
|
||||
},
|
||||
},
|
||||
ST.PhysicalPol: {
|
||||
|
|
|
@ -6,7 +6,7 @@ from . import sources
|
|||
from . import mediums
|
||||
from . import structures
|
||||
#from . import bounds
|
||||
#from . import monitors
|
||||
from . import monitors
|
||||
from . import simulations
|
||||
#from . import utilities
|
||||
|
||||
|
@ -18,7 +18,7 @@ BL_REGISTER = [
|
|||
*mediums.BL_REGISTER,
|
||||
*structures.BL_REGISTER,
|
||||
# *bounds.BL_REGISTER,
|
||||
# *monitors.BL_REGISTER,
|
||||
*monitors.BL_REGISTER,
|
||||
*simulations.BL_REGISTER,
|
||||
# *utilities.BL_REGISTER,
|
||||
]
|
||||
|
@ -30,7 +30,7 @@ BL_NODES = {
|
|||
**mediums.BL_NODES,
|
||||
**structures.BL_NODES,
|
||||
# **bounds.BL_NODES,
|
||||
# **monitors.BL_NODES,
|
||||
**monitors.BL_NODES,
|
||||
**simulations.BL_NODES,
|
||||
# **utilities.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -456,7 +456,9 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
# Draw Name
|
||||
col = layout.column(align=False)
|
||||
if self.use_sim_node_name:
|
||||
col.prop(self, "sim_node_name", text="")
|
||||
row = col.row(align=True)
|
||||
row.label(text="", icon="EVENT_N")
|
||||
row.prop(self, "sim_node_name", text="")
|
||||
|
||||
# Draw Name
|
||||
self.draw_props(context, col)
|
||||
|
@ -555,13 +557,13 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
for method in self._on_value_changed_methods:
|
||||
if (
|
||||
socket_name
|
||||
and socket_name == method._extra_data.get("changed_socket")
|
||||
and socket_name in method._extra_data.get("changed_sockets")
|
||||
) or (
|
||||
prop_name
|
||||
and prop_name == method._extra_data.get("changed_prop")
|
||||
and prop_name in method._extra_data.get("changed_props")
|
||||
) or (
|
||||
socket_name
|
||||
and method._extra_data.get("changed_loose_input")
|
||||
and method._extra_data["changed_loose_input"]
|
||||
and socket_name in self.loose_input_sockets
|
||||
):
|
||||
method(self)
|
||||
|
@ -833,8 +835,8 @@ def computes_output_socket(
|
|||
# - Decorator: On Show Preview
|
||||
####################
|
||||
def on_value_changed(
|
||||
socket_name: ct.SocketName | None = None,
|
||||
prop_name: str | None = None,
|
||||
socket_name: set[ct.SocketName] | ct.SocketName | None = None,
|
||||
prop_name: set[str] | str | None = None,
|
||||
any_loose_input_socket: bool = False,
|
||||
|
||||
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||
|
@ -863,8 +865,12 @@ def on_value_changed(
|
|||
return chain_event_decorator(
|
||||
callback_type="on_value_changed",
|
||||
extra_data={
|
||||
"changed_socket": socket_name,
|
||||
"changed_prop": prop_name,
|
||||
"changed_sockets": (
|
||||
socket_name if isinstance(socket_name, set) else {socket_name}
|
||||
),
|
||||
"changed_props": (
|
||||
prop_name if isinstance(prop_name, set) else {prop_name}
|
||||
),
|
||||
"changed_loose_input": any_loose_input_socket,
|
||||
},
|
||||
kind=kind,
|
||||
|
|
|
@ -38,10 +38,10 @@ class WaveConstantNode(base.MaxwellSimNode):
|
|||
kind=ct.DataFlowKind.Value,
|
||||
input_sockets={"WL", "Freq"},
|
||||
)
|
||||
def compute_vac_wl(self, input_socket_values: dict) -> sp.Expr:
|
||||
if (vac_wl := input_socket_values["WL"]):
|
||||
def compute_vac_wl(self, input_sockets: dict) -> sp.Expr:
|
||||
if (vac_wl := input_sockets["WL"]):
|
||||
return vac_wl
|
||||
elif (freq := input_socket_values["Freq"]):
|
||||
elif (freq := input_sockets["Freq"]):
|
||||
return spu.convert_to(
|
||||
VAC_SPEED_OF_LIGHT / freq,
|
||||
spu.meter,
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
from . import eh_field_monitor
|
||||
from . import field_power_flux_monitor
|
||||
from . import epsilon_tensor_monitor
|
||||
from . import diffraction_monitor
|
||||
#from . import field_power_flux_monitor
|
||||
#from . import epsilon_tensor_monitor
|
||||
#from . import diffraction_monitor
|
||||
|
||||
BL_REGISTER = [
|
||||
*eh_field_monitor.BL_REGISTER,
|
||||
*field_power_flux_monitor.BL_REGISTER,
|
||||
*epsilon_tensor_monitor.BL_REGISTER,
|
||||
*diffraction_monitor.BL_REGISTER,
|
||||
# *field_power_flux_monitor.BL_REGISTER,
|
||||
# *epsilon_tensor_monitor.BL_REGISTER,
|
||||
# *diffraction_monitor.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**eh_field_monitor.BL_NODES,
|
||||
**field_power_flux_monitor.BL_NODES,
|
||||
**epsilon_tensor_monitor.BL_NODES,
|
||||
**diffraction_monitor.BL_NODES,
|
||||
# **field_power_flux_monitor.BL_NODES,
|
||||
# **epsilon_tensor_monitor.BL_NODES,
|
||||
# **diffraction_monitor.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,171 @@
|
|||
import typing as typ
|
||||
import functools
|
||||
|
||||
import bpy
|
||||
import tidy3d as td
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
import numpy as np
|
||||
import scipy as sc
|
||||
|
||||
from .....utils import analyze_geonodes
|
||||
from .....utils import extra_sympy_units as spux
|
||||
from ... import contracts as ct
|
||||
from ... import sockets
|
||||
from ... import managed_objs
|
||||
from .. import base
|
||||
|
||||
GEONODES_MONITOR_BOX = "monitor_box"
|
||||
|
||||
class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.EHFieldMonitor
|
||||
bl_label = "E/H Field Monitor"
|
||||
use_sim_node_name = True
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets = {
|
||||
"Rec Start": sockets.PhysicalTimeSocketDef(),
|
||||
"Rec Stop": sockets.PhysicalTimeSocketDef(
|
||||
default_value=200*spux.fs
|
||||
),
|
||||
"Center": sockets.PhysicalPoint3DSocketDef(),
|
||||
"Size": sockets.PhysicalSize3DSocketDef(),
|
||||
"Samples/Space": sockets.Integer3DVectorSocketDef(
|
||||
default_value=sp.Matrix([10, 10, 10])
|
||||
),
|
||||
"Samples/Time": sockets.IntegerNumberSocketDef(
|
||||
default_value=100,
|
||||
),
|
||||
}
|
||||
output_sockets = {
|
||||
"Monitor": sockets.MaxwellMonitorSocketDef(),
|
||||
}
|
||||
|
||||
managed_obj_defs = {
|
||||
"monitor_box": ct.schemas.ManagedObjDef(
|
||||
mk=lambda name: managed_objs.ManagedBLObject(name),
|
||||
name_prefix="",
|
||||
)
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_props(self, context, layout):
|
||||
pass
|
||||
|
||||
def draw_info(self, context, col):
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Output Sockets
|
||||
####################
|
||||
@base.computes_output_socket(
|
||||
"Monitor",
|
||||
input_sockets={
|
||||
"Rec Start", "Rec Stop", "Center", "Size", "Samples/Space",
|
||||
"Samples/Time",
|
||||
},
|
||||
props={"sim_node_name"}
|
||||
)
|
||||
def compute_monitor(self, input_sockets: dict, props: dict) -> td.FieldTimeMonitor:
|
||||
_rec_start = input_sockets["Rec Start"]
|
||||
_rec_stop = input_sockets["Rec Stop"]
|
||||
_center = input_sockets["Center"]
|
||||
_size = input_sockets["Size"]
|
||||
_samples_space = input_sockets["Samples/Space"]
|
||||
samples_time = input_sockets["Samples/Time"]
|
||||
|
||||
rec_start = spu.convert_to(_rec_start, spu.second) / spu.second
|
||||
rec_stop = spu.convert_to(_rec_stop, spu.second) / spu.second
|
||||
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
|
||||
samples_space = tuple(_samples_space)
|
||||
|
||||
return td.FieldTimeMonitor(
|
||||
center=center,
|
||||
size=size,
|
||||
name=props["sim_node_name"],
|
||||
start=rec_start,
|
||||
stop=rec_stop,
|
||||
interval=samples_time,
|
||||
interval_space=samples_space,
|
||||
)
|
||||
|
||||
####################
|
||||
# - Preview - Changes to Input Sockets
|
||||
####################
|
||||
@base.on_value_changed(
|
||||
socket_name={"Center", "Size"},
|
||||
input_sockets={"Center", "Size"},
|
||||
managed_objs={"monitor_box"},
|
||||
)
|
||||
def on_value_changed__center_size(
|
||||
self,
|
||||
input_sockets: dict,
|
||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||
):
|
||||
_center = input_sockets["Center"]
|
||||
center = tuple([
|
||||
float(el)
|
||||
for el in spu.convert_to(_center, spu.um) / spu.um
|
||||
])
|
||||
|
||||
_size = input_sockets["Size"]
|
||||
size = tuple([
|
||||
float(el)
|
||||
for el in spu.convert_to(_size, spu.um) / spu.um
|
||||
])
|
||||
## TODO: Preview unit system?? Presume um for now
|
||||
|
||||
# Retrieve Hard-Coded GeoNodes and Analyze Input
|
||||
geo_nodes = bpy.data.node_groups[GEONODES_MONITOR_BOX]
|
||||
geonodes_interface = analyze_geonodes.interface(
|
||||
geo_nodes, direc="INPUT"
|
||||
)
|
||||
|
||||
# Sync Modifier Inputs
|
||||
managed_objs["monitor_box"].sync_geonodes_modifier(
|
||||
geonodes_node_group=geo_nodes,
|
||||
geonodes_identifier_to_value={
|
||||
geonodes_interface["Size"].identifier: size,
|
||||
## TODO: Use 'bl_socket_map.value_to_bl`!
|
||||
## - This accounts for auto-conversion, unit systems, etc. .
|
||||
## - We could keep it in the node base class...
|
||||
## - ...But it needs aligning with Blender, too. Hmm.
|
||||
}
|
||||
)
|
||||
|
||||
# Sync Object Position
|
||||
managed_objs["monitor_box"].bl_object("MESH").location = center
|
||||
|
||||
####################
|
||||
# - Preview - Show Preview
|
||||
####################
|
||||
@base.on_show_preview(
|
||||
managed_objs={"monitor_box"},
|
||||
)
|
||||
def on_show_preview(
|
||||
self,
|
||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||
):
|
||||
managed_objs["monitor_box"].show_preview("MESH")
|
||||
self.on_value_changed__center_size()
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
||||
|
||||
BL_REGISTER = [
|
||||
EHFieldMonitorNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
ct.NodeType.EHFieldMonitor: (
|
||||
ct.NodeCategory.MAXWELLSIM_MONITORS
|
||||
)
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ class Tidy3DTaskStatusModalOperator(bpy.types.Operator):
|
|||
node = context.node
|
||||
wm = context.window_manager
|
||||
|
||||
self._timer = wm.event_timer_add(0.25, window=context.window)
|
||||
self._timer = wm.event_timer_add(2.0, window=context.window)
|
||||
self._task_id = node.uploaded_task_id
|
||||
self._node = node
|
||||
self._status = task_status(self._task_id)
|
||||
|
@ -335,7 +335,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
|||
"queued": (0.0, "Queued..."),
|
||||
"preprocessing": (0.05, "Pre-processing..."),
|
||||
"running": (0.2, "Running..."),
|
||||
"postprocessing": (0.85, "Post-processing..."),
|
||||
"postprocess": (0.85, "Post-processing..."),
|
||||
"success": (1.0, f"Success (={billed_task_cost(self.uploaded_task_id)} credits)"),
|
||||
"error": (1.0, f"Error (={billed_task_cost(self.uploaded_task_id)} credits)"),
|
||||
}[task_status(self.uploaded_task_id)]
|
||||
|
|
|
@ -45,11 +45,14 @@ class FDTDSimNode(base.MaxwellSimNode):
|
|||
sources = [sources]
|
||||
if not isinstance(structures, list):
|
||||
structures = [structures]
|
||||
if not isinstance(monitors, list):
|
||||
monitors = [monitors]
|
||||
|
||||
return td.Simulation(
|
||||
**sim_domain, ## run_time=, size=, grid=, medium=
|
||||
structures=structures,
|
||||
sources=sources,
|
||||
monitors=monitors,
|
||||
boundary_spec=bounds,
|
||||
)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from ... import sockets
|
|||
from .. import base
|
||||
from ... import managed_objs
|
||||
|
||||
GEONODES_DOMAIN_BOX = "domain_box"
|
||||
GEONODES_DOMAIN_BOX = "simdomain_box"
|
||||
|
||||
class SimDomainNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.SimDomain
|
||||
|
@ -20,6 +20,7 @@ class SimDomainNode(base.MaxwellSimNode):
|
|||
default_value = 5 * spu.ps,
|
||||
default_unit = spu.ps,
|
||||
),
|
||||
"Center": sockets.PhysicalSize3DSocketDef(),
|
||||
"Size": sockets.PhysicalSize3DSocketDef(),
|
||||
"Grid": sockets.MaxwellSimGridSocketDef(),
|
||||
"Ambient Medium": sockets.MaxwellMediumSocketDef(),
|
||||
|
@ -40,19 +41,22 @@ class SimDomainNode(base.MaxwellSimNode):
|
|||
####################
|
||||
@base.computes_output_socket(
|
||||
"Domain",
|
||||
input_sockets={"Duration", "Size", "Grid", "Ambient Medium"},
|
||||
input_sockets={"Duration", "Center", "Size", "Grid", "Ambient Medium"},
|
||||
)
|
||||
def compute_sim_domain(self, input_sockets: dict) -> sp.Expr:
|
||||
if all([
|
||||
(_duration := input_sockets["Duration"]),
|
||||
(_center := input_sockets["Center"]),
|
||||
(_size := input_sockets["Size"]),
|
||||
(grid := input_sockets["Grid"]),
|
||||
(medium := input_sockets["Ambient Medium"]),
|
||||
]):
|
||||
duration = spu.convert_to(_duration, spu.second) / spu.second
|
||||
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
|
||||
return dict(
|
||||
run_time=duration,
|
||||
center=center,
|
||||
size=size,
|
||||
grid_spec=grid,
|
||||
medium=medium,
|
||||
|
@ -62,15 +66,21 @@ class SimDomainNode(base.MaxwellSimNode):
|
|||
# - Preview
|
||||
####################
|
||||
@base.on_value_changed(
|
||||
socket_name="Size",
|
||||
input_sockets={"Size"},
|
||||
socket_name={"Center", "Size"},
|
||||
input_sockets={"Center", "Size"},
|
||||
managed_objs={"domain_box"},
|
||||
)
|
||||
def on_value_changed__center(
|
||||
def on_value_changed__center_size(
|
||||
self,
|
||||
input_sockets: dict,
|
||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||
):
|
||||
_center = input_sockets["Center"]
|
||||
center = tuple([
|
||||
float(el)
|
||||
for el in spu.convert_to(_center, spu.um) / spu.um
|
||||
])
|
||||
|
||||
_size = input_sockets["Size"]
|
||||
size = tuple([
|
||||
float(el)
|
||||
|
@ -88,7 +98,7 @@ class SimDomainNode(base.MaxwellSimNode):
|
|||
managed_objs["domain_box"].sync_geonodes_modifier(
|
||||
geonodes_node_group=geo_nodes,
|
||||
geonodes_identifier_to_value={
|
||||
geonodes_interface["Size"].identifier: size
|
||||
geonodes_interface["Size"].identifier: size,
|
||||
## TODO: Use 'bl_socket_map.value_to_bl`!
|
||||
## - This accounts for auto-conversion, unit systems, etc. .
|
||||
## - We could keep it in the node base class...
|
||||
|
@ -96,6 +106,9 @@ class SimDomainNode(base.MaxwellSimNode):
|
|||
}
|
||||
)
|
||||
|
||||
# Sync Object Position
|
||||
managed_objs["domain_box"].bl_object("MESH").location = center
|
||||
|
||||
@base.on_show_preview(
|
||||
managed_objs={"domain_box"},
|
||||
)
|
||||
|
@ -104,7 +117,7 @@ class SimDomainNode(base.MaxwellSimNode):
|
|||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||
):
|
||||
managed_objs["domain_box"].show_preview("MESH")
|
||||
self.on_value_changed__center()
|
||||
self.on_value_changed__center_size()
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import typing_extensions as typx
|
||||
import math
|
||||
|
||||
import tidy3d as td
|
||||
|
@ -6,10 +7,46 @@ import sympy.physics.units as spu
|
|||
|
||||
import bpy
|
||||
|
||||
from .....utils import analyze_geonodes
|
||||
from ... import managed_objs
|
||||
from ... import contracts as ct
|
||||
from ... import sockets
|
||||
from .. import base
|
||||
|
||||
GEONODES_PLANE_WAVE = "source_plane_wave"
|
||||
|
||||
def convert_vector_to_spherical(
|
||||
v: sp.MatrixBase,
|
||||
) -> tuple[str, str, sp.Expr, sp.Expr]:
|
||||
"""Converts a vector (maybe normalized) to spherical coordinates from an arbitrary choice of injection axis.
|
||||
|
||||
Injection axis is chosen to minimize `theta`
|
||||
"""
|
||||
x, y, z = v
|
||||
|
||||
injection_axis = max(
|
||||
('x', abs(x)),
|
||||
('y', abs(y)),
|
||||
('z', abs(z)),
|
||||
key=lambda item: item[1]
|
||||
)[0]
|
||||
## Select injection axis that minimizes 'theta'
|
||||
|
||||
if injection_axis == "x":
|
||||
direction = "+" if x >= 0 else "-"
|
||||
theta = sp.acos(x / sp.sqrt(x**2 + y**2 + z**2))
|
||||
phi = sp.atan2(z, y)
|
||||
elif injection_axis == "y":
|
||||
direction = "+" if y >= 0 else "-"
|
||||
theta = sp.acos(y / sp.sqrt(x**2 + y**2 + z**2))
|
||||
phi = sp.atan2(x, z)
|
||||
else:
|
||||
direction = "+" if z >= 0 else "-"
|
||||
theta = sp.acos(z / sp.sqrt(x**2 + y**2 + z**2))
|
||||
phi = sp.atan2(y, x)
|
||||
|
||||
return injection_axis, direction, theta, phi
|
||||
|
||||
class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.PlaneWaveSource
|
||||
bl_label = "Plane Wave Source"
|
||||
|
@ -20,79 +57,112 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
|||
input_sockets = {
|
||||
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
|
||||
"Center": sockets.PhysicalPoint3DSocketDef(),
|
||||
"Direction": sockets.BoolSocketDef(
|
||||
default_value=True,
|
||||
"Direction": sockets.Real3DVectorSocketDef(
|
||||
default_value=sp.Matrix([0, 0, -1])
|
||||
),
|
||||
"Pol": sockets.PhysicalPolSocketDef(),
|
||||
"Pol Angle": sockets.PhysicalAngleSocketDef(),
|
||||
}
|
||||
output_sockets = {
|
||||
"Source": sockets.MaxwellSourceSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
inj_axis: bpy.props.EnumProperty(
|
||||
name="Injection Axis",
|
||||
description="Axis to inject plane wave along",
|
||||
items=[
|
||||
("X", "X", "X-Axis"),
|
||||
("Y", "Y", "Y-Axis"),
|
||||
("Z", "Z", "Z-Axis"),
|
||||
],
|
||||
default="Y",
|
||||
update=(lambda self, context: self.sync_prop("inj_axis")),
|
||||
managed_obj_defs = {
|
||||
"plane_wave_source": ct.schemas.ManagedObjDef(
|
||||
mk=lambda name: managed_objs.ManagedBLObject(name),
|
||||
name_prefix="",
|
||||
)
|
||||
}
|
||||
|
||||
####################
|
||||
# - Output Socket Computation
|
||||
####################
|
||||
@base.computes_output_socket(
|
||||
"Source",
|
||||
input_sockets={"Temporal Shape", "Center", "Direction", "Pol"},
|
||||
props={"inj_axis"},
|
||||
input_sockets={"Temporal Shape", "Center", "Direction", "Pol Angle"},
|
||||
)
|
||||
def compute_source(self, input_sockets: dict, props: dict):
|
||||
def compute_source(self, input_sockets: dict):
|
||||
temporal_shape = input_sockets["Temporal Shape"]
|
||||
_center = input_sockets["Center"]
|
||||
_direction = input_sockets["Direction"]
|
||||
_inj_axis = props["inj_axis"]
|
||||
pol = input_sockets["Pol"]
|
||||
direction = input_sockets["Direction"]
|
||||
pol_angle = input_sockets["Pol Angle"]
|
||||
|
||||
injection_axis, dir_sgn, theta, phi = convert_vector_to_spherical(direction)
|
||||
|
||||
direction = {
|
||||
False: "-",
|
||||
True: "+",
|
||||
}[_direction]
|
||||
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||
size = {
|
||||
"X": (0, math.inf, math.inf),
|
||||
"Y": (math.inf, 0, math.inf),
|
||||
"Z": (math.inf, math.inf, 0),
|
||||
}[_inj_axis]
|
||||
|
||||
S0, S1, S2, S3 = tuple(pol)
|
||||
|
||||
chi = 0.5 * sp.atan2(S2, S1)
|
||||
psi = 0.5 * sp.asin(S3/S0)
|
||||
## chi: Pol angle
|
||||
## psi: Ellipticity
|
||||
|
||||
## TODO: Something's wonky.
|
||||
#angle_theta = chi
|
||||
#angle_phi = psi
|
||||
pol_angle = sp.pi/2 - chi
|
||||
"x": (0, math.inf, math.inf),
|
||||
"y": (math.inf, 0, math.inf),
|
||||
"z": (math.inf, math.inf, 0),
|
||||
}[injection_axis]
|
||||
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||
|
||||
# Display the results
|
||||
return td.PlaneWave(
|
||||
center=tuple(_center),
|
||||
size=size,
|
||||
center=center,
|
||||
source_time=temporal_shape,
|
||||
direction="+" if _direction else "-",
|
||||
#angle_theta=angle_theta,
|
||||
#angle_phi=angle_phi,
|
||||
#pol_angle=pol_angle,
|
||||
size=size,
|
||||
direction=dir_sgn,
|
||||
angle_theta=theta,
|
||||
angle_phi=phi,
|
||||
pol_angle=pol_angle,
|
||||
)
|
||||
|
||||
####################
|
||||
# - Preview
|
||||
####################
|
||||
@base.on_value_changed(
|
||||
socket_name={"Center", "Direction"},
|
||||
input_sockets={"Center", "Direction"},
|
||||
managed_objs={"plane_wave_source"},
|
||||
)
|
||||
def on_value_changed__center_direction(
|
||||
self,
|
||||
input_sockets: dict,
|
||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||
):
|
||||
_center = input_sockets["Center"]
|
||||
center = tuple([
|
||||
float(el)
|
||||
for el in spu.convert_to(_center, spu.um) / spu.um
|
||||
])
|
||||
|
||||
_direction = input_sockets["Direction"]
|
||||
direction = tuple([
|
||||
float(el)
|
||||
for el in _direction
|
||||
])
|
||||
## TODO: Preview unit system?? Presume um for now
|
||||
|
||||
# Retrieve Hard-Coded GeoNodes and Analyze Input
|
||||
geo_nodes = bpy.data.node_groups[GEONODES_PLANE_WAVE]
|
||||
geonodes_interface = analyze_geonodes.interface(
|
||||
geo_nodes, direc="INPUT"
|
||||
)
|
||||
|
||||
# Sync Modifier Inputs
|
||||
managed_objs["plane_wave_source"].sync_geonodes_modifier(
|
||||
geonodes_node_group=geo_nodes,
|
||||
geonodes_identifier_to_value={
|
||||
geonodes_interface["Direction"].identifier: direction,
|
||||
## TODO: Use 'bl_socket_map.value_to_bl`!
|
||||
## - This accounts for auto-conversion, unit systems, etc. .
|
||||
## - We could keep it in the node base class...
|
||||
## - ...But it needs aligning with Blender, too. Hmm.
|
||||
}
|
||||
)
|
||||
|
||||
# Sync Object Position
|
||||
managed_objs["plane_wave_source"].bl_object("MESH").location = center
|
||||
|
||||
@base.on_show_preview(
|
||||
managed_objs={"plane_wave_source"},
|
||||
)
|
||||
def on_show_preview(
|
||||
self,
|
||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||
):
|
||||
managed_objs["plane_wave_source"].show_preview("MESH")
|
||||
self.on_value_changed__center_direction()
|
||||
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -15,6 +15,7 @@ ComplexNumberSocketDef = number.ComplexNumberSocketDef
|
|||
from . import vector
|
||||
Real2DVectorSocketDef = vector.Real2DVectorSocketDef
|
||||
Complex2DVectorSocketDef = vector.Complex2DVectorSocketDef
|
||||
Integer3DVectorSocketDef = vector.Integer3DVectorSocketDef
|
||||
Real3DVectorSocketDef = vector.Real3DVectorSocketDef
|
||||
Complex3DVectorSocketDef = vector.Complex3DVectorSocketDef
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
# Options
|
||||
#link_limit: int = 0
|
||||
use_units: bool = False
|
||||
#list_like: bool = False
|
||||
|
||||
# Computed
|
||||
bl_idname: str
|
||||
|
|
|
@ -17,32 +17,6 @@ VAC_SPEED_OF_LIGHT = (
|
|||
class MaxwellMonitorBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellMonitor
|
||||
bl_label = "Maxwell Monitor"
|
||||
use_units = True
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
wl: bpy.props.FloatProperty(
|
||||
name="WL",
|
||||
description="WL to store in monitor",
|
||||
default=500.0,
|
||||
precision=4,
|
||||
step=50,
|
||||
update=(lambda self, context: self.sync_prop("wl", context)),
|
||||
)
|
||||
|
||||
@property
|
||||
def value(self) -> td.Monitor:
|
||||
freq = spu.convert_to(
|
||||
VAC_SPEED_OF_LIGHT / (self.wl*self.unit),
|
||||
spu.hertz,
|
||||
) / spu.hertz
|
||||
return td.FieldMonitor(
|
||||
size=(td.inf, td.inf, 0),
|
||||
freqs=[freq],
|
||||
name="fields",
|
||||
colocate=True,
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
|
|
|
@ -162,7 +162,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
"queued": "Task is queued for simulation",
|
||||
"preprocessing": "Task is pre-processing",
|
||||
"running": "Task is currently running",
|
||||
"postprocessing": "Task is post-processing",
|
||||
"postprocess": "Task is post-processing",
|
||||
"success": "Task ran successfully, costing {task.real_flex_unit} credits",
|
||||
"error": "Task ran, but an error occurred",
|
||||
}[task.status],
|
||||
|
@ -174,7 +174,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
"queued": "SEQUENCE_COLOR_03",
|
||||
"preprocessing": "SEQUENCE_COLOR_02",
|
||||
"running": "SEQUENCE_COLOR_05",
|
||||
"postprocessing": "SEQUENCE_COLOR_06",
|
||||
"postprocess": "SEQUENCE_COLOR_06",
|
||||
"success": "SEQUENCE_COLOR_04",
|
||||
"error": "SEQUENCE_COLOR_01",
|
||||
}[task.status],
|
||||
|
|
|
@ -3,8 +3,10 @@ from . import complex_2d_vector
|
|||
Real2DVectorSocketDef = real_2d_vector.Real2DVectorSocketDef
|
||||
Complex2DVectorSocketDef = complex_2d_vector.Complex2DVectorSocketDef
|
||||
|
||||
from . import integer_3d_vector
|
||||
from . import real_3d_vector
|
||||
from . import complex_3d_vector
|
||||
Integer3DVectorSocketDef = integer_3d_vector.Integer3DVectorSocketDef
|
||||
Real3DVectorSocketDef = real_3d_vector.Real3DVectorSocketDef
|
||||
Complex3DVectorSocketDef = complex_3d_vector.Complex3DVectorSocketDef
|
||||
|
||||
|
@ -13,6 +15,7 @@ BL_REGISTER = [
|
|||
*real_2d_vector.BL_REGISTER,
|
||||
*complex_2d_vector.BL_REGISTER,
|
||||
|
||||
*integer_3d_vector.BL_REGISTER,
|
||||
*real_3d_vector.BL_REGISTER,
|
||||
*complex_3d_vector.BL_REGISTER,
|
||||
]
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import ConstrSympyExpr
|
||||
from .. import base
|
||||
from ... import contracts as ct
|
||||
|
||||
Integer3DVector = ConstrSympyExpr(
|
||||
allow_variables=False,
|
||||
allow_units=False,
|
||||
allowed_sets={"integer"},
|
||||
allowed_structures={"matrix"},
|
||||
allowed_matrix_shapes={(3, 1)},
|
||||
)
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class Integer3DVectorBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.Integer3DVector
|
||||
bl_label = "Integer 3D Vector"
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
raw_value: bpy.props.IntVectorProperty(
|
||||
name="Int 3D Vector",
|
||||
description="Represents an integer 3D (coordinate) vector",
|
||||
size=3,
|
||||
default=(0, 0, 0),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def value(self) -> Integer3DVector:
|
||||
return sp.Matrix(tuple(self.raw_value))
|
||||
|
||||
@value.setter
|
||||
def value(self, value: Integer3DVector) -> None:
|
||||
self.raw_value = tuple(int(el) for el in value)
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class Integer3DVectorSocketDef(pyd.BaseModel):
|
||||
socket_type: ct.SocketType = ct.SocketType.Integer3DVector
|
||||
|
||||
default_value: Integer3DVector = sp.Matrix([0, 0, 0])
|
||||
|
||||
def init(self, bl_socket: Integer3DVectorBLSocket) -> None:
|
||||
bl_socket.value = self.default_value
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
Integer3DVectorBLSocket,
|
||||
]
|
|
@ -22,6 +22,13 @@ def get_units(expression: sp.Expr):
|
|||
if isinstance(arg, spu.Quantity)
|
||||
}
|
||||
|
||||
####################
|
||||
# - Time
|
||||
####################
|
||||
femtosecond = fs = spu.Quantity("femtosecond", abbrev="fs")
|
||||
femtosecond.set_global_relative_scale_factor(spu.femto, spu.second)
|
||||
|
||||
|
||||
####################
|
||||
# - Force
|
||||
####################
|
||||
|
|
Loading…
Reference in New Issue