feat: Continue to add features.

See README.md.
blender-plugin-mvp
Sofus Albert Høgsbro Rose 2024-03-12 09:01:50 +01:00
parent 134bf0c358
commit 1e420376fc
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
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.
- [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

View File

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

View File

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

View File

@ -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()

View File

@ -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: {

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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,13 +98,16 @@ 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...
## - ...But it needs aligning with Blender, too. Hmm.
}
)
# 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

View File

@ -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,78 +57,111 @@ 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]
"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)
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
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()

View File

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

View File

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

View File

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

View File

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

View File

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

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)
}
####################
# - Time
####################
femtosecond = fs = spu.Quantity("femtosecond", abbrev="fs")
femtosecond.set_global_relative_scale_factor(spu.femto, spu.second)
####################
# - Force
####################