diff --git a/TODO.md b/TODO.md index 83987cf..d12b276 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ # Working TODO - [x] Wave Constant - Sources - - [ ] Temporal Shapes / Continuous Wave Temporal Shape + - [x] Temporal Shapes / Continuous Wave Temporal Shape - [ ] Temporal Shapes / Symbolic Temporal Shape - [ ] Plane Wave Source - [ ] TFSF Source @@ -149,9 +149,10 @@ ## Sources - [x] Temporal Shapes / Gaussian Pulse Temporal Shape - [x] Temporal Shapes / Continuous Wave Temporal Shape + - [ ] Merge Gaussian Pulse and Continuous Wave w/a socket set thing, since the I/O is effectively identical. - [ ] Temporal Shapes / Expr Temporal Shape - - [ ] Specify a Sympy function / data to generate appropriate array based on -- [ ] Temporal Shapes / Data Temporal Shape + - [ ] Specify a Sympy function / data to generate envelope data. + - [ ] Merge with the above. - [x] Point Dipole Source - [ ] Use a viz mesh, not empty (empty doesn't play well with alpha hashing). diff --git a/src/blender_maxwell/assets/internal/source/_source_point_dipole.blend b/src/blender_maxwell/assets/internal/source/_source_point_dipole.blend new file mode 100644 index 0000000..d9f19b6 --- /dev/null +++ b/src/blender_maxwell/assets/internal/source/_source_point_dipole.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9435828adb096261d178d04f05df7778f6071a709f1dcfef1624aa435bc54dae +size 931285 diff --git a/src/blender_maxwell/contracts/operator_types.py b/src/blender_maxwell/contracts/operator_types.py index 998b312..6fc4f24 100644 --- a/src/blender_maxwell/contracts/operator_types.py +++ b/src/blender_maxwell/contracts/operator_types.py @@ -24,3 +24,10 @@ class OperatorType(enum.StrEnum): # Node: Tidy3DWebImporter NodeLoadCloudSim = enum.auto() + + # Node: Tidy3DWebExporter + NodeUploadSimulation = enum.auto() + NodeRunSimulation = enum.auto() + NodeReloadTrackedTask = enum.auto() + NodeEstCostTrackedTask = enum.auto() + ReleaseTrackedTask = enum.auto() diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py index db8ee16..17481e7 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py @@ -1,25 +1,25 @@ from blender_maxwell.contracts import ( - BLClass, - BLColorRGBA, - BLEnumElement, - BLEnumID, - BLIcon, - BLIconSet, - BLIDStruct, - BLKeymapItem, - BLModifierType, - BLNodeTreeInterfaceID, - BLOperatorStatus, - BLPropFlag, - BLRegionType, - BLSpaceType, - KeymapItemDef, - ManagedObjName, - OperatorType, - PanelType, - PresetName, - SocketName, - addon, + BLClass, + BLColorRGBA, + BLEnumElement, + BLEnumID, + BLIcon, + BLIconSet, + BLIDStruct, + BLKeymapItem, + BLModifierType, + BLNodeTreeInterfaceID, + BLOperatorStatus, + BLPropFlag, + BLRegionType, + BLSpaceType, + KeymapItemDef, + ManagedObjName, + OperatorType, + PanelType, + PresetName, + SocketName, + addon, ) from .bl_socket_types import BLSocketInfo, BLSocketType @@ -27,20 +27,20 @@ from .category_labels import NODE_CAT_LABELS from .category_types import NodeCategory from .flow_events import FlowEvent from .flow_kinds import ( - ArrayFlow, - CapabilitiesFlow, - FlowKind, - InfoFlow, - LazyArrayRangeFlow, - LazyValueFuncFlow, - ParamsFlow, - ValueFlow, + ArrayFlow, + CapabilitiesFlow, + FlowKind, + InfoFlow, + LazyArrayRangeFlow, + LazyValueFuncFlow, + ParamsFlow, + ValueFlow, ) from .flow_signals import FlowSignal from .icons import Icon from .mobj_types import ManagedObjType from .node_types import NodeType -from .sim_types import BoundCondType, SimSpaceAxis +from .sim_types import BoundCondType, SimSpaceAxis, manual_amp_time from .socket_colors import SOCKET_COLORS from .socket_types import SocketType from .tree_types import TreeType @@ -80,6 +80,7 @@ __all__ = [ 'NodeType', 'BoundCondType', 'SimSpaceAxis', + 'manual_amp_time', 'NodeCategory', 'NODE_CAT_LABELS', 'ManagedObjType', diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/bl_socket_types.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/bl_socket_types.py index d76eb26..ff01479 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/bl_socket_types.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/bl_socket_types.py @@ -5,11 +5,13 @@ import typing as typ import bpy import sympy as sp -from blender_maxwell.utils import blender_type_enum from blender_maxwell.utils import extra_sympy_units as spux +from blender_maxwell.utils import logger from .socket_types import SocketType +log = logger.get(__name__) + BL_SOCKET_DESCR_ANNOT_STRING = ':: ' @@ -190,13 +192,14 @@ class BLSocketType(enum.StrEnum): return { BLST.Color: S.Vec4, BLST.Rotation: S.Vec3, + BLST.Vector: S.Vec3, BLST.VectorAcceleration: S.Vec3, BLST.VectorDirection: S.Vec3, BLST.VectorEuler: S.Vec3, BLST.VectorTranslation: S.Vec3, BLST.VectorVelocity: S.Vec3, BLST.VectorXYZ: S.Vec3, - }.get(self, {S.Scalar}) + }.get(self, S.Scalar) @property def unambiguous_physical_type(self) -> spux.PhysicalType | None: diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/flow_kinds.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/flow_kinds.py index b342bf5..ec5bda9 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/flow_kinds.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/flow_kinds.py @@ -111,7 +111,10 @@ class CapabilitiesFlow: for name in self.must_match ) # ∀b (b ∈ A) Constraint - and self.present_any.issubset(other.allow_any) + and ( + self.present_any & other.allow_any + or (not self.present_any and not self.allow_any) + ) ) @@ -777,7 +780,7 @@ class ParamsFlow: raise ValueError(msg) return [ - spux.convert_to_unit_system(arg, unit_system, use_jax_array=True) + spux.scale_to_unit_system(arg, unit_system, use_jax_array=True) if arg not in symbol_values else symbol_values[arg] for arg in self.func_args diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py index 7c75789..0c328a3 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py @@ -3,11 +3,45 @@ import enum import typing as typ +import jax.numpy as jnp import tidy3d as td +def manual_amp_time(self, time: float) -> complex: + """Copied implementation of `pulse.amp_time` for `tidy3d` temporal shapes, which replaces use of `numpy` with `jax.numpy` for `jit`-ability. + + Since the function is detached from the method, `self` is not implicitly available. It should be pre-defined from a real source time object using `functools.partial`, before `jax.jit`ing. + + ## License + **This function is directly copied from `tidy3d`**. + As such, it should be considered available under the `tidy3d` license (as of writing, LGPL 2.1): + + ## Reference + Permalink to GitHub source code: + """ + twidth = 1.0 / (2 * jnp.pi * self.fwidth) + omega0 = 2 * jnp.pi * self.freq0 + time_shifted = time - self.offset * twidth + + offset = jnp.exp(1j * self.phase) + oscillation = jnp.exp(-1j * omega0 * time) + amp = jnp.exp(-(time_shifted**2) / 2 / twidth**2) * self.amplitude + + pulse_amp = offset * oscillation * amp + + # subtract out DC component + if self.remove_dc_component: + pulse_amp = pulse_amp * (1j + time_shifted / twidth**2 / omega0) + else: + # 1j to make it agree in large omega0 limit + pulse_amp = pulse_amp * 1j + + return pulse_amp + + ## TODO: Sim Domain type, w/pydantic checks! + class SimSpaceAxis(enum.StrEnum): """The axis labels of the global simulation coordinate system.""" @@ -61,13 +95,13 @@ class BoundCondType(enum.StrEnum): Attributes: Pml: "Perfectly Matched Layer" models infinite free space. **Should be placed sufficiently far** (ex. $\frac{\lambda}{2}) from any active structures to mitigate divergence. - Periodic: Denotes Bloch-basedrepetition + Periodic: Denotes naive Bloch boundaries (aka. periodic w/phase shift of 0). Pec: "Perfect Electrical Conductor" models a surface that perfectly reflects electric fields. Pmc: "Perfect Magnetic Conductor" models a surface that perfectly reflects the magnetic fields. """ Pml = enum.auto() - Periodic = enum.auto() + NaiveBloch = enum.auto() Pec = enum.auto() Pmc = enum.auto() @@ -86,7 +120,7 @@ class BoundCondType(enum.StrEnum): BCT.Pml: 'PML', BCT.Pec: 'PEC', BCT.Pmc: 'PMC', - BCT.Periodic: 'Periodic', + BCT.NaiveBloch: 'NaiveBloch', }[v] @staticmethod @@ -115,5 +149,5 @@ class BoundCondType(enum.StrEnum): BCT.Pml: td.PML(), BCT.Pec: td.PECBoundary(), BCT.Pmc: td.PMCBoundary(), - BCT.Periodic: td.Periodic(), + BCT.NaiveBloch: td.Periodic(), }[self] diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py index dd3ed23..7804655 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py @@ -5,10 +5,9 @@ from . import ( mediums, monitors, outputs, - # simulations, + simulations, sources, - # structures, - # utilities, + structures, ) BL_REGISTER = [ @@ -17,11 +16,10 @@ BL_REGISTER = [ *outputs.BL_REGISTER, *sources.BL_REGISTER, *mediums.BL_REGISTER, - # *structures.BL_REGISTER, + *structures.BL_REGISTER, *bounds.BL_REGISTER, *monitors.BL_REGISTER, - # *simulations.BL_REGISTER, - # *utilities.BL_REGISTER, + *simulations.BL_REGISTER, ] BL_NODES = { **analysis.BL_NODES, @@ -29,9 +27,8 @@ BL_NODES = { **outputs.BL_NODES, **sources.BL_NODES, **mediums.BL_NODES, - # **structures.BL_NODES, + **structures.BL_NODES, **bounds.BL_NODES, **monitors.BL_NODES, - # **simulations.BL_NODES, - # **utilities.BL_NODES, + **simulations.BL_NODES, } diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/extract_data.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/extract_data.py index b596953..0f55bda 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/extract_data.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/extract_data.py @@ -45,7 +45,7 @@ class ExtractDataNode(base.MaxwellSimNode): } output_socket_sets: typ.ClassVar = { 'Sim Data': {'Monitor Data': sockets.MaxwellMonitorDataSocketDef()}, - 'Monitor Data': {'Expr': sockets.ExprSocketDef()}, + 'Monitor Data': {'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array)}, } #################### @@ -409,7 +409,7 @@ class ExtractDataNode(base.MaxwellSimNode): #################### @events.computes_output_socket( # Trigger - 'Data', + 'Expr', kind=ct.FlowKind.Info, # Loaded props={'monitor_data_type', 'extract_filter'}, diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/filter_math.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/filter_math.py index 2df631f..09fa603 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/filter_math.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/filter_math.py @@ -110,10 +110,10 @@ class FilterMathNode(base.MaxwellSimNode): bl_label = 'Filter Math' input_sockets: typ.ClassVar = { - 'Expr': sockets.ExprSocketDef(), + 'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array), } output_sockets: typ.ClassVar = { - 'Expr': sockets.ExprSocketDef(), + 'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array), } #################### @@ -187,14 +187,13 @@ class FilterMathNode(base.MaxwellSimNode): def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None: layout.prop(self, self.blfields['operation'], text='') - if self.active_socket_set == 'Dimensions': - if self.operation in [FilterOperation.PinLen1, FilterOperation.Pin]: - layout.prop(self, self.blfields['dim_0'], text='') + if self.operation in [FilterOperation.PinLen1, FilterOperation.Pin]: + layout.prop(self, self.blfields['dim_0'], text='') - if self.operation == FilterOperation.Swap: - row = layout.row(align=True) - row.prop(self, self.blfields['dim_0'], text='') - row.prop(self, self.blfields['dim_1'], text='') + if self.operation == FilterOperation.Swap: + row = layout.row(align=True) + row.prop(self, self.blfields['dim_0'], text='') + row.prop(self, self.blfields['dim_1'], text='') #################### # - Events @@ -292,7 +291,7 @@ class FilterMathNode(base.MaxwellSimNode): return lazy_value_func.compose_within( operation.jax_func(axis_0, axis_1), - enclosing_func_args=[int] if operation == 'PIN' else [], + enclosing_func_args=[int] if operation == FilterOperation.Pin else [], supports_jax=True, ) return ct.FlowSignal.FlowPending @@ -383,7 +382,7 @@ class FilterMathNode(base.MaxwellSimNode): pinned_value = input_sockets['Value'] has_pinned_value = not ct.FlowSignal.check(pinned_value) - if props['operation'] == 'PIN' and has_pinned_value: + if props['operation'] == FilterOperation.Pin and has_pinned_value: nearest_idx_to_value = info.dim_idx[dim_0].nearest_idx_of( pinned_value, require_sorted=True ) diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/operate_math.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/operate_math.py index 6ddac23..58bb57c 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/operate_math.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/operate_math.py @@ -56,11 +56,11 @@ class OperateMathNode(base.MaxwellSimNode): bl_label = 'Operate Math' input_sockets: typ.ClassVar = { - 'Expr L': sockets.ExprSocketDef(), - 'Expr R': sockets.ExprSocketDef(), + 'Expr L': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array), + 'Expr R': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array), } output_sockets: typ.ClassVar = { - 'Expr': sockets.ExprSocketDef(), + 'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array), } #################### diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py index b99611a..a202cab 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py @@ -477,6 +477,7 @@ class MaxwellSimNode(bpy.types.Node): for socket_name, socket_def in created_sockets.items(): socket_def.preinit(all_bl_sockets[socket_name]) socket_def.init(all_bl_sockets[socket_name]) + socket_def.postinit(all_bl_sockets[socket_name]) def _sync_sockets(self) -> None: """Synchronize the node's sockets with the active sockets. diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py index d3d1a10..15787dc 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py @@ -208,7 +208,7 @@ class BlochBoundCondNode(base.MaxwellSimNode): return td.Periodic() # Source-Derived - if props['active_socket_set'] == 'Naive': + if props['active_socket_set'] == 'Source-Derived': sim_domain = input_sockets['Sim Domain'] valid_sim_axis = props['valid_sim_axis'] diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/mediums/library_medium.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/mediums/library_medium.py index 3e499a2..ac26478 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/mediums/library_medium.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/mediums/library_medium.py @@ -301,15 +301,16 @@ class LibraryMediumNode(base.MaxwellSimNode): #################### @events.on_show_plot( managed_objs={'plot'}, - props={'material'}, + props={'medium'}, stop_propagation=True, ) def on_show_plot( self, - managed_objs: dict, + managed_objs, + props, ): managed_objs['plot'].mpl_plot_to_image( - lambda ax: self.medium.plot(self.medium.frequency_range, ax=ax), + lambda ax: self.medium.plot(props['medium'].frequency_range, ax=ax), bl_select=True, ) ## TODO: Plot based on Wl, not freq. diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/__init__.py index 67fb705..287618d 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/__init__.py @@ -1,13 +1,13 @@ -#from . import file_exporters, viewer, web_exporters -from . import viewer +# from . import file_exporters, viewer, web_exporters +from . import viewer, web_exporters BL_REGISTER = [ *viewer.BL_REGISTER, - #*file_exporters.BL_REGISTER, - #*web_exporters.BL_REGISTER, + # *file_exporters.BL_REGISTER, + *web_exporters.BL_REGISTER, ] BL_NODES = { **viewer.BL_NODES, - #**file_exporters.BL_NODES, - #**web_exporters.BL_NODES, + # **file_exporters.BL_NODES, + **web_exporters.BL_NODES, } diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/web_exporters/tidy3d_web_exporter.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/web_exporters/tidy3d_web_exporter.py index f28c0d3..3d14627 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/web_exporters/tidy3d_web_exporter.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/outputs/web_exporters/tidy3d_web_exporter.py @@ -1,16 +1,23 @@ -import bpy +import typing as typ + +import bpy +import tidy3d as td + +from blender_maxwell.services import tdcloud +from blender_maxwell.utils import bl_cache, logger -from ......services import tdcloud from .... import contracts as ct from .... import sockets from ... import base, events +log = logger.get(__name__) + #################### # - Web Uploader / Loader / Runner / Releaser #################### class UploadSimulation(bpy.types.Operator): - bl_idname = 'blender_maxwell.nodes__upload_simulation' + bl_idname = ct.OperatorType.NodeUploadSimulation bl_label = 'Upload Tidy3D Simulation' bl_description = 'Upload the attached (locked) simulation, such that it is ready to run on the Tidy3D cloud' @@ -23,7 +30,7 @@ class UploadSimulation(bpy.types.Operator): and context.node.lock_tree and tdcloud.IS_AUTHENTICATED and not context.node.tracked_task_id - and context.node.inputs['FDTD Sim'].is_linked + and context.node.inputs['Sim'].is_linked ) def execute(self, context): @@ -33,7 +40,7 @@ class UploadSimulation(bpy.types.Operator): class RunSimulation(bpy.types.Operator): - bl_idname = 'blender_maxwell.nodes__run_simulation' + bl_idname = ct.OperatorType.NodeRunSimulation bl_label = 'Run Tracked Tidy3D Sim' bl_description = 'Run the currently tracked simulation task' @@ -61,7 +68,7 @@ class RunSimulation(bpy.types.Operator): class ReloadTrackedTask(bpy.types.Operator): - bl_idname = 'blender_maxwell.nodes__reload_tracked_task' + bl_idname = ct.OperatorType.NodeReloadTrackedTask bl_label = 'Reload Tracked Tidy3D Cloud Task' bl_description = 'Reload the currently tracked simulation task' @@ -86,7 +93,7 @@ class ReloadTrackedTask(bpy.types.Operator): class EstCostTrackedTask(bpy.types.Operator): - bl_idname = 'blender_maxwell.nodes__est_cost_tracked_task' + bl_idname = ct.OperatorType.NodeEstCostTrackedTask bl_label = 'Est Cost of Tracked Tidy3D Cloud Task' bl_description = 'Reload the currently tracked simulation task' @@ -113,7 +120,7 @@ class EstCostTrackedTask(bpy.types.Operator): class ReleaseTrackedTask(bpy.types.Operator): - bl_idname = 'blender_maxwell.nodes__release_tracked_task' + bl_idname = ct.OperatorType.ReleaseTrackedTask bl_label = 'Release Tracked Tidy3D Cloud Task' bl_description = 'Release the currently tracked simulation task' @@ -140,92 +147,60 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode): node_type = ct.NodeType.Tidy3DWebExporter bl_label = 'Tidy3D Web Exporter' - input_sockets = { - 'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(), + input_sockets: typ.ClassVar = { + 'Sim': sockets.MaxwellFDTDSimSocketDef(), 'Cloud Task': sockets.Tidy3DCloudTaskSocketDef( should_exist=False, ), } + output_sockets: typ.ClassVar = { + 'Sim Data': sockets.Tidy3DCloudTaskSocketDef( + should_exist=True, + ), + 'Cloud Task': sockets.Tidy3DCloudTaskSocketDef( + should_exist=True, + ), + } #################### # - Properties #################### - lock_tree: bpy.props.BoolProperty( - name='Whether to lock the attached tree', - description='Whether or not to lock the attached tree', - default=False, - update=lambda self, context: self.sync_lock_tree(context), - ) - tracked_task_id: bpy.props.StringProperty( - name='Tracked Task ID', - description='The currently tracked task ID', - default='', - update=lambda self, context: self.sync_tracked_task_id(context), - ) - - # Cache - cache_total_monitor_data: bpy.props.FloatProperty( - name='(Cached) Total Monitor Data', - description='Required storage space by all monitors', - default=0.0, - ) - cache_est_cost: bpy.props.FloatProperty( - name='(Cached) Estimated Total Cost', - description='Est. Cost in FlexCompute units', - default=-1.0, - ) + lock_tree: bool = bl_cache.BLField(False, prop_ui=True) + tracked_task_id: str = bl_cache.BLField('', prop_ui=True) #################### - # - Sync Methods + # - Computed #################### - def sync_lock_tree(self, context): - if self.lock_tree: - self.trigger_event(ct.FlowEvent.EnableLock) - self.locked = False - for bl_socket in self.inputs: - if bl_socket.name == 'FDTD Sim': - continue - bl_socket.locked = False + @bl_cache.cached_bl_property(persist=False) + def sim(self) -> td.Simulation | None: + sim = self._compute_input('Sim') + has_sim = not ct.FlowSignal.check(sim) - else: - self.trigger_event(ct.FlowEvent.DisableLock) + if has_sim: + sim.validate_pre_upload(source_required=True) + return sim + return None - self.on_prop_changed('lock_tree', context) + @bl_cache.cached_bl_property(persist=False) + def total_monitor_data(self) -> float: + if self.sim is not None: + return sum(self.sim.monitors_data_size.values()) + return 0.0 - def sync_tracked_task_id(self, context): - # Select Tracked Task - if self.tracked_task_id: - cloud_task = tdcloud.TidyCloudTasks.task(self.tracked_task_id) + @bl_cache.cached_bl_property(persist=False) + def est_cost(self) -> float | None: + if self.tracked_task_id != '': task_info = tdcloud.TidyCloudTasks.task_info(self.tracked_task_id) + if task_info is not None: + return task_info.cost_est() - self.loose_output_sockets = { - 'Cloud Task': sockets.Tidy3DCloudTaskSocketDef( - should_exist=True, - ), - } - self.inputs['Cloud Task'].locked = True - - # Release Tracked Task - else: - self.cache_est_cost = -1.0 - self.loose_output_sockets = {} - self.inputs['Cloud Task'].on_prepare_new_task() - self.inputs['Cloud Task'].locked = False - - self.on_prop_changed('tracked_task_id', context) + return None #################### - # - Output Socket Callbacks + # - Methods #################### - def validate_sim(self): - if (sim := self._compute_input('FDTD Sim')) is None: - msg = 'Tried to validate simulation, but none is attached' - raise ValueError(msg) - - sim.validate_pre_upload(source_required=True) - def upload_sim(self): - if (sim := self._compute_input('FDTD Sim')) is None: + if self.sim is None: msg = 'Tried to upload simulation, but none is attached' raise ValueError(msg) @@ -242,7 +217,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode): cloud_task = tdcloud.TidyCloudTasks.mk_task( task_name=new_task[0], cloud_folder=new_task[1], - sim=sim, + sim=self.sim, verbose=True, ) @@ -271,22 +246,24 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode): # Row: Upload Sim Buttons row = layout.row(align=True) row.operator( - UploadSimulation.bl_idname, + ct.OperatorType.NodeUploadSimulation, text='Upload', ) tree_lock_icon = 'LOCKED' if self.lock_tree else 'UNLOCKED' - row.prop(self, 'lock_tree', toggle=True, icon=tree_lock_icon, text='') + row.prop( + self, self.blfields['lock_tree'], toggle=True, icon=tree_lock_icon, text='' + ) # Row: Run Sim Buttons row = layout.row(align=True) row.operator( - RunSimulation.bl_idname, + ct.OperatorType.NodeRunSimulation, text='Run', ) if self.tracked_task_id: tree_lock_icon = 'LOOP_BACK' row.operator( - ReleaseTrackedTask.bl_idname, + ct.OperatorType.ReleaseTrackedTask, icon='LOOP_BACK', text='', ) @@ -313,7 +290,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode): col.label(icon=conn_icon) # Simulation Info - if self.inputs['FDTD Sim'].is_linked: + if self.inputs['Sim'].is_linked: row = layout.row() row.alignment = 'CENTER' row.label(text='Sim Info') @@ -327,7 +304,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode): ## Split: Right Column col = split.column(align=False) col.alignment = 'RIGHT' - col.label(text=f'{self.cache_total_monitor_data / 1_000_000:.2f}MB') + col.label(text=f'{self.total_monitor_data / 1_000_000:.2f}MB') # Cloud Task Info if self.tracked_task_id and tdcloud.IS_AUTHENTICATED: @@ -348,12 +325,12 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode): text=f'Status: {task_info.status.capitalize()}', ) row.operator( - ReloadTrackedTask.bl_idname, + ct.OperatorType.NodeReloadTrackedTask, text='', icon='FILE_REFRESH', ) row.operator( - EstCostTrackedTask.bl_idname, + ct.OperatorType.NodeEstCostTrackedTask, text='', icon='SORTTIME', ) @@ -369,9 +346,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode): col.label(text='Real Cost') ## Split: Right Column - cost_est = ( - f'{self.cache_est_cost:.2f}' if self.cache_est_cost >= 0 else 'TBD' - ) + cost_est = f'{self.est_cost:.2f}' if self.est_cost >= 0 else 'TBD' cost_real = ( f'{task_info.cost_real:.2f}' if task_info.cost_real is not None @@ -386,9 +361,37 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode): # Connection Information + #################### + # - Events + #################### + @events.on_value_changed(prop_name='lock_tree', props={'lock_tree'}) + def on_lock_tree_changed(self, props): + if props['lock_tree']: + self.trigger_event(ct.FlowEvent.EnableLock) + self.locked = False + for bl_socket in self.inputs: + if bl_socket.name == 'Sim': + continue + bl_socket.locked = False + + else: + self.trigger_event(ct.FlowEvent.DisableLock) + + @events.on_value_changed(prop_name='tracked_task_id', props={'tracked_task_id'}) + def on_tracked_task_id_changed(self, props): + if props['tracked_task_id']: + self.inputs['Cloud Task'].locked = True + + else: + self.total_monitor_data = bl_cache.Signal.InvalidateCache + self.est_cost = bl_cache.Signal.InvalidateCache + self.inputs['Cloud Task'].on_prepare_new_task() + self.inputs['Cloud Task'].locked = False + #################### # - Output Methods #################### + ## TODO: Retrieve simulation data if/when the simulation is done @events.computes_output_socket( 'Cloud Task', input_sockets={'Cloud Task'}, @@ -399,21 +402,6 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode): return None - #################### - # - Output Methods - #################### - @events.on_value_changed( - socket_name='FDTD Sim', - input_sockets={'FDTD Sim'}, - ) - def on_value_changed__fdtd_sim(self, input_sockets): - if (sim := self._compute_input('FDTD Sim')) is None: - self.cache_total_monitor_data = 0 - return - - sim.validate_pre_upload(source_required=True) - self.cache_total_monitor_data = sum(sim.monitors_data_size.values()) - #################### # - Blender Registration diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/__init__.py index b84eb0e..ba7cf05 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/__init__.py @@ -1,14 +1,16 @@ # from . import sim_grid # from . import sim_grid_axes -from . import fdtd_sim, sim_domain +from . import fdtd_sim, sim_domain, combine BL_REGISTER = [ + *combine.BL_REGISTER, *sim_domain.BL_REGISTER, # *sim_grid.BL_REGISTER, # *sim_grid_axes.BL_REGISTER, *fdtd_sim.BL_REGISTER, ] BL_NODES = { + **combine.BL_NODES, **sim_domain.BL_NODES, # **sim_grid.BL_NODES, # **sim_grid_axes.BL_NODES, diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/utilities/combine.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/combine.py similarity index 52% rename from src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/utilities/combine.py rename to src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/combine.py index 487390c..f8838df 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/utilities/combine.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/combine.py @@ -1,8 +1,9 @@ import typing as typ -import bpy import sympy as sp +from blender_maxwell.utils import bl_cache + from ... import contracts as ct from ... import sockets from .. import base, events @@ -19,24 +20,8 @@ class CombineNode(base.MaxwellSimNode): 'Maxwell Sources': {}, 'Maxwell Structures': {}, 'Maxwell Monitors': {}, - 'Real 3D Vector': {f'x_{i}': sockets.RealNumberSocketDef() for i in range(3)}, - # "Point 3D": { - # axis: sockets.PhysicalLengthSocketDef() - # for i, axis in zip( - # range(3), - # ["x", "y", "z"] - # ) - # }, - # "Size 3D": { - # axis_key: sockets.PhysicalLengthSocketDef() - # for i, axis_key, axis_label in zip( - # range(3), - # ["x_size", "y_size", "z_size"], - # ["X Size", "Y Size", "Z Size"], - # ) - # }, } - output_socket_sets = { + output_socket_sets: typ.ClassVar = { 'Maxwell Sources': { 'Sources': sockets.MaxwellSourceSocketDef( is_list=True, @@ -52,43 +37,69 @@ class CombineNode(base.MaxwellSimNode): is_list=True, ), }, - 'Real 3D Vector': { - 'Real 3D Vector': sockets.Real3DVectorSocketDef(), - }, - # "Point 3D": { - # "3D Point": sockets.PhysicalPoint3DSocketDef(), - # }, - # "Size 3D": { - # "3D Size": sockets.PhysicalSize3DSocketDef(), - # }, } - amount: bpy.props.IntProperty( - name='# Objects to Combine', - description='Amount of Objects to Combine', - default=1, - min=1, - # max=MAX_AMOUNT, - update=lambda self, context: self.on_prop_changed('amount', context), - ) + #################### + # - Draw + #################### + amount: int = bl_cache.BLField(2, abs_min=1, prop_ui=True) #################### # - Draw #################### def draw_props(self, context, layout): - layout.prop(self, 'amount', text='#') + layout.prop(self, self.blfields['amount'], text='') + + #################### + # - Events + #################### + @events.on_value_changed( + # Trigger + prop_name={'active_socket_set', 'amount'}, + props={'active_socket_set', 'amount'}, + run_on_init=True, + ) + def on_inputs_changed(self, props): + if props['active_socket_set'] == 'Maxwell Sources': + if ( + not self.loose_input_sockets + or not next(iter(self.loose_input_sockets)).startswith('Source') + or len(self.loose_input_sockets) != props['amount'] + ): + self.loose_input_sockets = { + f'Source #{i}': sockets.MaxwellSourceSocketDef() + for i in range(props['amount']) + } + + elif props['active_socket_set'] == 'Maxwell Structures': + if ( + not self.loose_input_sockets + or not next(iter(self.loose_input_sockets)).startswith('Structure') + or len(self.loose_input_sockets) != props['amount'] + ): + self.loose_input_sockets = { + f'Structure #{i}': sockets.MaxwellStructureSocketDef() + for i in range(props['amount']) + } + elif props['active_socket_set'] == 'Maxwell Monitors': + if ( + not self.loose_input_sockets + or not next(iter(self.loose_input_sockets)).startswith('Monitor') + or len(self.loose_input_sockets) != props['amount'] + ): + self.loose_input_sockets = { + f'Monitor #{i}': sockets.MaxwellMonitorSocketDef() + for i in range(props['amount']) + } + elif self.loose_input_sockets: + self.loose_input_sockets = {} #################### # - Output Socket Computation #################### - @events.computes_output_socket( - 'Real 3D Vector', input_sockets={'x_0', 'x_1', 'x_2'} - ) - def compute_real_3d_vector(self, input_sockets) -> sp.Expr: - return sp.Matrix([input_sockets[f'x_{i}'] for i in range(3)]) - @events.computes_output_socket( 'Sources', + kind=ct.FlowKind.Array, all_loose_input_sockets=True, props={'amount'}, ) @@ -97,6 +108,7 @@ class CombineNode(base.MaxwellSimNode): @events.computes_output_socket( 'Structures', + kind=ct.FlowKind.Array, all_loose_input_sockets=True, props={'amount'}, ) @@ -105,45 +117,13 @@ class CombineNode(base.MaxwellSimNode): @events.computes_output_socket( 'Monitors', + kind=ct.FlowKind.Array, all_loose_input_sockets=True, props={'amount'}, ) def compute_monitors(self, loose_input_sockets, props) -> sp.Expr: return [loose_input_sockets[f'Monitor #{i}'] for i in range(props['amount'])] - #################### - # - Input Socket Compilation - #################### - @events.on_value_changed( - prop_name='active_socket_set', - props={'active_socket_set', 'amount'}, - run_on_init=True, - ) - def on_value_changed__active_socket_set(self, props): - if props['active_socket_set'] == 'Maxwell Sources': - self.loose_input_sockets = { - f'Source #{i}': sockets.MaxwellSourceSocketDef() - for i in range(props['amount']) - } - elif props['active_socket_set'] == 'Maxwell Structures': - self.loose_input_sockets = { - f'Structure #{i}': sockets.MaxwellStructureSocketDef() - for i in range(props['amount']) - } - elif props['active_socket_set'] == 'Maxwell Monitors': - self.loose_input_sockets = { - f'Monitor #{i}': sockets.MaxwellMonitorSocketDef() - for i in range(props['amount']) - } - else: - self.loose_input_sockets = {} - - @events.on_value_changed( - prop_name='amount', - ) - def on_value_changed__amount(self): - self.on_value_changed__active_socket_set() - #################### # - Blender Registration @@ -151,4 +131,4 @@ class CombineNode(base.MaxwellSimNode): BL_REGISTER = [ CombineNode, ] -BL_NODES = {ct.NodeType.Combine: (ct.NodeCategory.MAXWELLSIM_UTILITIES)} +BL_NODES = {ct.NodeType.Combine: (ct.NodeCategory.MAXWELLSIM_SIMS)} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/fdtd_sim.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/fdtd_sim.py index 1dd43c8..25955da 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/fdtd_sim.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/fdtd_sim.py @@ -1,3 +1,5 @@ +import typing as typ + import sympy as sp import tidy3d as td @@ -13,9 +15,9 @@ class FDTDSimNode(base.MaxwellSimNode): #################### # - Sockets #################### - input_sockets = { - 'Domain': sockets.MaxwellSimDomainSocketDef(), + input_sockets: typ.ClassVar = { 'BCs': sockets.MaxwellBoundCondsSocketDef(), + 'Domain': sockets.MaxwellSimDomainSocketDef(), 'Sources': sockets.MaxwellSourceSocketDef( is_list=True, ), @@ -26,32 +28,33 @@ class FDTDSimNode(base.MaxwellSimNode): is_list=True, ), } - output_sockets = { - 'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(), + output_sockets: typ.ClassVar = { + 'Sim': sockets.MaxwellFDTDSimSocketDef(), } #################### # - Output Socket Computation #################### @events.computes_output_socket( - 'FDTD Sim', + 'Sim', kind=ct.FlowKind.Value, input_sockets={'Sources', 'Structures', 'Domain', 'BCs', 'Monitors'}, + input_socket_kinds={ + 'Sources': ct.FlowKind.Array, + 'Structures': ct.FlowKind.Array, + 'Domain': ct.FlowKind.Value, + 'BCs': ct.FlowKind.Value, + 'Monitors': ct.FlowKind.Array, + }, ) def compute_fdtd_sim(self, input_sockets: dict) -> sp.Expr: + ## TODO: Visualize the boundary conditions on top of the sim domain sim_domain = input_sockets['Domain'] sources = input_sockets['Sources'] structures = input_sockets['Structures'] bounds = input_sockets['BCs'] monitors = input_sockets['Monitors'] - # if not isinstance(sources, list): - # sources = [sources] - # if not isinstance(structures, list): - # structures = [structures] - # if not isinstance(monitors, list): - # monitors = [monitors] - return td.Simulation( **sim_domain, ## run_time=, size=, grid=, medium= structures=structures, diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/sim_domain.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/sim_domain.py index 326af10..a84a466 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/sim_domain.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/simulations/sim_domain.py @@ -4,11 +4,15 @@ import sympy as sp import sympy.physics.units as spu from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes +from blender_maxwell.utils import extra_sympy_units as spux +from blender_maxwell.utils import logger from ... import contracts as ct from ... import managed_objs, sockets from .. import base, events +log = logger.get(__name__) + class SimDomainNode(base.MaxwellSimNode): node_type = ct.NodeType.SimDomain @@ -16,12 +20,27 @@ class SimDomainNode(base.MaxwellSimNode): use_sim_node_name = True input_sockets: typ.ClassVar = { - 'Duration': sockets.PhysicalTimeSocketDef( - default_value=5 * spu.ps, - default_unit=spu.ps, + 'Duration': sockets.ExprSocketDef( + physical_type=spux.PhysicalType.Time, + default_unit=spu.picosecond, + default_value=5, + abs_min=0, + ), + 'Center': sockets.ExprSocketDef( + shape=(3,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_unit=spu.micrometer, + default_value=sp.Matrix([0, 0, 0]), + ), + 'Size': sockets.ExprSocketDef( + shape=(3,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_unit=spu.micrometer, + default_value=sp.Matrix([1, 1, 1]), + abs_min=0.001, ), - 'Center': sockets.PhysicalPoint3DSocketDef(), - 'Size': sockets.PhysicalSize3DSocketDef(), 'Grid': sockets.MaxwellSimGridSocketDef(), 'Ambient Medium': sockets.MaxwellMediumSocketDef(), } @@ -34,44 +53,6 @@ class SimDomainNode(base.MaxwellSimNode): 'modifier': managed_objs.ManagedBLModifier, } - #################### - # - Events - #################### - @events.on_value_changed( - socket_name={'Center', 'Size'}, - prop_name='preview_active', - run_on_init=True, - props={'preview_active'}, - input_sockets={'Center', 'Size'}, - managed_objs={'mesh', 'modifier'}, - unit_systems={'BlenderUnits': ct.UNITS_BLENDER}, - scale_input_sockets={ - 'Center': 'BlenderUnits', - }, - ) - def on_input_changed( - self, - props: dict, - managed_objs: dict, - input_sockets: dict, - unit_systems: dict, - ): - # Push Input Values to GeoNodes Modifier - managed_objs['modifier'].bl_modifier( - managed_objs['mesh'].bl_object(location=input_sockets['Center']), - 'NODES', - { - 'node_group': import_geonodes(GeoNodes.SimulationSimDomain), - 'unit_system': unit_systems['BlenderUnits'], - 'inputs': { - 'Size': input_sockets['Size'], - }, - }, - ) - # Push Preview State - if props['preview_active']: - managed_objs['mesh'].show_preview() - #################### # - Outputs #################### @@ -94,6 +75,59 @@ class SimDomainNode(base.MaxwellSimNode): 'medium': input_sockets['Ambient Medium'], } + #################### + # - Preview + #################### + @events.on_value_changed( + prop_name='preview_active', + run_on_init=True, + props={'preview_active'}, + managed_objs={'mesh'}, + ) + def on_preview_changed(self, props, managed_objs) -> None: + mesh = managed_objs['mesh'] + + # Push Preview State to Managed Mesh + if props['preview_active']: + mesh.show_preview() + else: + mesh.hide_preview() + + @events.on_value_changed( + ## Trigger + socket_name={'Center', 'Size'}, + run_on_init=True, + # Loaded + input_sockets={'Center', 'Size'}, + managed_objs={'mesh', 'modifier'}, + unit_systems={'BlenderUnits': ct.UNITS_BLENDER}, + scale_input_sockets={ + 'Center': 'BlenderUnits', + }, + ) + def on_input_changed( + self, + managed_objs, + input_sockets, + unit_systems, + ): + mesh = managed_objs['mesh'] + modifier = managed_objs['modifier'] + center = input_sockets['Center'] + size = input_sockets['Size'] + unit_system = unit_systems['BlenderUnits'] + + # Push Loose Input Values to GeoNodes Modifier + modifier.bl_modifier( + mesh.bl_object(location=center), + 'NODES', + { + 'node_group': import_geonodes(GeoNodes.SimulationSimDomain), + 'inputs': {'Size': size}, + 'unit_system': unit_system, + }, + ) + #################### # - Blender Registration diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/__init__.py index 5d14684..7f4c68d 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/__init__.py @@ -2,7 +2,7 @@ from . import ( # astigmatic_gaussian_beam_source, # gaussian_beam_source, # plane_wave_source, - # point_dipole_source, + point_dipole_source, temporal_shapes, # tfsf_source, # uniform_current_source, @@ -10,18 +10,18 @@ from . import ( BL_REGISTER = [ *temporal_shapes.BL_REGISTER, - #*point_dipole_source.BL_REGISTER, + *point_dipole_source.BL_REGISTER, # *uniform_current_source.BL_REGISTER, - #*plane_wave_source.BL_REGISTER, + # *plane_wave_source.BL_REGISTER, # *gaussian_beam_source.BL_REGISTER, # *astigmatic_gaussian_beam_source.BL_REGISTER, # *tfsf_source.BL_REGISTER, ] BL_NODES = { **temporal_shapes.BL_NODES, - #**point_dipole_source.BL_NODES, + **point_dipole_source.BL_NODES, # **uniform_current_source.BL_NODES, - #**plane_wave_source.BL_NODES, + # **plane_wave_source.BL_NODES, # **gaussian_beam_source.BL_NODES, # **astigmatic_gaussian_beam_source.BL_NODES, # **tfsf_source.BL_NODES, diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/point_dipole_source.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/point_dipole_source.py index 3cead0d..b4503a4 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/point_dipole_source.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/point_dipole_source.py @@ -1,64 +1,62 @@ import typing as typ import bpy +import sympy as sp import tidy3d as td +from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes +from blender_maxwell.utils import bl_cache, logger +from blender_maxwell.utils import extra_sympy_units as spux + from ... import contracts as ct from ... import managed_objs, sockets from .. import base, events +log = logger.get(__name__) + class PointDipoleSourceNode(base.MaxwellSimNode): node_type = ct.NodeType.PointDipoleSource bl_label = 'Point Dipole Source' + use_sim_node_name = True #################### # - Sockets #################### - input_sockets = { + input_sockets: typ.ClassVar = { 'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(), - 'Center': sockets.PhysicalPoint3DSocketDef(), + 'Center': sockets.ExprSocketDef( + shape=(3,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_value=sp.Matrix([0, 0, 0]), + ), 'Interpolate': sockets.BoolSocketDef( default_value=True, ), } - output_sockets = { + output_sockets: typ.ClassVar = { 'Source': sockets.MaxwellSourceSocketDef(), } - managed_obj_types = { + managed_obj_types: typ.ClassVar = { 'mesh': managed_objs.ManagedBLMesh, + 'modifier': managed_objs.ManagedBLModifier, } #################### # - Properties #################### - pol_axis: bpy.props.EnumProperty( - name='Polarization Axis', - description='Polarization Axis', - items=[ - ('EX', 'Ex', 'Electric field in x-dir'), - ('EY', 'Ey', 'Electric field in y-dir'), - ('EZ', 'Ez', 'Electric field in z-dir'), - ], - default='EX', - update=(lambda self, context: self.on_prop_changed('pol_axis', context)), - ) + pol_axis: ct.SimSpaceAxis = bl_cache.BLField(ct.SimSpaceAxis.X, prop_ui=True) #################### # - UI #################### - def draw_props(self, context, layout): - split = layout.split(factor=0.6) - - col = split.column() - col.label(text='Pol Axis') - - col = split.column() - col.prop(self, 'pol_axis', text='') + def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout): + layout.prop(self, self.blfields['pol_axis'], expand=True) #################### - # - Output Socket Computation + # - Outputs #################### @events.computes_output_socket( 'Source', @@ -76,9 +74,9 @@ class PointDipoleSourceNode(base.MaxwellSimNode): unit_systems: dict, ) -> td.PointDipole: pol_axis = { - 'EX': 'Ex', - 'EY': 'Ey', - 'EZ': 'Ez', + ct.SimSpaceAxis.X: 'Ex', + ct.SimSpaceAxis.Y: 'Ey', + ct.SimSpaceAxis.Z: 'Ez', }[props['pol_axis']] return td.PointDipole( @@ -88,39 +86,59 @@ class PointDipoleSourceNode(base.MaxwellSimNode): polarization=pol_axis, ) - ##################### - ## - Preview - ##################### - # @events.on_value_changed( - # socket_name='Center', - # input_sockets={'Center'}, - # managed_objs={'sphere_empty'}, - # ) - # def on_value_changed__center( - # self, - # input_sockets: dict, - # managed_objs: dict[str, ct.schemas.ManagedObj], - # ): - # _center = input_sockets['Center'] - # center = tuple(spu.convert_to(_center, spu.um) / spu.um) - # ## TODO: Preview unit system?? Presume um for now + #################### + # - Preview + #################### + @events.on_value_changed( + prop_name='preview_active', + run_on_init=True, + props={'preview_active'}, + managed_objs={'mesh'}, + ) + def on_preview_changed(self, props, managed_objs) -> None: + """Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen.""" + mesh = managed_objs['mesh'] - # mobj = managed_objs['sphere_empty'] - # bl_object = mobj.bl_object('EMPTY') - # bl_object.location = center # tuple([float(el) for el in center]) + # Push Preview State to Managed Mesh + if props['preview_active']: + mesh.show_preview() + else: + mesh.hide_preview() - # @events.on_show_preview( - # managed_objs={'sphere_empty'}, - # ) - # def on_show_preview( - # self, - # managed_objs: dict[str, ct.schemas.ManagedObj], - # ): - # managed_objs['sphere_empty'].show_preview( - # 'EMPTY', - # empty_display_type='SPHERE', - # ) - # managed_objs['sphere_empty'].bl_object('EMPTY').empty_display_size = 0.2 + @events.on_value_changed( + socket_name={'Center'}, + prop_name='pol_axis', + run_on_init=True, + # Pass Data + managed_objs={'mesh', 'modifier'}, + props={'pol_axis'}, + input_sockets={'Center'}, + unit_systems={'BlenderUnits': ct.UNITS_BLENDER}, + scale_input_sockets={'Center': 'BlenderUnits'}, + ) + def on_inputs_changed( + self, managed_objs, props, input_sockets, unit_systems + ) -> None: + mesh = managed_objs['mesh'] + modifier = managed_objs['modifier'] + center = input_sockets['Center'] + unit_system = unit_systems['BlenderUnits'] + axis = { + ct.SimSpaceAxis.X: 0, + ct.SimSpaceAxis.Y: 1, + ct.SimSpaceAxis.Z: 2, + }[props['pol_axis']] + + # Push Loose Input Values to GeoNodes Modifier + modifier.bl_modifier( + mesh.bl_object(location=center), + 'NODES', + { + 'node_group': import_geonodes(GeoNodes.SourcePointDipole), + 'inputs': {'Axis': axis}, + 'unit_system': unit_system, + }, + ) #################### diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/__init__.py index 0f32b5d..3b07e70 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/__init__.py @@ -1,13 +1,13 @@ # from . import expr_temporal_shape, pulse_temporal_shape, wave_temporal_shape -from . import pulse_temporal_shape +from . import pulse_temporal_shape, wave_temporal_shape BL_REGISTER = [ *pulse_temporal_shape.BL_REGISTER, - # *wave_temporal_shape.BL_REGISTER, + *wave_temporal_shape.BL_REGISTER, # *expr_temporal_shape.BL_REGISTER, ] BL_NODES = { **pulse_temporal_shape.BL_NODES, - # **wave_temporal_shape.BL_NODES, + **wave_temporal_shape.BL_NODES, # **expr_temporal_shape.BL_NODES, } diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/pulse_temporal_shape.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/pulse_temporal_shape.py index 267e6a1..2a5a6bb 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/pulse_temporal_shape.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/pulse_temporal_shape.py @@ -4,7 +4,6 @@ import functools import typing as typ import bpy -import jax.numpy as jnp import sympy as sp import sympy.physics.units as spu import tidy3d as td @@ -16,38 +15,6 @@ from .... import managed_objs, sockets from ... import base, events -def _manual_amp_time(self, time: float) -> complex: - """Copied implementation of `pulse.amp_time` for `tidy3d` temporal shapes, which replaces use of `numpy` with `jax.numpy` for `jit`-ability. - - Since the function is detached from the method, `self` is not implicitly available. It should be pre-defined from a real source time object using `functools.partial`, before `jax.jit`ing. - - ## License - **This function is directly copied from `tidy3d`**. - As such, it should be considered available under the `tidy3d` license (as of writing, LGPL 2.1): - - ## Reference - Permalink to GitHub source code: - """ - twidth = 1.0 / (2 * jnp.pi * self.fwidth) - omega0 = 2 * jnp.pi * self.freq0 - time_shifted = time - self.offset * twidth - - offset = jnp.exp(1j * self.phase) - oscillation = jnp.exp(-1j * omega0 * time) - amp = jnp.exp(-(time_shifted**2) / 2 / twidth**2) * self.amplitude - - pulse_amp = offset * oscillation * amp - - # subtract out DC component - if self.remove_dc_component: - pulse_amp = pulse_amp * (1j + time_shifted / twidth**2 / omega0) - else: - # 1j to make it agree in large omega0 limit - pulse_amp = pulse_amp * 1j - - return pulse_amp - - class PulseTemporalShapeNode(base.MaxwellSimNode): node_type = ct.NodeType.PulseTemporalShape bl_label = 'Gaussian Pulse Temporal Shape' @@ -145,7 +112,7 @@ class PulseTemporalShapeNode(base.MaxwellSimNode): ) def compute_time_to_efield_lazy(self, output_sockets) -> td.GaussianPulse: temporal_shape = output_sockets['Temporal Shape'] - jax_amp_time = functools.partial(_manual_amp_time, temporal_shape) + jax_amp_time = functools.partial(ct.manual_amp_time, temporal_shape) ## TODO: Don't just partial() it up, do it property in the ParamsFlow! ## -> Right now it's recompiled every time. diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/wave_temporal_shape.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/wave_temporal_shape.py index 669cbb7..58aaf86 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/wave_temporal_shape.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/temporal_shapes/wave_temporal_shape.py @@ -1,75 +1,156 @@ +"""Implements the `WaveTemporalShapeNode`.""" + +import functools +import typing as typ + +import bpy +import sympy as sp import sympy.physics.units as spu import tidy3d as td -from .... import contracts, sockets +from blender_maxwell.utils import extra_sympy_units as spux + +from .... import contracts as ct +from .... import managed_objs, sockets from ... import base, events -class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode): - node_type = contracts.NodeType.ContinuousWaveTemporalShape - +class WaveTemporalShapeNode(base.MaxwellSimNode): + node_type = ct.NodeType.WaveTemporalShape bl_label = 'Continuous Wave Temporal Shape' - # bl_icon = ... #################### # - Sockets #################### - input_sockets = { - # "amplitude": sockets.RealNumberSocketDef( - # label="Temporal Shape", - # ), ## Should have a unit of some kind... - 'phase': sockets.PhysicalAngleSocketDef( - label='Phase', + input_sockets: typ.ClassVar = { + 'max E': sockets.ExprSocketDef( + mathtype=spux.MathType.Complex, + physical_type=spux.PhysicalType.EField, + default_value=1 + 0j, ), - 'freq_center': sockets.PhysicalFreqSocketDef( - label='Freq Center', + 'μ Freq': sockets.ExprSocketDef( + physical_type=spux.PhysicalType.Freq, + default_unit=spux.THz, + default_value=500, ), - 'freq_std': sockets.PhysicalFreqSocketDef( - label='Freq STD', - ), - 'time_delay_rel_ang_freq': sockets.RealNumberSocketDef( - label='Time Delay rel. Ang. Freq', - default_value=5.0, + 'σ Freq': sockets.ExprSocketDef( + physical_type=spux.PhysicalType.Freq, + default_unit=spux.THz, + default_value=200, ), + 'Offset Time': sockets.ExprSocketDef(default_value=5, abs_min=2.5), } - output_sockets = { - 'temporal_shape': sockets.MaxwellTemporalShapeSocketDef( - label='Temporal Shape', - ), + output_sockets: typ.ClassVar = { + 'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(), + 'E(t)': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array), + } + + managed_obj_types: typ.ClassVar = { + 'plot': managed_objs.ManagedBLImage, } #################### - # - Output Socket Computation + # - UI #################### - @events.computes_output_socket('temporal_shape') - def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole: - _phase = self.compute_input('phase') - _freq_center = self.compute_input('freq_center') - _freq_std = self.compute_input('freq_std') - time_delay_rel_ang_freq = self.compute_input('time_delay_rel_ang_freq') + def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None: + box = layout.box() + row = box.row() + row.alignment = 'CENTER' + row.label(text='Parameter Scale') - cheating_amplitude = 1.0 - phase = spu.convert_to(_phase, spu.radian) / spu.radian - freq_center = spu.convert_to(_freq_center, spu.hertz) / spu.hertz - freq_std = spu.convert_to(_freq_std, spu.hertz) / spu.hertz + # Split + split = box.split(factor=0.3, align=False) + ## LHS: Parameter Names + col = split.column() + col.alignment = 'RIGHT' + col.label(text='Off t:') + + ## RHS: Parameter Units + col = split.column() + col.label(text='1 / 2π·σ(𝑓)') + + #################### + # - FlowKind: Value + #################### + @events.computes_output_socket( + 'Temporal Shape', + input_sockets={ + 'max E', + 'μ Freq', + 'σ Freq', + 'Offset Time', + }, + unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D}, + scale_input_sockets={ + 'max E': 'Tidy3DUnits', + 'μ Freq': 'Tidy3DUnits', + 'σ Freq': 'Tidy3DUnits', + }, + ) + def compute_temporal_shape(self, input_sockets, unit_systems) -> td.GaussianPulse: return td.ContinuousWave( - amplitude=cheating_amplitude, - phase=phase, - freq0=freq_center, - fwidth=freq_std, - offset=time_delay_rel_ang_freq, + amplitude=sp.re(input_sockets['max E']), + phase=sp.im(input_sockets['max E']), + freq0=input_sockets['μ Freq'], + fwidth=input_sockets['σ Freq'], + offset=input_sockets['Offset Time'], ) + #################### + # - FlowKind: LazyValueFunc / Info / Params + #################### + @events.computes_output_socket( + 'E(t)', + kind=ct.FlowKind.LazyValueFunc, + output_sockets={'Temporal Shape'}, + ) + def compute_time_to_efield_lazy(self, output_sockets) -> td.GaussianPulse: + temporal_shape = output_sockets['Temporal Shape'] + jax_amp_time = functools.partial(ct.manual_amp_time, temporal_shape) + + ## TODO: Don't just partial() it up, do it property in the ParamsFlow! + ## -> Right now it's recompiled every time. + + return ct.LazyValueFuncFlow( + func=jax_amp_time, + func_args=[spux.PhysicalType.Time], + supports_jax=True, + ) + + @events.computes_output_socket( + 'E(t)', + kind=ct.FlowKind.Info, + ) + def compute_time_to_efield_info(self) -> td.GaussianPulse: + return ct.InfoFlow( + dim_names=['t'], + dim_idx={ + 't': ct.LazyArrayRangeFlow( + start=sp.S(0), stop=sp.oo, steps=0, unit=spu.second + ) + }, + output_name='E', + output_shape=None, + output_mathtype=spux.MathType.Complex, + output_unit=spu.volt / spu.um, + ) + + @events.computes_output_socket( + 'E(t)', + kind=ct.FlowKind.Params, + ) + def compute_time_to_efield_params(self) -> td.GaussianPulse: + sym_time = sp.Symbol('t', real=True, nonnegative=True) + return ct.ParamsFlow(func_args=[sym_time], symbols={sym_time}) + #################### # - Blender Registration #################### BL_REGISTER = [ - ContinuousWaveTemporalShapeNode, + WaveTemporalShapeNode, ] BL_NODES = { - contracts.NodeType.ContinuousWaveTemporalShape: ( - contracts.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES - ) + ct.NodeType.WaveTemporalShape: (ct.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES) } diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/geonodes_structure.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/geonodes_structure.py index ce2cc68..8df4096 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/geonodes_structure.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/geonodes_structure.py @@ -1,8 +1,12 @@ import typing as typ +import sympy as sp +import sympy.physics.units as spu import tidy3d as td -from blender_maxwell.utils import bl_cache, logger +from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes +from blender_maxwell.utils import extra_sympy_units as spux +from blender_maxwell.utils import logger from ... import bl_socket_map, managed_objs, sockets from ... import contracts as ct @@ -22,7 +26,13 @@ class GeoNodesStructureNode(base.MaxwellSimNode): input_sockets: typ.ClassVar = { 'GeoNodes': sockets.BlenderGeoNodesSocketDef(), 'Medium': sockets.MaxwellMediumSocketDef(), - 'Center': sockets.PhysicalPoint3DSocketDef(), + 'Center': sockets.ExprSocketDef( + shape=(3,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_unit=spu.micrometer, + default_value=sp.Matrix([0, 0, 0]), + ), } output_sockets: typ.ClassVar = { 'Structure': sockets.MaxwellStructureSocketDef(), @@ -34,7 +44,7 @@ class GeoNodesStructureNode(base.MaxwellSimNode): } #################### - # - Output + # - Outputs #################### @events.computes_output_socket( 'Structure', @@ -43,14 +53,10 @@ class GeoNodesStructureNode(base.MaxwellSimNode): ) def compute_structure( self, - input_sockets: dict, - managed_objs: dict, + input_sockets, + managed_objs, ) -> td.Structure: """Computes a triangle-mesh based Tidy3D structure, by manually copying mesh data from Blender to a `td.TriangleMesh`.""" - # Simulate Input Value Change - ## This ensures that the mesh has been re-computed. - self.on_input_socket_changed() - ## TODO: mesh_as_arrays might not take the Center into account. ## - Alternatively, Tidy3D might have a way to transform? mesh_as_arrays = managed_objs['mesh'].mesh_as_arrays @@ -68,20 +74,12 @@ class GeoNodesStructureNode(base.MaxwellSimNode): @events.on_value_changed( prop_name='preview_active', props={'preview_active'}, - input_sockets={'Center'}, managed_objs={'mesh'}, ) - def on_preview_changed(self, props, input_sockets) -> None: + def on_preview_changed(self, props) -> None: """Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen.""" mesh = managed_objs['mesh'] - # No Mesh: Create Empty Object - ## Ensures that when there is mesh data, it'll be correctly previewed. - ## Bit of a workaround - the idea is usually to make the MObj as needed. - if not mesh.exists: - center = input_sockets['Center'] - _ = mesh.bl_object(location=center) - # Push Preview State to Managed Mesh if props['preview_active']: mesh.show_preview() @@ -139,8 +137,8 @@ class GeoNodesStructureNode(base.MaxwellSimNode): ) def on_input_changed( self, - managed_objs: dict, - input_sockets: dict, + managed_objs, + input_sockets, ) -> None: """Declares new loose input sockets in response to a new GeoNodes tree (if any).""" geonodes = input_sockets['GeoNodes'] diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/primitives/box_structure.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/primitives/box_structure.py index c1e37be..64536b9 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/primitives/box_structure.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/primitives/box_structure.py @@ -5,11 +5,15 @@ import sympy.physics.units as spu import tidy3d as td from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes +from blender_maxwell.utils import extra_sympy_units as spux +from blender_maxwell.utils import logger from .... import contracts as ct from .... import managed_objs, sockets from ... import base, events +log = logger.get(__name__) + class BoxStructureNode(base.MaxwellSimNode): node_type = ct.NodeType.BoxStructure @@ -21,9 +25,20 @@ class BoxStructureNode(base.MaxwellSimNode): #################### input_sockets: typ.ClassVar = { 'Medium': sockets.MaxwellMediumSocketDef(), - 'Center': sockets.PhysicalPoint3DSocketDef(), - 'Size': sockets.PhysicalSize3DSocketDef( - default_value=sp.Matrix([500, 500, 500]) * spu.nm + 'Center': sockets.ExprSocketDef( + shape=(3,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_unit=spu.micrometer, + default_value=sp.Matrix([0, 0, 0]), + ), + 'Size': sockets.ExprSocketDef( + shape=(3,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_unit=spu.nanometer, + default_value=sp.Matrix([500, 500, 500]), + abs_min=0.001, ), } output_sockets: typ.ClassVar = { @@ -36,7 +51,7 @@ class BoxStructureNode(base.MaxwellSimNode): } #################### - # - Event Methods + # - Outputs #################### @events.computes_output_socket( 'Structure', @@ -47,7 +62,7 @@ class BoxStructureNode(base.MaxwellSimNode): 'Size': 'Tidy3DUnits', }, ) - def compute_structure(self, input_sockets: dict, unit_systems: dict) -> td.Box: + def compute_structure(self, input_sockets, unit_systems) -> td.Box: return td.Structure( geometry=td.Box( center=input_sockets['Center'], @@ -56,11 +71,27 @@ class BoxStructureNode(base.MaxwellSimNode): medium=input_sockets['Medium'], ) + #################### + # - Preview + #################### @events.on_value_changed( - socket_name={'Center', 'Size'}, prop_name='preview_active', run_on_init=True, props={'preview_active'}, + managed_objs={'mesh'}, + ) + def on_preview_changed(self, props, managed_objs) -> None: + mesh = managed_objs['mesh'] + + # Push Preview State to Managed Mesh + if props['preview_active']: + mesh.show_preview() + else: + mesh.hide_preview() + + @events.on_value_changed( + socket_name={'Center', 'Size'}, + run_on_init=True, input_sockets={'Center', 'Size'}, managed_objs={'mesh', 'modifier'}, unit_systems={'BlenderUnits': ct.UNITS_BLENDER}, @@ -70,26 +101,26 @@ class BoxStructureNode(base.MaxwellSimNode): ) def on_inputs_changed( self, - props: dict, - managed_objs: dict, - input_sockets: dict, - unit_systems: dict, + managed_objs, + input_sockets, + unit_systems, ): - # Push Input Values to GeoNodes Modifier - managed_objs['modifier'].bl_modifier( - managed_objs['mesh'].bl_object(location=input_sockets['Center']), + mesh = managed_objs['mesh'] + modifier = managed_objs['modifier'] + center = input_sockets['Center'] + size = input_sockets['Size'] + unit_system = unit_systems['BlenderUnits'] + + # Push Loose Input Values to GeoNodes Modifier + modifier.bl_modifier( + mesh.bl_object(location=center), 'NODES', { 'node_group': import_geonodes(GeoNodes.StructurePrimitiveBox), - 'unit_system': unit_systems['BlenderUnits'], - 'inputs': { - 'Size': input_sockets['Size'], - }, + 'inputs': {'Size': size}, + 'unit_system': unit_system, }, ) - # Push Preview State - if props['preview_active']: - managed_objs['mesh'].show_preview() #################### diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/primitives/sphere_structure.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/primitives/sphere_structure.py index 8363045..0f56190 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/primitives/sphere_structure.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/structures/primitives/sphere_structure.py @@ -1,14 +1,19 @@ import typing as typ +import sympy as sp import sympy.physics.units as spu import tidy3d as td from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes +from blender_maxwell.utils import extra_sympy_units as spux +from blender_maxwell.utils import logger from .... import contracts as ct from .... import managed_objs, sockets from ... import base, events +log = logger.get(__name__) + class SphereStructureNode(base.MaxwellSimNode): node_type = ct.NodeType.SphereStructure @@ -20,9 +25,17 @@ class SphereStructureNode(base.MaxwellSimNode): #################### input_sockets: typ.ClassVar = { 'Medium': sockets.MaxwellMediumSocketDef(), - 'Center': sockets.PhysicalPoint3DSocketDef(), - 'Radius': sockets.PhysicalLengthSocketDef( - default_value=150 * spu.nm, + 'Center': sockets.ExprSocketDef( + shape=(3,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_unit=spu.micrometer, + default_value=sp.Matrix([0, 0, 0]), + ), + 'Radius': sockets.ExprSocketDef( + physical_type=spux.PhysicalType.Length, + default_unit=spu.nanometer, + default_value=150, ), } output_sockets: typ.ClassVar = { @@ -35,7 +48,7 @@ class SphereStructureNode(base.MaxwellSimNode): } #################### - # - Output Socket Computation + # - Outputs #################### @events.computes_output_socket( 'Structure', @@ -46,7 +59,7 @@ class SphereStructureNode(base.MaxwellSimNode): 'Radius': 'Tidy3DUnits', }, ) - def compute_structure(self, input_sockets: dict) -> td.Box: + def compute_structure(self, input_sockets, unit_systems) -> td.Box: return td.Structure( geometry=td.Sphere( radius=input_sockets['Radius'], @@ -56,43 +69,55 @@ class SphereStructureNode(base.MaxwellSimNode): ) #################### - # - Preview - Changes to Input Sockets + # - Preview #################### @events.on_value_changed( - socket_name={'Center', 'Radius'}, prop_name='preview_active', run_on_init=True, props={'preview_active'}, + managed_objs={'mesh'}, + ) + def on_preview_changed(self, props, managed_objs) -> None: + mesh = managed_objs['mesh'] + + # Push Preview State to Managed Mesh + if props['preview_active']: + mesh.show_preview() + else: + mesh.hide_preview() + + @events.on_value_changed( + socket_name={'Center', 'Radius'}, + run_on_init=True, input_sockets={'Center', 'Radius'}, managed_objs={'mesh', 'modifier'}, unit_systems={'BlenderUnits': ct.UNITS_BLENDER}, scale_input_sockets={ - 'Center': 'Tidy3DUnits', - 'Radius': 'Tidy3DUnits', + 'Center': 'BlenderUnits', }, ) def on_inputs_changed( self, - props: dict, - managed_objs: dict, - input_sockets: dict, - unit_systems: dict, + managed_objs, + input_sockets, + unit_systems, ): - # Push Input Values to GeoNodes Modifier - managed_objs['modifier'].bl_modifier( - managed_objs['mesh'].bl_object(location=input_sockets['Center']), + mesh = managed_objs['mesh'] + modifier = managed_objs['modifier'] + center = input_sockets['Center'] + radius = input_sockets['Radius'] + unit_system = unit_systems['BlenderUnits'] + + # Push Loose Input Values to GeoNodes Modifier + modifier.bl_modifier( + mesh.bl_object(location=center), 'NODES', { 'node_group': import_geonodes(GeoNodes.StructurePrimitiveSphere), - 'unit_system': unit_systems['BlenderUnits'], - 'inputs': { - 'Radius': input_sockets['Radius'], - }, + 'inputs': {'Radius': radius}, + 'unit_system': unit_system, }, ) - # Push Preview State - if props['preview_active']: - managed_objs['mesh'].show_preview() #################### diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/utilities/math.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/utilities/math.py deleted file mode 100644 index 41fac16..0000000 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/utilities/math.py +++ /dev/null @@ -1,5 +0,0 @@ -#################### -# - Blender Registration -#################### -BL_REGISTER = [] -BL_NODES = {} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/utilities/separate.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/utilities/separate.py deleted file mode 100644 index dc824fd..0000000 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/utilities/separate.py +++ /dev/null @@ -1,80 +0,0 @@ -import scipy as sc -import sympy as sp -import sympy.physics.units as spu - -from .... import contracts, sockets -from ... import base, events - - -class WaveConverterNode(base.MaxwellSimTreeNode): - node_type = contracts.NodeType.WaveConverter - bl_label = 'Wave Converter' - # bl_icon = ... - - #################### - # - Sockets - #################### - input_sockets = {} - input_socket_sets = { - 'freq_to_vacwl': { - 'freq': sockets.PhysicalFreqSocketDef( - label='Freq', - ), - }, - 'vacwl_to_freq': { - 'vacwl': sockets.PhysicalVacWLSocketDef( - label='Vac WL', - ), - }, - } - output_sockets = {} - output_socket_sets = { - 'freq_to_vacwl': { - 'vacwl': sockets.PhysicalVacWLSocketDef( - label='Vac WL', - ), - }, - 'vacwl_to_freq': { - 'freq': sockets.PhysicalFreqSocketDef( - label='Freq', - ), - }, - } - - #################### - # - Output Socket Computation - #################### - @events.computes_output_socket('freq') - def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr: - vac_speed_of_light = sc.constants.speed_of_light * spu.meter / spu.second - - vacwl = self.compute_input('vacwl') - - return spu.convert_to( - vac_speed_of_light / vacwl, - spu.hertz, - ) - - @events.computes_output_socket('vacwl') - def compute_vacwl(self: contracts.NodeTypeProtocol) -> sp.Expr: - vac_speed_of_light = sc.constants.speed_of_light * spu.meter / spu.second - - freq = self.compute_input('freq') - - return spu.convert_to( - vac_speed_of_light / freq, - spu.meter, - ) - - -#################### -# - Blender Registration -#################### -BL_REGISTER = [ - WaveConverterNode, -] -BL_NODES = { - contracts.NodeType.WaveConverter: ( - contracts.NodeCategory.MAXWELLSIM_UTILITIES_CONVERTERS - ) -} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/base.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/base.py index 8523da8..7608ae2 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/base.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/base.py @@ -38,6 +38,14 @@ class SocketDef(pyd.BaseModel, abc.ABC): """ bl_socket.reset_instance_id() + def postinit(self, bl_socket: bpy.types.NodeSocket) -> None: + """Pre-initialize a real Blender node socket from this socket definition. + + Parameters: + bl_socket: The Blender node socket to alter using data from this SocketDef. + """ + bl_socket.initializing = False + @abc.abstractmethod def init(self, bl_socket: bpy.types.NodeSocket) -> None: """Initializes a real Blender node socket from this socket definition. @@ -195,6 +203,9 @@ class MaxwellSimSocket(bpy.types.NodeSocket): ## Identifiers cls.bl_idname: str = str(cls.socket_type.value) cls.set_prop('instance_id', bpy.props.StringProperty, no_update=True) + cls.set_prop( + 'initializing', bpy.props.BoolProperty, default=True, no_update=True + ) ## Special States cls.set_prop('locked', bpy.props.BoolProperty, no_update=True, default=False) @@ -249,6 +260,10 @@ class MaxwellSimSocket(bpy.types.NodeSocket): Attributes: prop_name: The name of the property that was changed. """ + ## TODO: Evaluate this properly + if self.initializing: + return + if hasattr(self, prop_name): # Invalidate UI BLField Caches if prop_name in self.ui_blfields: @@ -537,7 +552,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket): msg = f'Socket {self.bl_label} {self.socket_type}): Tried to set "ct.FlowKind.Value", but socket does not define it' raise NotImplementedError(msg) - # ValueArray + # Array @property def array(self) -> ct.ArrayFlow: """Throws a descriptive error. diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/fdtd_sim.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/fdtd_sim.py index 283bd98..455514a 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/fdtd_sim.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/fdtd_sim.py @@ -1,4 +1,3 @@ - from ... import contracts as ct from .. import base diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/structure.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/structure.py index 40f0fc2..3f2a206 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/structure.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/structure.py @@ -1,4 +1,3 @@ - from ... import contracts as ct from .. import base @@ -18,7 +17,7 @@ class MaxwellStructureSocketDef(base.SocketDef): def init(self, bl_socket: MaxwellStructureBLSocket) -> None: if self.is_list: - bl_socket.active_kind = ct.FlowKind.ValueArray + bl_socket.active_kind = ct.FlowKind.Array ####################