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)
|
||||
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:
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -14,8 +14,239 @@
|
|||
# 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/>.
|
||||
|
||||
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
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
||||
BL_REGISTER = [
|
||||
GaussianBeamSourceNode,
|
||||
]
|
||||
BL_NODES = {ct.NodeType.GaussianBeamSource: (ct.NodeCategory.MAXWELLSIM_SOURCES)}
|
||||
|
|
|
@ -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: <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
|
||||
bl_label = 'Plane Wave Source'
|
||||
use_sim_node_name = True
|
||||
|
|
Loading…
Reference in New Issue