From 0b7a068775c9a5aba471cf8e27abdba4a6bd344b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20Le=20Guyader?= <loic.le.guyader@xfel.eu>
Date: Fri, 1 Oct 2021 17:06:14 +0200
Subject: [PATCH] Adds Flat liquid Jet

---
 src/TZPGcalc/TZPGcalc.py | 236 +++++++++++++++++++++++++++++++--------
 1 file changed, 190 insertions(+), 46 deletions(-)

diff --git a/src/TZPGcalc/TZPGcalc.py b/src/TZPGcalc/TZPGcalc.py
index 74e4dcb..cf6a17d 100644
--- a/src/TZPGcalc/TZPGcalc.py
+++ b/src/TZPGcalc/TZPGcalc.py
@@ -10,8 +10,9 @@
 import numpy as np
 import pandas as pd
 import matplotlib.pyplot as plt
-from matplotlib.patches import Rectangle, Polygon
+from matplotlib.patches import PathPatch, Rectangle, Polygon
 from matplotlib.colors import hsv_to_rgb
+from matplotlib.path import Path
 
 import ipywidgets as widgets
 from ipywidgets import HBox, VBox
@@ -32,7 +33,8 @@ TZPG_db = {
         'TZPGoffaxis': 0.75,
         'grating': 3.8,
         'F_x': 0.25,
-        'F_y': 0.25
+        'F_y': 0.25,
+        '3beams': True
     },
     'O': {
         'design_nrj': 530,
@@ -41,7 +43,8 @@ TZPG_db = {
         'TZPGoffaxis': 0.55,
         'grating': 3.1,
         'F_x': 0.25,
-        'F_y': 0.25
+        'F_y': 0.25,
+        '3beams': True
     },
     'Fe': {
         'design_nrj': 715,
@@ -50,7 +53,8 @@ TZPG_db = {
         'TZPGoffaxis': 0.55,
         'grating': 3.1,
         'F_x': 0.25,
-        'F_y': 0.25
+        'F_y': 0.25,
+        '3beams': True
     },
     'Co': {
         'design_nrj': 785,
@@ -59,7 +63,8 @@ TZPG_db = {
         'TZPGoffaxis': 0.55,
         'grating': 3.1,
         'F_x': 0.25,
-        'F_y': 0.25
+        'F_y': 0.25,
+        '3beams': True
     },
     'Ni': {
         'design_nrj': 860,
@@ -68,7 +73,8 @@ TZPG_db = {
         'TZPGoffaxis': 0.55,
         'grating': 3.1,
         'F_x': 0.25,
-        'F_y': 0.25
+        'F_y': 0.25,
+        '3beams': True
     },
     'Cu': {
         'design_nrj': 927,
@@ -77,7 +83,8 @@ TZPG_db = {
         'TZPGoffaxis': 0.55,
         'grating': 3.1,
         'F_x': 0.25,
-        'F_y': 0.25
+        'F_y': 0.25,
+        '3beams': True
     },
     'Gd': {
         'design_nrj': 1210,
@@ -86,7 +93,8 @@ TZPG_db = {
         'TZPGoffaxis': 0.55,
         'grating': 3.1,
         'F_x': 0.25,
-        'F_y': 0.25
+        'F_y': 0.25,
+        '3beams': True
     }
 }
 
@@ -94,8 +102,8 @@ TZPG_db = {
 class TZPGcalc():
     def __init__(self):
         self.geo_beams = GeoBeams()
-        self.initFig()
         self.initWidgets()
+        self.initFig()
         self.init_beam_transport()
 
         # spot sizes of all beams
@@ -216,7 +224,7 @@ class TZPGcalc():
                           angle=45))
                }
 
-        # 5x5 membranes
+        # SampleNxSampleN membranes
         self.sampleLines = {}
         self.etchLines = {}
         for k in range(SampleN*SampleN):
@@ -226,6 +234,63 @@ class TZPGcalc():
                 Rectangle((0, 0), 1, 1, fill=False, facecolor='k',
                           alpha=0.4, ls='--'))
 
+        # Flat Liquid Jet
+        self.FLJ_lines = {}
+        self.FlatLiquidJet()
+
+    def FlatLiquidJet(self):
+        """Draw a Flat Liquid jet.
+        """
+        sw = self.widgets
+        HW = 0.5*sw['FLJ_W'].value  # [mm]
+        L = sw['FLJ_L'].value  # [mm]
+        mf = sw['FLJ_mf'].value  # [mm]
+        incidence = np.deg2rad(sw['samIncidence'].value)  # [rad]
+        ox = sw['samX'].value  # [mm]
+        oy = -L/2 + sw['samY'].value  # [mm]
+
+        # incidence angle squeezes sample
+        ci = np.cos(incidence)
+
+        verts = [
+            [(0*ci, L + oy),   # P0
+             (HW/2*ci, L + oy),  # P1
+             (HW*ci, 0.5*(1+mf)*L + oy),  # P2
+             (HW*ci, mf*L + oy)],  # P3
+            [(HW*ci, mf*L + oy),  # P0
+             (HW*ci, 0.5*mf*L + oy),  # P1
+             (0, 0 + oy),  # P2
+             (0, 0 + oy)]  # P3
+        ]
+
+        # second half image of liquid jet
+        mirror = []
+        for v in verts:
+            mirror.append([(-p[0], p[1]) for p in v])
+
+        verts += mirror
+
+        # apply offsets
+        fverts = []
+        for v in verts:
+            fverts.append([(p[0] + ox, p[1]) for p in v])
+
+        codes = [
+            Path.MOVETO,  # P0
+            Path.CURVE4,  # P1
+            Path.CURVE4,  # P2
+            Path.CURVE4  # P3
+        ]
+
+        for k, v in enumerate(fverts):
+            path = Path(v, codes)
+            if k in self.FLJ_lines:
+                self.FLJ_lines[k].remove()
+
+            self.FLJ_lines[k] = self.ax_sam.add_patch(
+                PathPatch(path, alpha=0.4, facecolor='none', lw=2)
+                )
+
     def RectUpdate(self, rect, xLeft, yBottom, xRight, yTop):
         """Updates the position and size of the given Rectangle.
 
@@ -303,6 +368,11 @@ class TZPGcalc():
                 self.SpotSizes[img['type']][conf['Energy']][z, k] = (
                     1e3*(np.max(vs) - np.min(vs)))
 
+        # 3 beams configuration
+        b = self.widgets['3beams'].value
+        Beams['F0G0'].set_visible(b)
+        Beams['F1G0'].set_visible(b)
+
     def DetectorUpdate(self, Xoff, Yoff):
         """Draws DSSC detector module with filter mask.
 
@@ -338,8 +408,28 @@ class TZPGcalc():
         self.detLines['diamond'].set_xy((
             self.scale*Xoff, self.scale*(Yoff - diamondW/2*np.sqrt(2))))
 
-    def SampleUpdate(self, w, p, Xoff, Yoff, thickness=0.525,
-                     incidence=0, etch_angle=54.74):
+    def SampleUpdate(self):
+        if self.widgets['SampleType'].value == 'Membranes Array':
+            b = True
+        elif self.widgets['SampleType'].value == 'Flat Liquid Jet':
+            b = False
+        else:
+            raise ValueError('Sample type must be either "Membranes Array" or'
+                             '"Flat Liquid Jet"')
+
+        for k in range(SampleN*SampleN):
+            self.sampleLines[k].set_visible(b)
+            self.etchLines[k].set_visible(b)
+
+        for k in range(4):
+            self.FLJ_lines[k].set_visible(not(b))
+
+        if b:
+            self.MembraneSampleUpdate()
+        else:
+            self.FlatLiquidJet()
+
+    def MembraneSampleUpdate(self):
         """Draws the sample.
 
         Inputs
@@ -352,6 +442,15 @@ class TZPGcalc():
             incidence: incidence angle in rad
             etch_angle: etching angle from surface in rad
         """
+        sw = self.widgets
+        w = sw['samw'].value*1e-3  # [m]
+        p = sw['samp'].value*1e-3  # [m]
+        Xoff = sw['samX'].value*1e-3  # [m]
+        Yoff = sw['samY'].value*1e-3  # [m]
+        thickness = sw['samthickness'].value*1e-6  # [m]
+        etch_angle = np.deg2rad(sw['samEtchAngle'].value)  # [rad]
+        incidence = np.deg2rad(sw['samIncidence'].value)  # [rad]
+
         # Si etching angle
         wp = w + 2*thickness/np.tan(etch_angle)
 
@@ -462,14 +561,7 @@ class TZPGcalc():
         self.DetectorUpdate(detXoff, detYoff)
 
         # update the sample
-        samw = self.widgets['samw'].value*1e-3  # [m]
-        samp = self.widgets['samp'].value*1e-3  # [m]
-        samXoff = self.widgets['samX'].value*1e-3  # [m]
-        samYoff = self.widgets['samY'].value*1e-3  # [m]
-        samthickness = self.widgets['samthickness'].value*1e-6  # [m]
-        samEtchAngle = np.deg2rad(self.widgets['samEtchAngle'].value)  # [rad]
-        self.SampleUpdate(samw, samp, samXoff, samYoff, samthickness,
-                          samIncidence, samEtchAngle)
+        self.SampleUpdate()
 
     def initWidgets(self):
         """ Creates the necessary interactive widget controls.
@@ -568,6 +660,7 @@ class TZPGcalc():
             self.widgets['grating'].value = v['grating']
             self.widgets['F_x'].value = v['F_x']
             self.widgets['F_y'].value = v['F_y']
+            self.widgets['3beams'].value = v['3beams']
 
             # necessary to recompute grating pitch and outer zone plate width
             self.UpdateFig()
@@ -658,6 +751,12 @@ class TZPGcalc():
             style=style,
             layout=layout
         )
+        self.widgets['3beams'] = widgets.Checkbox(
+            value=True,
+            description='3 beams:',
+            style=style,
+            layout=layout
+        )
         self.widgets['dr_label_x'] = widgets.Label(value='dr_x')
         self.widgets['dr_label_y'] = widgets.Label(value='dr_y')
         self.widgets['d_label'] = widgets.Label(value='dr')
@@ -668,7 +767,8 @@ class TZPGcalc():
                   self.widgets['F_y']
                   ]),
             HBox([self.widgets['grating'],
-                  self.widgets['TZPGoffaxis']]),
+                  self.widgets['TZPGoffaxis'],
+                  self.widgets['3beams']]),
             HBox([self.widgets['dr_label_x'],
                   self.widgets['dr_label_y']]),
             HBox([widgets.Label(value='Energy range (eV):'),
@@ -679,6 +779,13 @@ class TZPGcalc():
             ])
 
         # sample part
+        self.widgets['SampleType'] = widgets.Dropdown(
+            options=['Membranes Array', 'Flat Liquid Jet'],
+            value='Membranes Array',
+            decription='Sample type:',
+            style=style,
+            layout=layout
+        )
         self.widgets['SAMZ'] = widgets.BoundedFloatText(
             value=30.,
             min=-10.,
@@ -688,12 +795,14 @@ class TZPGcalc():
             style=style,
             layout=layout
         )
+
+        # membranes
         self.widgets['samw'] = widgets.BoundedFloatText(
             value=.5,
             min=0.01,
             max=2.0,
             step=.01,
-            description='width:',
+            description='width (mm):',
             style=style,
             layout=layout
         )
@@ -702,10 +811,58 @@ class TZPGcalc():
             min=0.01,
             max=2.0,
             step=.01,
-            description='pitch:',
+            description='pitch (mm):',
+            style=style,
+            layout=layout
+        )
+        self.widgets['samthickness'] = widgets.BoundedFloatText(
+            value=381,
+            min=1,
+            max=1000,
+            step=1,
+            description='Substrate thickness (um):',
+            style=style,
+            layout=layout
+        )
+        self.widgets['samEtchAngle'] = widgets.BoundedFloatText(
+            value=54.74,
+            min=0,
+            max=90,
+            step=0.01,
+            description='etch angle (deg):',
+            style=style,
+            layout=layout
+        )
+
+        # Flat Liquid Jet
+        self.widgets['FLJ_W'] = widgets.BoundedFloatText(
+            value=1.0,
+            min=0,
+            max=5,
+            step=0.1,
+            description='Flat Liquid Jet width (mm):',
+            style=style,
+            layout=layout
+        )
+        self.widgets['FLJ_L'] = widgets.BoundedFloatText(
+            value=4.6,
+            min=0,
+            max=15,
+            step=0.1,
+            description='of length (mm):',
+            style=style,
+            layout=layout
+        )
+        self.widgets['FLJ_mf'] = widgets.BoundedFloatText(
+            value=0.75,
+            min=0,
+            max=1,
+            step=0.01,
+            description='at:',
             style=style,
             layout=layout
         )
+
         self.widgets['samX'] = widgets.BoundedFloatText(
             value=0.,
             min=-10,
@@ -724,15 +881,6 @@ class TZPGcalc():
             style=style,
             layout=layout
         )
-        self.widgets['samthickness'] = widgets.BoundedFloatText(
-            value=381,
-            min=1,
-            max=1000,
-            step=1,
-            description='Substrate thickness (um):',
-            style=style,
-            layout=layout
-        )
         self.widgets['samIncidence'] = widgets.BoundedFloatText(
             value=0,
             min=0,
@@ -742,26 +890,22 @@ class TZPGcalc():
             style=style,
             layout=layout
         )
-        self.widgets['samEtchAngle'] = widgets.BoundedFloatText(
-            value=54.74,
-            min=0,
-            max=90,
-            step=0.01,
-            description='Etch angle from surface (deg):',
-            style=style,
-            layout=layout
-        )
         samTab = VBox([
             self.widgets['SAMZ'],
-            HBox([widgets.Label(value='Membrane (mm), '),
+            self.widgets['SampleType'],
+            HBox([widgets.Label(value='Membranes array, '),
                   self.widgets['samw'],
                   self.widgets['samp']]),
+            HBox([self.widgets['samthickness'],
+                  self.widgets['samEtchAngle']]),
+            HBox([self.widgets['FLJ_W'],
+                  self.widgets['FLJ_mf'],
+                  self.widgets['FLJ_L']]),
             HBox([widgets.Label(value='Sample Offset (mm), '),
                   self.widgets['samX'],
                   self.widgets['samY']]),
-            self.widgets['samthickness'],
             HBox([self.widgets['samIncidence'],
-                  self.widgets['samEtchAngle']])
+                  ])
         ])
 
         # Detector tab
@@ -775,7 +919,7 @@ class TZPGcalc():
             layout=layout
         )
         self.widgets['detX'] = widgets.BoundedFloatText(
-            value=20.,
+            value=34.5,
             min=-50,
             max=50,
             step=0.5,
@@ -784,7 +928,7 @@ class TZPGcalc():
             layout=layout
         )
         self.widgets['detY'] = widgets.BoundedFloatText(
-            value=0.,
+            value=-2.,
             min=-50,
             max=50,
             step=0.5,
-- 
GitLab