From 9a148d7d97556675085ebfa3026be35dcc13bf6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sofus=20Albert=20H=C3=B8gsbro=20Rose?= Date: Tue, 12 Mar 2024 09:01:50 +0100 Subject: [PATCH] feat: Continue to add features. See README.md. --- README.md | 10 +- .../contracts/socket_colors.py | 2 + .../contracts/socket_shapes.py | 2 + .../contracts/socket_types.py | 2 + .../contracts/socket_units.py | 27 +-- .../maxwell_sim_nodes/nodes/__init__.py | 6 +- .../maxwell_sim_nodes/nodes/base.py | 22 ++- .../nodes/inputs/wave_constant.py | 6 +- .../nodes/monitors/__init__.py | 18 +- .../nodes/monitors/eh_field_monitor.py | 171 +++++++++++++++++- .../outputs/exporters/tidy3d_web_exporter.py | 4 +- .../nodes/simulations/fdtd_sim.py | 3 + .../nodes/simulations/sim_domain.py | 27 ++- .../nodes/sources/plane_wave_source.py | 170 ++++++++++++----- .../maxwell_sim_nodes/sockets/__init__.py | 1 + .../maxwell_sim_nodes/sockets/base.py | 1 + .../sockets/maxwell/monitor.py | 26 --- .../sockets/tidy3d/cloud_task.py | 4 +- .../sockets/vector/__init__.py | 3 + .../sockets/vector/integer_3d_vector.py | 70 +++++++ blender_maxwell/utils/extra_sympy_units.py | 7 + 21 files changed, 455 insertions(+), 127 deletions(-) create mode 100644 blender_maxwell/node_trees/maxwell_sim_nodes/sockets/vector/integer_3d_vector.py diff --git a/README.md b/README.md index e4d9abd..fbf5af1 100644 --- a/README.md +++ b/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 () - [ ] 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 diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_colors.py b/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_colors.py index e47e980..0a4eff1 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_colors.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_colors.py @@ -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 diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_shapes.py b/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_shapes.py index 56f578c..d1f8f7f 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_shapes.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_shapes.py @@ -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", diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_types.py b/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_types.py index 0cdd0b6..2d0e0a0 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_types.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_types.py @@ -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() diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_units.py b/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_units.py index 65b664a..cf58462 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_units.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/socket_units.py @@ -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: { diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py index 68312cb..2b924cf 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py @@ -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, } diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py index f3b5e2c..18aa31a 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py @@ -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, diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/wave_constant.py b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/wave_constant.py index 203e563..9e11e4c 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/wave_constant.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/wave_constant.py @@ -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, diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/__init__.py b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/__init__.py index 83bda0e..27ddb03 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/__init__.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/__init__.py @@ -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, } diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/eh_field_monitor.py b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/eh_field_monitor.py index 8f4a665..b1ff9d9 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/eh_field_monitor.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/eh_field_monitor.py @@ -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 + ) +} diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/exporters/tidy3d_web_exporter.py b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/exporters/tidy3d_web_exporter.py index fffd368..7867780 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/exporters/tidy3d_web_exporter.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/exporters/tidy3d_web_exporter.py @@ -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)] diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/fdtd_sim.py b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/fdtd_sim.py index 71fd520..9dde55c 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/fdtd_sim.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/fdtd_sim.py @@ -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, ) diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/sim_domain.py b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/sim_domain.py index 960fe02..968bf15 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/sim_domain.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/sim_domain.py @@ -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 diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/plane_wave_source.py b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/plane_wave_source.py index 999fb96..fc37fa2 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/plane_wave_source.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/plane_wave_source.py @@ -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() diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/__init__.py b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/__init__.py index e9df45c..f0fdac7 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/__init__.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/__init__.py @@ -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 diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/base.py b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/base.py index 1a5c567..5f7c9a6 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/base.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/base.py @@ -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 diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/monitor.py b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/monitor.py index 91dfa61..d88f142 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/monitor.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/monitor.py @@ -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 diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/tidy3d/cloud_task.py b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/tidy3d/cloud_task.py index cc57077..4bc0aac 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/tidy3d/cloud_task.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/tidy3d/cloud_task.py @@ -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], diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/vector/__init__.py b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/vector/__init__.py index f7ab146..45f1ccf 100644 --- a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/vector/__init__.py +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/vector/__init__.py @@ -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, ] diff --git a/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/vector/integer_3d_vector.py b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/vector/integer_3d_vector.py new file mode 100644 index 0000000..70ece6f --- /dev/null +++ b/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/vector/integer_3d_vector.py @@ -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, +] diff --git a/blender_maxwell/utils/extra_sympy_units.py b/blender_maxwell/utils/extra_sympy_units.py index c7225b6..8c491e5 100644 --- a/blender_maxwell/utils/extra_sympy_units.py +++ b/blender_maxwell/utils/extra_sympy_units.py @@ -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 ####################