feat: implemented `GaussianBeam` source
This node is good for approximating a simple laser.main
parent
9f8ff33e4f
commit
929fb2dae9
BIN
src/blender_maxwell/assets/internal/source/_source_gaussian_beam.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/source/_source_gaussian_beam.blend (Stored with Git LFS)
100644
Binary file not shown.
BIN
src/blender_maxwell/assets/internal/source/_source_plane_wave.blend (Stored with Git LFS)
BIN
src/blender_maxwell/assets/internal/source/_source_plane_wave.blend (Stored with Git LFS)
Binary file not shown.
|
@ -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:
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue