oscillode/oscillode/node_trees/maxwell_sim_nodes/sockets/physical/pol.py

284 lines
7.6 KiB
Python

# oscillode
# Copyright (C) 2024 oscillode Project Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# 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/>.
# blender_maxwell
# Copyright (C) 2024 blender_maxwell Project Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# 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 bpy
import sympy as sp
import sympy.physics.optics.polarization as spo_pol
import sympy.physics.units as spu
from blender_maxwell.utils import sympy_extra as spux
from ... import contracts as ct
from .. import base
StokesVector = spux.SympyExpr
class PhysicalPolBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalPol
bl_label = 'Polarization'
use_units = True
def radianize(self, ang):
return (
spu.convert_to(
ang * self.unit,
spu.radian,
)
/ spu.radian
)
####################
# - Properties
####################
model: bpy.props.EnumProperty(
name='Polarization Model',
description='A choice of polarization representation',
items=[
('UNPOL', 'Unpolarized', 'Unpolarized'),
('LIN_ANG', 'Linear', 'Linearly polarized at angle'),
('CIRC', 'Circular', 'Linearly polarized at angle'),
('JONES', 'Jones', 'Polarized waves described by Jones vector'),
('STOKES', 'Stokes', 'Linear x-pol of field'),
],
default='UNPOL',
update=(lambda self, context: self.on_prop_changed('model', context)),
)
## Lin Ang
lin_ang: bpy.props.FloatProperty(
name='Pol. Angle',
description='Angle to polarize linearly along',
default=0.0,
update=(lambda self, context: self.on_prop_changed('lin_ang', context)),
)
## Circ
circ: bpy.props.EnumProperty(
name='Pol. Orientation',
description='LCP or RCP',
items=[
('LCP', 'LCP', "'Left Circular Polarization'"),
('RCP', 'RCP', "'Right Circular Polarization'"),
],
default='LCP',
update=(lambda self, context: self.on_prop_changed('circ', context)),
)
## Jones
jones_psi: bpy.props.FloatProperty(
name='Jones X-Axis Angle',
description='Angle of the ellipse to the x-axis',
default=0.0,
precision=2,
update=(lambda self, context: self.on_prop_changed('jones_psi', context)),
)
jones_chi: bpy.props.FloatProperty(
name='Jones Major-Axis-Adjacent Angle',
description='Angle of adjacent to the ellipse major axis',
default=0.0,
precision=2,
update=(lambda self, context: self.on_prop_changed('jones_chi', context)),
)
## Stokes
stokes_psi: bpy.props.FloatProperty(
name='Stokes X-Axis Angle',
description='Angle of the ellipse to the x-axis',
default=0.0,
precision=2,
update=(lambda self, context: self.on_prop_changed('stokes_psi', context)),
)
stokes_chi: bpy.props.FloatProperty(
name='Stokes Major-Axis-Adjacent Angle',
description='Angle of adjacent to the ellipse major axis',
default=0.0,
precision=2,
update=(lambda self, context: self.on_prop_changed('stokes_chi', context)),
)
stokes_p: bpy.props.FloatProperty(
name='Stokes Polarization Degree',
description='The degree of polarization',
default=0.0,
precision=2,
update=(lambda self, context: self.on_prop_changed('stokes_p', context)),
)
stokes_I: bpy.props.FloatProperty(
name='Stokes Field Intensity',
description='The intensity of the polarized field',
default=0.0,
precision=2,
update=(lambda self, context: self.on_prop_changed('stokes_I', context)),
) ## TODO: Units?
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'model', text='')
if self.model == 'LIN_ANG':
col.prop(self, 'lin_ang', text='')
elif self.model == 'CIRC':
col.prop(self, 'circ', text='')
elif self.model == 'JONES':
split = col.split(factor=0.2, align=True)
col = split.column(align=False)
col.label(text='ψ,χ')
col = split.row(align=True)
col.prop(self, 'jones_psi', text='')
col.prop(self, 'jones_chi', text='')
elif self.model == 'STOKES':
split = col.split(factor=0.2, align=True)
# Split #1
col = split.column(align=False)
col.label(text='ψ,χ')
col.label(text='p,I')
# Split #2
col = split.column(align=False)
row = col.row(align=True)
row.prop(self, 'stokes_psi', text='')
row.prop(self, 'stokes_chi', text='')
row = col.row(align=True)
row.prop(self, 'stokes_p', text='')
row.prop(self, 'stokes_I', text='')
## TODO: Visualize stokes vector as oriented plane wave shape in plot (direct), in 3D (maybe in combination with a source), and on Poincare sphere.
####################
# - Default Value
####################
@property
def _value_unpol(self) -> StokesVector:
return spo_pol.stokes_vector(0, 0, 0)
@property
def _value_lin_ang(self) -> StokesVector:
return spo_pol.stokes_vector(self.radianize(self.lin_ang), 0, 0)
@property
def _value_circ(self) -> StokesVector:
return {
'RCP': spo_pol.stokes_vector(0, sp.pi / 4, 0),
'LCP': spo_pol.stokes_vector(0, -sp.pi / 4, 0),
}[self.circ]
@property
def _value_jones(self) -> StokesVector:
return spo_pol.jones_2_stokes(
spo_pol.jones_vector(
self.radianize(self.jones_psi),
self.radianize(self.jones_chi),
)
)
@property
def _value_stokes(self) -> StokesVector:
return spo_pol.stokes_vector(
self.radianize(self.stokes_psi),
self.radianize(self.stokes_chi),
self.stokes_p,
self.stokes_I,
)
@property
def value(self) -> StokesVector:
return {
'UNPOL': self._value_unpol,
'LIN_ANG': self._value_lin_ang,
'CIRC': self._value_circ,
'JONES': self._value_jones,
'STOKES': self._value_stokes,
}[self.model]
def sync_unit_change(self) -> None:
"""We don't have a setter, so we need to manually implement the unit change operation."""
self.lin_ang = (
spu.convert_to(
self.lin_ang * self.prev_unit,
self.unit,
)
/ self.unit
)
self.jones_psi = (
spu.convert_to(
self.jones_psi * self.prev_unit,
self.unit,
)
/ self.unit
)
self.jones_chi = (
spu.convert_to(
self.jones_chi * self.prev_unit,
self.unit,
)
/ self.unit
)
self.stokes_psi = (
spu.convert_to(
self.stokes_psi * self.prev_unit,
self.unit,
)
/ self.unit
)
self.stokes_chi = (
spu.convert_to(
self.stokes_chi * self.prev_unit,
self.unit,
)
/ self.unit
)
self.prev_active_unit = self.active_unit
####################
# - Socket Configuration
####################
class PhysicalPolSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalPol
def init(self, bl_socket: PhysicalPolBLSocket) -> None:
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalPolBLSocket,
]