feat: Continue to add features.

See README.md.
Sofus Albert Høgsbro Rose 2024-03-12 09:01:50 +01:00
parent 2dc871bbb0
commit c6c21c78be
21 changed files with 455 additions and 127 deletions

View File

@ -38,6 +38,10 @@
- [ ] Pop-up multiline string print as alternative to console print. - [ ] Pop-up multiline string print as alternative to console print.
- [x] Toggleable auto-plot, auto-3D-preview, auto-value-view, (?)auto-text-view. - [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 [x] File Export / JSON File Export
[ ] File Import / Tidy3D File Export [ ] File Import / Tidy3D File Export
- [ ] Implement HDF-based export of Tidy3D-exported object (which includes ex. mesh data and such) - [ ] 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. - [ ] Standardize 1D and 2D array loading/saving on numpy's savetxt with gzip enabled.
## Viz ## Viz
[ ] Sim Info
- [ ] Implement estimation of monitor storage
- [ ] Implement cost estimation
[ ] Monitor Data Viz [ ] 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>) - [ ] 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) - [ ] Project field values onto a plane object (managed)
@ -61,7 +68,8 @@
[x] Point Dipole Source [x] Point Dipole Source
- [ ] Consider a "real" mesh - the empty kind of gets stuck inside of the sim domain. - [ ] Consider a "real" mesh - the empty kind of gets stuck inside of the sim domain.
[-] Plane Wave Source [-] 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 [ ] Uniform Current Source
[ ] TFSF Source [ ] TFSF Source

View File

