feat: implemented `GaussianBeam` source

This node is good for approximating a simple laser.
main
Sofus Albert Høgsbro Rose 2024-05-07 12:39:17 +02:00
parent 9f8ff33e4f
commit 929fb2dae9
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
6 changed files with 256 additions and 9 deletions

Binary file not shown.

Binary file not shown.

View File

@ -128,6 +128,7 @@ class ManagedBLMesh(base.ManagedObj):
""" """
bl_object = bpy.data.objects.get(self.name) bl_object = bpy.data.objects.get(self.name)
if bl_object is None: if bl_object is None:
log.info('Created previewable ManagedBLMesh "%s"', bl_object.name)
bl_object = self.bl_object() bl_object = self.bl_object()
if bl_object.name not in preview_collection().objects: if bl_object.name not in preview_collection().objects:

View File

@ -16,7 +16,7 @@
from . import ( from . import (
# astigmatic_gaussian_beam_source, # astigmatic_gaussian_beam_source,
# gaussian_beam_source, gaussian_beam_source,
plane_wave_source, plane_wave_source,
point_dipole_source, point_dipole_source,
temporal_shapes, temporal_shapes,
@ -24,19 +24,19 @@ from . import (
BL_REGISTER = [ BL_REGISTER = [
*temporal_shapes.BL_REGISTER, *temporal_shapes.BL_REGISTER,
*plane_wave_source.BL_REGISTER,
*point_dipole_source.BL_REGISTER, *point_dipole_source.BL_REGISTER,
# *uniform_current_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, # *astigmatic_gaussian_beam_source.BL_REGISTER,
# *tfsf_source.BL_REGISTER, # *tfsf_source.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**temporal_shapes.BL_NODES, **temporal_shapes.BL_NODES,
**plane_wave_source.BL_NODES,
**point_dipole_source.BL_NODES, **point_dipole_source.BL_NODES,
# **uniform_current_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, # **astigmatic_gaussian_beam_source.BL_NODES,
# **tfsf_source.BL_NODES, # **tfsf_source.BL_NODES,
} }

View File

@ -14,8 +14,239 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
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: <https://docs.flexcompute.com/projects/tidy3d/en/latest/api/_autosummary/tidy3d.GaussianBeam.html#tidy3d.GaussianBeam>
- Mathematical Formalism: <https://en.wikipedia.org/wiki/Gaussian_beam>
"""
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 # - Blender Registration
#################### ####################
BL_REGISTER = [] BL_REGISTER = [
BL_NODES = {} GaussianBeamSourceNode,
]
BL_NODES = {ct.NodeType.GaussianBeamSource: (ct.NodeCategory.MAXWELLSIM_SOURCES)}

View File

@ -30,6 +30,18 @@ from .. import base, events
class PlaneWaveSourceNode(base.MaxwellSimNode): 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: <https://docs.flexcompute.com/projects/tidy3d/en/latest/api/_autosummary/tidy3d.GaussianBeam.html#tidy3d.GaussianBeam>
- Mathematical Formalism: <https://en.wikipedia.org/wiki/Gaussian_beam>
"""
node_type = ct.NodeType.PlaneWaveSource node_type = ct.NodeType.PlaneWaveSource
bl_label = 'Plane Wave Source' bl_label = 'Plane Wave Source'
use_sim_node_name = True use_sim_node_name = True