1""" 2write_blazed.py 3Andrew Lorimer, January 2025 4Monash University 5 6Writes a reflective brazed grating pattern (sawtooth) using 7the confocal direct laser writing setup on Sb2S3 thin-film PCM. 8""" 9 10import configparser as cp 11import cv2 12import numpy as np 13import sys 14import matplotlib.pyplot as plt 15#import write_path 16#from pipython import datarectools, pitools 17#import nidaqmx 18#import pipython 19import time 20 21defcalculate_offset_vertices(outer_vertices, offset_distance): 22""" 23 Calculate the vertices of an inner triangle offset inward from an outer triangle. 24 25 Parameters: 26 - outer_vertices: A list of tuples [(x1, y1), (x2, y2), (x3, y3)] defining the outer triangle. 27 - offset_distance: The distance by which the inner triangle is offset inward. 28 29 Returns: 30 - A list of tuples [(x1', y1'), (x2', y2'), (x3', y3')] defining the inner triangle. 31 """ 32defunit_vector(vector): 33"""Return the unit vector from two points""" 34return vector / np.linalg.norm(vector) 35 36defnormal(vector): 37"""Return the normal vector from two points""" 38return np.array([-vector[1], vector[0]]) 39 40defintersection(p1, p2, p3, p4): 41"""Find the intersection point of two lines defined by points (p1, p2) and (p3, p4)""" 42 A1, B1 = p2[1] - p1[1], p1[0] - p2[0] 43 C1 = A1 * p1[0] + B1 * p1[1] 44 45 A2, B2 = p4[1] - p3[1], p3[0] - p4[0] 46 C2 = A2 * p3[0] + B2 * p3[1] 47 48 determinant = A1 * B2 - A2 * B1 49if determinant ==0: 50raiseValueError("Lines do not intersect") 51 52 x = (B2 * C1 - B1 * C2) / determinant 53 y = (A1 * C2 - A2 * C1) / determinant 54return(x, y) 55 56defis_parallel(vec1, vec2): 57 uv1 =unit_vector(vec1) 58 uv2 =unit_vector(vec2) 59return np.all(abs(uv1 - uv2) <0.001) 60 61 inner_vertices = [] 62 num_vertices =len(outer_vertices) 63 64for i inrange(num_vertices): 65# Current vertex and next vertex 66 p1 = np.array(outer_vertices[i]) 67 p2 = np.array(outer_vertices[(i +1) % num_vertices]) 68 69# Calculate edge direction and inward normal 70 edge_vector = p2 - p1 71 inward_normal =unit_vector(normal(edge_vector)) * offset_distance 72 73# Offset the two points on the edge 74 offset_p1 = p1 + inward_normal 75 offset_p2 = p2 + inward_normal 76 77# Store the offset line 78 inner_vertices.append((offset_p1, offset_p2)) 79 80# Find intersections of the offset lines 81 result_vertices = [] 82for i inrange(num_vertices): 83# Current offset line and next offset line 84 line1 = inner_vertices[i] 85 line2 = inner_vertices[(i +1) % num_vertices] 86 87# Calculate the intersection of the two lines 88int=intersection(line1[0], line1[1], line2[0], line2[1]) 89 result_vertices.append(int) 90 91 result_vertices = np.array([result_vertices[2], result_vertices[0], result_vertices[1]]) 92 93 result_edges = np.array([ 94[result_vertices[0], result_vertices[1]], 95[result_vertices[1], result_vertices[2]], 96[result_vertices[2], result_vertices[0]] 97]) 98 outer_edges = np.array([ 99[outer_vertices[0], outer_vertices[1]], 100[outer_vertices[1], outer_vertices[2]], 101[outer_vertices[2], outer_vertices[0]] 102]) 103 104if notis_parallel(outer_vertices[1] - outer_vertices[0], result_vertices[1] - result_vertices[0]): 105raiseValueError("Offset is too large for the given outer trianlge") 106 107return result_vertices 108 109if __name__ =="__main__": 110 111#task, pidevice = write_path.setup() 112 113# Input parameters 114 grating_height =1000# h, nm 115 grating_pitch =5000# Lambda, nm 116 blaze_angle =None# theta_b, degrees 117 n_blazes =10# number of steps ("teeth") 118 beam_dia =500# diameter of laser beam, nm 119 base_height =500# nm 120 121# Calculate vertices of a single step ("tooth") 122if blaze_angle is None: 123 d1 = grating_pitch # axial length of upwards section of sawtooth 124 d2 =0# axial length of downwards section of sawtooth 125 back_angle =90# angle between grating normal and downwards section of sawtooth, degrees 126else: 127 d1 = grating_height / np.tan(blaze_angle * np.pi /180)# axial length of upwards section of sawtooth 128 d2 = grating_pitch - d1 # axial length of downwards section of sawtooth 129 back_angle = np.arctan(d2 / grating_height) / np.pi *180# angle between grating normal and downwards section of sawtooth, degrees 130 131# Put these vertices into an array as coordinates [ [x1, y1], [x2, y2], [x3, y3] ] 132 pts = [ 133[0,0], 134[d1, grating_height ], 135[grating_pitch,0] 136] 137 138# Calculate vertices of the inner triangles to fill in the middle 139 n_passes =1 140while True: 141try: 142 pts_inner =calculate_offset_vertices(np.array(pts[-3:]), -beam_dia) 143exceptValueError: 144# Not possible to fit any smaller triangles in the middle with given beam diameter 145break 146# Add inner triangles to the list of vertices 147for pt_inner in pts_inner: 148 pts.append(pt_inner) 149 n_passes +=1 150 151 lines = [] 152 153# Generate lines between each set of three consecutive vertices 154for i inrange(len(pts) //3): 155 lines.append((pts[i*3+0], pts[i*3+1])) 156 lines.append((pts[i*3+1], pts[i*3+2])) 157 lines.append((pts[i*3+2], pts[i*3+0])) 158 lines = np.array(lines) 159 160# Remove a bit of the line in the corner to prevent overwriting 161 lines[2,1,0] += beam_dia 162 163 164# Duplicate this set of triangles a number of times along the axis of the grating (defined by n_blazes) 165 blazes_addition_x = np.tile(np.repeat(np.arange(n_blazes) * grating_pitch,3*n_passes), (2,1)).T 166 blazes_addition_y = np.zeros((n_blazes *3* n_passes,2)) 167 blazes_addition = np.stack((blazes_addition_x, blazes_addition_y), axis=-1) 168 lines = np.tile(lines, (n_blazes,1,1)) + blazes_addition 169 170# Calculate a set of y values for lines to make up the solid rectangular base 171 base_y_vals = np.arange(-base_height,0, beam_dia) 172 173# Calculate alternating x values for a zig-zag raster scan 174 base_x_vals_single = np.array([0, grating_pitch*n_blazes]) 175 base_x_vals_duplicated = np.array((base_x_vals_single, np.flip(base_x_vals_single))) 176 base_x_vals = np.tile(base_x_vals_duplicated, (int(len(base_y_vals)//2),1)) 177# Deal with odd number of lines 178iflen(base_y_vals) %2!=0: 179 base_x_vals = np.vstack((base_x_vals, base_x_vals_single)) 180 181# Put x and y values into a list of pairs of points 182 base_y_vals = np.tile(base_y_vals, (2,1)).T 183 base_lines = np.stack((base_x_vals, base_y_vals), axis=2) 184 185# Add these lines to the overall array 186 lines = np.concatenate((lines, base_lines)) 187 188# Plot 189 fig, ax = plt.subplots() 190 ax.set_aspect("equal") 191for pair in lines: 192 ax.plot(pair[:,0], pair[:,1],'-', color='black') 193 plt.show()