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