@ -18,8 +18,10 @@ SOCKET_COLORS = {
ST.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue ST.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue
# Vector # 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.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.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.Real3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
ST.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green ST.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green

View File

@ -14,8 +14,10 @@ SOCKET_SHAPES = {
ST.ComplexNumber: "CIRCLE_DOT", ST.ComplexNumber: "CIRCLE_DOT",
# Vector # Vector
ST.Integer2DVector: "SQUARE_DOT",
ST.Real2DVector: "SQUARE_DOT", ST.Real2DVector: "SQUARE_DOT",
ST.Complex2DVector: "DIAMOND_DOT", ST.Complex2DVector: "DIAMOND_DOT",
ST.Integer3DVector: "SQUARE_DOT",
ST.Real3DVector: "SQUARE_DOT", ST.Real3DVector: "SQUARE_DOT",
ST.Complex3DVector: "DIAMOND_DOT", ST.Complex3DVector: "DIAMOND_DOT",

View File

@ -20,10 +20,12 @@ class SocketType(BlenderTypeEnum):
ComplexNumber = enum.auto() ComplexNumber = enum.auto()
# Vector # Vector
Integer2DVector = enum.auto()
Real2DVector = enum.auto() Real2DVector = enum.auto()
Real2DVectorDir = enum.auto() Real2DVectorDir = enum.auto()
Complex2DVector = enum.auto() Complex2DVector = enum.auto()
Integer3DVector = enum.auto()
Real3DVector = enum.auto() Real3DVector = enum.auto()
Real3DVectorDir = enum.auto() Real3DVectorDir = enum.auto()
Complex3DVector = enum.auto() Complex3DVector = enum.auto()

View File

@ -1,5 +1,5 @@
import sympy.physics.units as spu 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 from .socket_types import SocketType as ST
@ -7,6 +7,7 @@ SOCKET_UNITS = {
ST.PhysicalTime: { ST.PhysicalTime: {
"default": "PS", "default": "PS",
"values": { "values": {
"FS": spux.femtosecond,
"PS": spu.picosecond, "PS": spu.picosecond,
"NS": spu.nanosecond, "NS": spu.nanosecond,
"MS": spu.microsecond, "MS": spu.microsecond,
@ -188,9 +189,9 @@ SOCKET_UNITS = {
"default": "UNEWT", "default": "UNEWT",
"values": { "values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2, "KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spuex.nanonewton, "NNEWT": spux.nanonewton,
"UNEWT": spuex.micronewton, "UNEWT": spux.micronewton,
"MNEWT": spuex.millinewton, "MNEWT": spux.millinewton,
"NEWT": spu.newton, "NEWT": spu.newton,
}, },
}, },
@ -210,9 +211,9 @@ SOCKET_UNITS = {
"default": "UNEWT", "default": "UNEWT",
"values": { "values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2, "KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spuex.nanonewton, "NNEWT": spux.nanonewton,
"UNEWT": spuex.micronewton, "UNEWT": spux.micronewton,
"MNEWT": spuex.millinewton, "MNEWT": spux.millinewton,
"NEWT": spu.newton, "NEWT": spu.newton,
}, },
}, },
@ -221,12 +222,12 @@ SOCKET_UNITS = {
"default": "THZ", "default": "THZ",
"values": { "values": {
"HZ": spu.hertz, "HZ": spu.hertz,
"KHZ": spuex.kilohertz, "KHZ": spux.kilohertz,
"MHZ": spuex.megahertz, "MHZ": spux.megahertz,
"GHZ": spuex.gigahertz, "GHZ": spux.gigahertz,
"THZ": spuex.terahertz, "THZ": spux.terahertz,
"PHZ": spuex.petahertz, "PHZ": spux.petahertz,
"EHZ": spuex.exahertz, "EHZ": spux.exahertz,
}, },
}, },
ST.PhysicalPol: { ST.PhysicalPol: {

View File

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

View File

@ -456,7 +456,9 @@ class MaxwellSimNode(bpy.types.Node):
# Draw Name # Draw Name
col = layout.column(align=False) col = layout.column(align=False)
if self.use_sim_node_name: 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 # Draw Name
self.draw_props(context, col) self.draw_props(context, col)
@ -555,13 +557,13 @@ class MaxwellSimNode(bpy.types.Node):
for method in self._on_value_changed_methods: for method in self._on_value_changed_methods:
if ( if (
socket_name socket_name
and socket_name == method._extra_data.get("changed_socket") and socket_name in method._extra_data.get("changed_sockets")
) or ( ) or (
prop_name prop_name
and prop_name == method._extra_data.get("changed_prop") and prop_name in method._extra_data.get("changed_props")
) or ( ) or (
socket_name 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 and socket_name in self.loose_input_sockets
): ):
method(self) method(self)
@ -833,8 +835,8 @@ def computes_output_socket(
# - Decorator: On Show Preview # - Decorator: On Show Preview
#################### ####################
def on_value_changed( def on_value_changed(
socket_name: ct.SocketName | None = None, socket_name: set[ct.SocketName] | ct.SocketName | None = None,
prop_name: str | None = None, prop_name: set[str] | str | None = None,
any_loose_input_socket: bool = False, any_loose_input_socket: bool = False,
kind: ct.DataFlowKind = ct.DataFlowKind.Value, kind: ct.DataFlowKind = ct.DataFlowKind.Value,
@ -863,8 +865,12 @@ def on_value_changed(
return chain_event_decorator( return chain_event_decorator(
callback_type="on_value_changed", callback_type="on_value_changed",
extra_data={ extra_data={
"changed_socket": socket_name, "changed_sockets": (
"changed_prop": prop_name, 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, "changed_loose_input": any_loose_input_socket,
}, },
kind=kind, kind=kind,

View File

@ -38,10 +38,10 @@ class WaveConstantNode(base.MaxwellSimNode):
kind=ct.DataFlowKind.Value, kind=ct.DataFlowKind.Value,
input_sockets={"WL", "Freq"}, input_sockets={"WL", "Freq"},
) )
def compute_vac_wl(self, input_socket_values: dict) -> sp.Expr: def compute_vac_wl(self, input_sockets: dict) -> sp.Expr:
if (vac_wl := input_socket_values["WL"]): if (vac_wl := input_sockets["WL"]):
return vac_wl return vac_wl
elif (freq := input_socket_values["Freq"]): elif (freq := input_sockets["Freq"]):
return spu.convert_to( return spu.convert_to(
VAC_SPEED_OF_LIGHT / freq, VAC_SPEED_OF_LIGHT / freq,
spu.meter, spu.meter,

View File

@ -1,17 +1,17 @@
from . import eh_field_monitor from . import eh_field_monitor
from . import field_power_flux_monitor #from . import field_power_flux_monitor
from . import epsilon_tensor_monitor #from . import epsilon_tensor_monitor
from . import diffraction_monitor #from . import diffraction_monitor
BL_REGISTER = [ BL_REGISTER = [
*eh_field_monitor.BL_REGISTER, *eh_field_monitor.BL_REGISTER,
*field_power_flux_monitor.BL_REGISTER, # *field_power_flux_monitor.BL_REGISTER,
*epsilon_tensor_monitor.BL_REGISTER, # *epsilon_tensor_monitor.BL_REGISTER,
*diffraction_monitor.BL_REGISTER, # *diffraction_monitor.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**eh_field_monitor.BL_NODES, **eh_field_monitor.BL_NODES,
**field_power_flux_monitor.BL_NODES, # **field_power_flux_monitor.BL_NODES,
**epsilon_tensor_monitor.BL_NODES, # **epsilon_tensor_monitor.BL_NODES,
**diffraction_monitor.BL_NODES, # **diffraction_monitor.BL_NODES,
} }

View File

@ -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 # - Blender Registration
#################### ####################
BL_REGISTER = [] BL_REGISTER = [
BL_NODES = {} EHFieldMonitorNode,
]
BL_NODES = {
ct.NodeType.EHFieldMonitor: (
ct.NodeCategory.MAXWELLSIM_MONITORS
)
}

View File

@ -74,7 +74,7 @@ class Tidy3DTaskStatusModalOperator(bpy.types.Operator):
node = context.node node = context.node
wm = context.window_manager 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._task_id = node.uploaded_task_id
self._node = node self._node = node
self._status = task_status(self._task_id) self._status = task_status(self._task_id)
@ -335,7 +335,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
"queued": (0.0, "Queued..."), "queued": (0.0, "Queued..."),
"preprocessing": (0.05, "Pre-processing..."), "preprocessing": (0.05, "Pre-processing..."),
"running": (0.2, "Running..."), "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)"), "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)"), "error": (1.0, f"Error (={billed_task_cost(self.uploaded_task_id)} credits)"),
}[task_status(self.uploaded_task_id)] }[task_status(self.uploaded_task_id)]

View File

@ -45,11 +45,14 @@ class FDTDSimNode(base.MaxwellSimNode):
sources = [sources] sources = [sources]
if not isinstance(structures, list): if not isinstance(structures, list):
structures = [structures] 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,
sources=sources, sources=sources,
monitors=monitors,
boundary_spec=bounds, boundary_spec=bounds,
) )

View File

@ -9,7 +9,7 @@ from ... import sockets
from .. import base from .. import base
from ... import managed_objs from ... import managed_objs
GEONODES_DOMAIN_BOX = "domain_box" GEONODES_DOMAIN_BOX = "simdomain_box"
class SimDomainNode(base.MaxwellSimNode): class SimDomainNode(base.MaxwellSimNode):
node_type = ct.NodeType.SimDomain node_type = ct.NodeType.SimDomain
@ -20,6 +20,7 @@ class SimDomainNode(base.MaxwellSimNode):
default_value = 5 * spu.ps, default_value = 5 * spu.ps,
default_unit = spu.ps, default_unit = spu.ps,
), ),
"Center": sockets.PhysicalSize3DSocketDef(),
"Size": sockets.PhysicalSize3DSocketDef(), "Size": sockets.PhysicalSize3DSocketDef(),
"Grid": sockets.MaxwellSimGridSocketDef(), "Grid": sockets.MaxwellSimGridSocketDef(),
"Ambient Medium": sockets.MaxwellMediumSocketDef(), "Ambient Medium": sockets.MaxwellMediumSocketDef(),
@ -40,19 +41,22 @@ class SimDomainNode(base.MaxwellSimNode):
#################### ####################
@base.computes_output_socket( @base.computes_output_socket(
"Domain", "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: def compute_sim_domain(self, input_sockets: dict) -> sp.Expr:
if all([ if all([
(_duration := input_sockets["Duration"]), (_duration := input_sockets["Duration"]),
(_center := input_sockets["Center"]),
(_size := input_sockets["Size"]), (_size := input_sockets["Size"]),
(grid := input_sockets["Grid"]), (grid := input_sockets["Grid"]),
(medium := input_sockets["Ambient Medium"]), (medium := input_sockets["Ambient Medium"]),
]): ]):
duration = spu.convert_to(_duration, spu.second) / spu.second 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) size = tuple(spu.convert_to(_size, spu.um) / spu.um)
return dict( return dict(
run_time=duration, run_time=duration,
center=center,
size=size, size=size,
grid_spec=grid, grid_spec=grid,
medium=medium, medium=medium,
@ -62,15 +66,21 @@ class SimDomainNode(base.MaxwellSimNode):
# - Preview # - Preview
#################### ####################
@base.on_value_changed( @base.on_value_changed(
socket_name="Size", socket_name={"Center", "Size"},
input_sockets={"Size"}, input_sockets={"Center", "Size"},
managed_objs={"domain_box"}, managed_objs={"domain_box"},
) )
def on_value_changed__center( def on_value_changed__center_size(
self, self,
input_sockets: dict, input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj], 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 = input_sockets["Size"]
size = tuple([ size = tuple([
float(el) float(el)
@ -88,7 +98,7 @@ class SimDomainNode(base.MaxwellSimNode):
managed_objs["domain_box"].sync_geonodes_modifier( managed_objs["domain_box"].sync_geonodes_modifier(
geonodes_node_group=geo_nodes, geonodes_node_group=geo_nodes,
geonodes_identifier_to_value={ geonodes_identifier_to_value={
geonodes_interface["Size"].identifier: size geonodes_interface["Size"].identifier: size,
## TODO: Use 'bl_socket_map.value_to_bl`! ## TODO: Use 'bl_socket_map.value_to_bl`!
## - This accounts for auto-conversion, unit systems, etc. . ## - This accounts for auto-conversion, unit systems, etc. .
## - We could keep it in the node base class... ## - 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( @base.on_show_preview(
managed_objs={"domain_box"}, managed_objs={"domain_box"},
) )
@ -104,7 +117,7 @@ class SimDomainNode(base.MaxwellSimNode):
managed_objs: dict[str, ct.schemas.ManagedObj], managed_objs: dict[str, ct.schemas.ManagedObj],
): ):
managed_objs["domain_box"].show_preview("MESH") managed_objs["domain_box"].show_preview("MESH")
self.on_value_changed__center() self.on_value_changed__center_size()
#################### ####################
# - Blender Registration # - Blender Registration

View File

@ -1,3 +1,4 @@
import typing_extensions as typx
import math import math
import tidy3d as td import tidy3d as td
@ -6,10 +7,46 @@ import sympy.physics.units as spu
import bpy import bpy
from .....utils import analyze_geonodes
from ... import managed_objs
from ... import contracts as ct from ... import contracts as ct
from ... import sockets from ... import sockets
from .. import base 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): class PlaneWaveSourceNode(base.MaxwellSimNode):
node_type = ct.NodeType.PlaneWaveSource node_type = ct.NodeType.PlaneWaveSource
bl_label = "Plane Wave Source" bl_label = "Plane Wave Source"
@ -20,79 +57,112 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
input_sockets = { input_sockets = {
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(), "Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
"Center": sockets.PhysicalPoint3DSocketDef(), "Center": sockets.PhysicalPoint3DSocketDef(),
"Direction": sockets.BoolSocketDef( "Direction": sockets.Real3DVectorSocketDef(
default_value=True, default_value=sp.Matrix([0, 0, -1])
), ),
"Pol": sockets.PhysicalPolSocketDef(), "Pol Angle": sockets.PhysicalAngleSocketDef(),
} }
output_sockets = { output_sockets = {
"Source": sockets.MaxwellSourceSocketDef(), "Source": sockets.MaxwellSourceSocketDef(),
} }
#################### managed_obj_defs = {
# - Properties "plane_wave_source": ct.schemas.ManagedObjDef(
#################### mk=lambda name: managed_objs.ManagedBLObject(name),
inj_axis: bpy.props.EnumProperty( name_prefix="",
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")),
)
#################### ####################
# - Output Socket Computation # - Output Socket Computation
#################### ####################
@base.computes_output_socket( @base.computes_output_socket(
"Source", "Source",
input_sockets={"Temporal Shape", "Center", "Direction", "Pol"}, input_sockets={"Temporal Shape", "Center", "Direction", "Pol Angle"},
props={"inj_axis"},
) )
def compute_source(self, input_sockets: dict, props: dict): def compute_source(self, input_sockets: dict):
temporal_shape = input_sockets["Temporal Shape"] temporal_shape = input_sockets["Temporal Shape"]
_center = input_sockets["Center"] _center = input_sockets["Center"]
_direction = input_sockets["Direction"] direction = input_sockets["Direction"]
_inj_axis = props["inj_axis"] pol_angle = input_sockets["Pol Angle"]
pol = input_sockets["Pol"]
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 = { size = {
"X": (0, math.inf, math.inf), "x": (0, math.inf, math.inf),
"Y": (math.inf, 0, math.inf), "y": (math.inf, 0, math.inf),
"Z": (math.inf, math.inf, 0), "z": (math.inf, math.inf, 0),
}[_inj_axis] }[injection_axis]
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
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
# Display the results # Display the results
return td.PlaneWave( return td.PlaneWave(
center=tuple(_center), center=center,
size=size,
source_time=temporal_shape, source_time=temporal_shape,
direction="+" if _direction else "-", size=size,
#angle_theta=angle_theta, direction=dir_sgn,
#angle_phi=angle_phi, angle_theta=theta,
#pol_angle=pol_angle, 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()
#################### ####################

View File

@ -15,6 +15,7 @@ ComplexNumberSocketDef = number.ComplexNumberSocketDef
from . import vector from . import vector
Real2DVectorSocketDef = vector.Real2DVectorSocketDef Real2DVectorSocketDef = vector.Real2DVectorSocketDef
Complex2DVectorSocketDef = vector.Complex2DVectorSocketDef Complex2DVectorSocketDef = vector.Complex2DVectorSocketDef
Integer3DVectorSocketDef = vector.Integer3DVectorSocketDef
Real3DVectorSocketDef = vector.Real3DVectorSocketDef Real3DVectorSocketDef = vector.Real3DVectorSocketDef
Complex3DVectorSocketDef = vector.Complex3DVectorSocketDef Complex3DVectorSocketDef = vector.Complex3DVectorSocketDef

View File

@ -24,6 +24,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
# Options # Options
#link_limit: int = 0 #link_limit: int = 0
use_units: bool = False use_units: bool = False
#list_like: bool = False
# Computed # Computed
bl_idname: str bl_idname: str

View File

@ -17,32 +17,6 @@ VAC_SPEED_OF_LIGHT = (
class MaxwellMonitorBLSocket(base.MaxwellSimSocket): class MaxwellMonitorBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellMonitor socket_type = ct.SocketType.MaxwellMonitor
bl_label = "Maxwell Monitor" 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 # - Socket Configuration

View File

@ -162,7 +162,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
"queued": "Task is queued for simulation", "queued": "Task is queued for simulation",
"preprocessing": "Task is pre-processing", "preprocessing": "Task is pre-processing",
"running": "Task is currently running", "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", "success": "Task ran successfully, costing {task.real_flex_unit} credits",
"error": "Task ran, but an error occurred", "error": "Task ran, but an error occurred",
}[task.status], }[task.status],
@ -174,7 +174,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
"queued": "SEQUENCE_COLOR_03", "queued": "SEQUENCE_COLOR_03",
"preprocessing": "SEQUENCE_COLOR_02", "preprocessing": "SEQUENCE_COLOR_02",
"running": "SEQUENCE_COLOR_05", "running": "SEQUENCE_COLOR_05",
"postprocessing": "SEQUENCE_COLOR_06", "postprocess": "SEQUENCE_COLOR_06",
"success": "SEQUENCE_COLOR_04", "success": "SEQUENCE_COLOR_04",
"error": "SEQUENCE_COLOR_01", "error": "SEQUENCE_COLOR_01",
}[task.status], }[task.status],

View File

@ -3,8 +3,10 @@ from . import complex_2d_vector
Real2DVectorSocketDef = real_2d_vector.Real2DVectorSocketDef Real2DVectorSocketDef = real_2d_vector.Real2DVectorSocketDef
Complex2DVectorSocketDef = complex_2d_vector.Complex2DVectorSocketDef Complex2DVectorSocketDef = complex_2d_vector.Complex2DVectorSocketDef
from . import integer_3d_vector
from . import real_3d_vector from . import real_3d_vector
from . import complex_3d_vector from . import complex_3d_vector
Integer3DVectorSocketDef = integer_3d_vector.Integer3DVectorSocketDef
Real3DVectorSocketDef = real_3d_vector.Real3DVectorSocketDef Real3DVectorSocketDef = real_3d_vector.Real3DVectorSocketDef
Complex3DVectorSocketDef = complex_3d_vector.Complex3DVectorSocketDef Complex3DVectorSocketDef = complex_3d_vector.Complex3DVectorSocketDef
@ -13,6 +15,7 @@ BL_REGISTER = [
*real_2d_vector.BL_REGISTER, *real_2d_vector.BL_REGISTER,
*complex_2d_vector.BL_REGISTER, *complex_2d_vector.BL_REGISTER,
*integer_3d_vector.BL_REGISTER,
*real_3d_vector.BL_REGISTER, *real_3d_vector.BL_REGISTER,
*complex_3d_vector.BL_REGISTER, *complex_3d_vector.BL_REGISTER,
] ]

View File

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

View File

@ -22,6 +22,13 @@ def get_units(expression: sp.Expr):
if isinstance(arg, spu.Quantity) if isinstance(arg, spu.Quantity)
} }
####################
# - Time
####################
femtosecond = fs = spu.Quantity("femtosecond", abbrev="fs")
femtosecond.set_global_relative_scale_factor(spu.femto, spu.second)
#################### ####################
# - Force # - Force
#################### ####################