From 929fb2dae92e92544b157c2a7262aeecb45d857e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sofus=20Albert=20H=C3=B8gsbro=20Rose?= Date: Tue, 7 May 2024 12:39:17 +0200 Subject: [PATCH] feat: implemented `GaussianBeam` source This node is good for approximating a simple laser. --- .../source/_source_gaussian_beam.blend | 3 + .../internal/source/_source_plane_wave.blend | 4 +- .../managed_objs/managed_bl_mesh.py | 1 + .../nodes/sources/__init__.py | 10 +- .../nodes/sources/gaussian_beam_source.py | 235 +++++++++++++++++- .../nodes/sources/plane_wave_source.py | 12 + 6 files changed, 256 insertions(+), 9 deletions(-) create mode 100644 src/blender_maxwell/assets/internal/source/_source_gaussian_beam.blend diff --git a/src/blender_maxwell/assets/internal/source/_source_gaussian_beam.blend b/src/blender_maxwell/assets/internal/source/_source_gaussian_beam.blend new file mode 100644 index 0000000..f10bd2d --- /dev/null +++ b/src/blender_maxwell/assets/internal/source/_source_gaussian_beam.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e10949ae762da334781224d3a48d19b3dc6bc1b44f31ab062da2930cb6312139 +size 1564739 diff --git a/src/blender_maxwell/assets/internal/source/_source_plane_wave.blend b/src/blender_maxwell/assets/internal/source/_source_plane_wave.blend index 0214a29..3ae77dc 100644 --- a/src/blender_maxwell/assets/internal/source/_source_plane_wave.blend +++ b/src/blender_maxwell/assets/internal/source/_source_plane_wave.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb4bb76ea45a8a072a7a7792af0911a9d11483163a0e1aa8486a27eb67bf05a9 -size 1939925 +oid sha256:faf56bee30a9832b850767d620e9a430eef04a80f879ae84239d71391d0b3f61 +size 1484900 diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_mesh.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_mesh.py index 66649d7..89e6f35 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_mesh.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_mesh.py @@ -128,6 +128,7 @@ class ManagedBLMesh(base.ManagedObj): """ bl_object = bpy.data.objects.get(self.name) if bl_object is None: + log.info('Created previewable ManagedBLMesh "%s"', bl_object.name) bl_object = self.bl_object() if bl_object.name not in preview_collection().objects: 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 9381487..6a83d80 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 @@ -16,7 +16,7 @@ from . import ( # astigmatic_gaussian_beam_source, - # gaussian_beam_source, + gaussian_beam_source, plane_wave_source, point_dipole_source, temporal_shapes, @@ -24,19 +24,19 @@ from . import ( BL_REGISTER = [ *temporal_shapes.BL_REGISTER, + *plane_wave_source.BL_REGISTER, *point_dipole_source.BL_REGISTER, # *uniform_current_source.BL_REGISTER, - *plane_wave_source.BL_REGISTER, - # *gaussian_beam_source.BL_REGISTER, + *gaussian_beam_source.BL_REGISTER, # *astigmatic_gaussian_beam_source.BL_REGISTER, # *tfsf_source.BL_REGISTER, ] BL_NODES = { **temporal_shapes.BL_NODES, + **plane_wave_source.BL_NODES, **point_dipole_source.BL_NODES, # **uniform_current_source.BL_NODES, - **plane_wave_source.BL_NODES, - # **gaussian_beam_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/gaussian_beam_source.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/gaussian_beam_source.py index 5b7d824..fc72c76 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/gaussian_beam_source.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/gaussian_beam_source.py @@ -14,8 +14,239 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +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 GaussianBeamSourceNode(base.MaxwellSimNode): + """A finite-extent angled source simulating a wave produced by lensed fibers, with focal properties similar to that of ideal lasers, and linear polarization. + + The amplitude envelope is a gaussian function, and the complex electric field is a well-defined frequency-dependent phasor with very few shape parameters. + + The only critical shape parameter is the **waist**: At a chosen "focus" distance, the width of the beam has a chosen radius. + These properties are called "waist distance" and "waist radius". + At all other points, the width of the beam has a well-defined hyperbolic relationship to the waist, when given the IOR-dependent Rayleigh length. + + - Tidy3D Documentation: + - Mathematical Formalism: + """ + + node_type = ct.NodeType.GaussianBeamSource + bl_label = 'Gaussian Beam Source' + use_sim_node_name = True + + #################### + # - Sockets + #################### + input_sockets: typ.ClassVar = { + 'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(), + 'Center': sockets.ExprSocketDef( + shape=(3,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_value=sp.Matrix([0, 0, 0]), + ), + 'Size': sockets.ExprSocketDef( + shape=(2,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_value=sp.Matrix([1, 1]), + ), + 'Waist Dist': sockets.ExprSocketDef( + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_value=0.0, + ), + 'Waist Radius': sockets.ExprSocketDef( + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Length, + default_value=1.0, + abs_min=0.01, + ), + 'Spherical': sockets.ExprSocketDef( + shape=(2,), + mathtype=spux.MathType.Real, + physical_type=spux.PhysicalType.Angle, + default_value=sp.Matrix([0, 0]), + ), + 'Pol ∡': sockets.ExprSocketDef( + physical_type=spux.PhysicalType.Angle, + default_value=0, + ), + } + output_sockets: typ.ClassVar = { + 'Angled Source': sockets.MaxwellSourceSocketDef(), + } + + managed_obj_types: typ.ClassVar = { + 'mesh': managed_objs.ManagedBLMesh, + 'modifier': managed_objs.ManagedBLModifier, + } + + #################### + # - Properties + #################### + injection_axis: ct.SimSpaceAxis = bl_cache.BLField(ct.SimSpaceAxis.X, prop_ui=True) + injection_direction: ct.SimAxisDir = bl_cache.BLField( + ct.SimAxisDir.Plus, prop_ui=True + ) + num_freqs: int = bl_cache.BLField(1, abs_min=1, soft_max=20, prop_ui=True) + + #################### + # - UI + #################### + def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout): + layout.prop(self, self.blfields['injection_axis'], expand=True) + layout.prop(self, self.blfields['injection_direction'], expand=True) + # layout.prop(self, self.blfields['num_freqs'], text='f Points') + ## TODO: UI is a bit crowded already! + + #################### + # - Outputs + #################### + @events.computes_output_socket( + 'Angled Source', + props={'sim_node_name', 'injection_axis', 'injection_direction', 'num_freqs'}, + input_sockets={ + 'Temporal Shape', + 'Center', + 'Size', + 'Waist Dist', + 'Waist Radius', + 'Spherical', + 'Pol ∡', + }, + unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D}, + scale_input_sockets={ + 'Center': 'Tidy3DUnits', + 'Size': 'Tidy3DUnits', + 'Waist Dist': 'Tidy3DUnits', + 'Waist Radius': 'Tidy3DUnits', + 'Spherical': 'Tidy3DUnits', + 'Pol ∡': 'Tidy3DUnits', + }, + ) + def compute_source(self, props, input_sockets, unit_systems): + size_2d = input_sockets['Size'] + size = { + ct.SimSpaceAxis.X: (0, *size_2d), + ct.SimSpaceAxis.Y: (size_2d[0], 0, size_2d[1]), + ct.SimSpaceAxis.Z: (*size_2d, 0), + }[props['injection_axis']] + + # Display the results + return td.GaussianBeam( + name=props['sim_node_name'], + center=input_sockets['Center'], + size=size, + source_time=input_sockets['Temporal Shape'], + num_freqs=props['num_freqs'], + direction=props['injection_direction'].plus_or_minus, + angle_theta=input_sockets['Spherical'][0], + angle_phi=input_sockets['Spherical'][1], + pol_angle=input_sockets['Pol ∡'], + waist_radius=input_sockets['Waist Radius'], + waist_distance=input_sockets['Waist Dist'], + ## NOTE: Waist is place at this signed dist along neg. direction + ) + + #################### + # - Preview - Changes to Input Sockets + #################### + @events.on_value_changed( + # Trigger + prop_name='preview_active', + # Loaded + managed_objs={'mesh'}, + props={'preview_active'}, + ) + def on_preview_changed(self, managed_objs, props): + """Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen.""" + 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', + 'Waist Dist', + 'Waist Radius', + 'Spherical', + 'Pol ∡', + }, + prop_name={'injection_axis', 'injection_direction'}, + run_on_init=True, + # Loaded + managed_objs={'mesh', 'modifier'}, + props={'injection_axis', 'injection_direction'}, + input_sockets={ + 'Temporal Shape', + 'Center', + 'Size', + 'Waist Dist', + 'Waist Radius', + 'Spherical', + 'Pol ∡', + }, + unit_systems={'BlenderUnits': ct.UNITS_BLENDER}, + scale_input_sockets={ + 'Center': 'BlenderUnits', + }, + ) + def on_inputs_changed(self, managed_objs, props, input_sockets, unit_systems): + size_2d = input_sockets['Size'] + size = { + ct.SimSpaceAxis.X: sp.Matrix([0, *size_2d]), + ct.SimSpaceAxis.Y: sp.Matrix([size_2d[0], 0, size_2d[1]]), + ct.SimSpaceAxis.Z: sp.Matrix([*size_2d, 0]), + }[props['injection_axis']] + + # 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.SourceGaussianBeam), + 'unit_system': unit_systems['BlenderUnits'], + 'inputs': { + # Orientation + 'Inj Axis': props['injection_axis'].axis, + 'Direction': props['injection_direction'].true_or_false, + 'theta': input_sockets['Spherical'][0], + 'phi': input_sockets['Spherical'][1], + 'Pol Angle': input_sockets['Pol ∡'], + # Gaussian Beam + 'Size': size, + 'Waist Dist': input_sockets['Waist Dist'], + 'Waist Radius': input_sockets['Waist Radius'], + }, + }, + ) + + #################### # - Blender Registration #################### -BL_REGISTER = [] -BL_NODES = {} +BL_REGISTER = [ + GaussianBeamSourceNode, +] +BL_NODES = {ct.NodeType.GaussianBeamSource: (ct.NodeCategory.MAXWELLSIM_SOURCES)} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/plane_wave_source.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/plane_wave_source.py index 75b2dad..7927836 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/plane_wave_source.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/sources/plane_wave_source.py @@ -30,6 +30,18 @@ from .. import base, events class PlaneWaveSourceNode(base.MaxwellSimNode): + """An infinite-extent angled source simulating an plane wave with linear polarization. + + The amplitude envelope is a gaussian function, and the complex electric field is a well-defined frequency-dependent phasor with very few shape parameters. + + The only critical shape parameter is the **waist**: At a chosen "focus" distance, the width of the beam has a chosen radius. + These properties are called "waist distance" and "waist radius". + At all other points, the width of the beam has a well-defined hyperbolic relationship to the waist, when given the IOR-dependent Rayleigh length. + + - Tidy3D Documentation: + - Mathematical Formalism: + """ + node_type = ct.NodeType.PlaneWaveSource bl_label = 'Plane Wave Source' use_sim_node_name = True