write_path / write_blazed.pyon commit binary grating code (3ae49cb)
   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
  21def calculate_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    """
  32    def unit_vector(vector):
  33        """Return the unit vector from two points"""
  34        return vector / np.linalg.norm(vector)
  35
  36    def normal(vector):
  37        """Return the normal vector from two points"""
  38        return np.array([-vector[1], vector[0]])
  39    
  40    def intersection(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
  49        if determinant == 0:
  50            raise ValueError("Lines do not intersect")
  51        
  52        x = (B2 * C1 - B1 * C2) / determinant
  53        y = (A1 * C2 - A2 * C1) / determinant
  54        return (x, y)
  55
  56    def is_parallel(vec1, vec2):
  57        uv1 = unit_vector(vec1)
  58        uv2 = unit_vector(vec2)
  59        return np.all(abs(uv1 - uv2) < 0.001)
  60        
  61    inner_vertices = []
  62    num_vertices = len(outer_vertices)
  63    
  64    for i in range(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 = []
  82    for i in range(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
  88        int = 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
 104    if not is_parallel(outer_vertices[1] - outer_vertices[0], result_vertices[1] - result_vertices[0]):
 105        raise ValueError("Offset is too large for the given outer trianlge")
 106
 107    return result_vertices
 108
 109if __name__ == "__main__":
 110
 111    #task, pidevice = write_path.setup()
 112
 113    # Input parameters
 114    grating_height = None # h, nm
 115    grating_pitch = 2000   # 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 = 0  # nm
 120    wavelength = 450    # nm
 121    order = 1
 122    n_cr = 1.3
 123
 124    blaze_angle = np.arcsin(order*wavelength/(2*grating_pitch)) / np.pi * 180
 125    print("Calculated blaze angle: {:.1f} deg".format(blaze_angle))
 126
 127    grating_height = np.tan(blaze_angle*np.pi/180)*grating_pitch
 128    print("Calculated grating height: {:.1f} nm".format(grating_height))
 129
 130    diff_angle = np.arcsin(wavelength / grating_pitch) / np.pi * 180
 131    print("First order diffraction angle: {:.1f} deg".format(diff_angle))
 132
 133    # Calculate vertices of a single step ("tooth")
 134    if blaze_angle is None:
 135        d1 = grating_pitch # axial length of upwards section of sawtooth
 136        d2 = 0 # axial length of downwards section of sawtooth
 137        back_angle = 90 # angle between grating normal and downwards section of sawtooth, degrees
 138    else:
 139        d1 = grating_height / np.tan(blaze_angle * np.pi / 180) # axial length of upwards section of sawtooth
 140        d2 = grating_pitch - d1 # axial length of downwards section of sawtooth
 141        back_angle = np.arctan(d2 / grating_height) / np.pi * 180 # angle between grating normal and downwards section of sawtooth, degrees
 142    
 143    # Put these vertices into an array as coordinates [ [x1, y1], [x2, y2], [x3, y3] ]
 144    pts = [
 145        [0,             0               ],
 146        [d1,            grating_height  ],
 147        [grating_pitch, 0               ]
 148    ]
 149
 150    # Calculate vertices of the inner triangles to fill in the middle
 151    n_passes = 1
 152    while True:
 153        try:
 154            pts_inner = calculate_offset_vertices(np.array(pts[-3:]), -beam_dia)
 155        except ValueError:
 156            # Not possible to fit any smaller triangles in the middle with given beam diameter
 157            break
 158        # Add inner triangles to the list of vertices
 159        for pt_inner in pts_inner:
 160            pts.append(pt_inner)
 161        n_passes += 1
 162
 163    lines = []
 164
 165    # Generate lines between each set of three consecutive vertices
 166    for i in range(len(pts) // 3):
 167        lines.append((pts[i*3+0], pts[i*3+1]))
 168        lines.append((pts[i*3+1], pts[i*3+2]))
 169        lines.append((pts[i*3+2], pts[i*3+0]))
 170    lines = np.array(lines)
 171
 172    # Remove a bit of the line in the corner to prevent overwriting
 173    lines[2,1,0] += beam_dia
 174
 175
 176    # Duplicate this set of triangles a number of times along the axis of the grating (defined by n_blazes)
 177    blazes_addition_x = np.tile(np.repeat(np.arange(n_blazes) * grating_pitch, 3*n_passes), (2, 1)).T
 178    blazes_addition_y = np.zeros((n_blazes * 3 * n_passes, 2))
 179    blazes_addition = np.stack((blazes_addition_x, blazes_addition_y), axis=-1)
 180    lines = np.tile(lines, (n_blazes, 1, 1)) + blazes_addition
 181
 182    # Calculate a set of y values for lines to make up the solid rectangular base
 183    base_y_vals = np.arange(-base_height, 0, beam_dia)
 184
 185    # Calculate alternating x values for a zig-zag raster scan
 186    base_x_vals_single = np.array([0, grating_pitch*n_blazes])
 187    base_x_vals_duplicated = np.array((base_x_vals_single, np.flip(base_x_vals_single)))
 188    base_x_vals = np.tile(base_x_vals_duplicated, (int(len(base_y_vals)//2), 1))
 189    # Deal with odd number of lines
 190    if len(base_y_vals) % 2 != 0:
 191        base_x_vals = np.vstack((base_x_vals, base_x_vals_single))
 192
 193    # Put x and y values into a list of pairs of points
 194    base_y_vals = np.tile(base_y_vals, (2, 1)).T
 195    base_lines = np.stack((base_x_vals, base_y_vals), axis=2)
 196
 197    # Add these lines to the overall array
 198    lines = np.concatenate((lines, base_lines))
 199
 200    # Plot
 201    fig, ax = plt.subplots()
 202    ax.set_aspect("equal")
 203    for pair in lines:
 204        ax.plot(pair[:,0], pair[:,1], '-', color='black')  
 205    plt.show()