9771c7a9b60247c4ad55abf2d433d8b14af00d2c
   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
  15import write_path
  16from pipython import datarectools, pitools
  17import nidaqmx
  18import 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 = 1  # h, nm
 115    grating_pitch = 5   # Lambda, nm
 116    blaze_angle = 15    # theta_b, degrees
 117    n_blazes = 2      # number of steps ("teeth")
 118    beam_dia = 0.1     # diameter of laser beam, nm
 119
 120    # Calculate turning points of a single step ("tooth")
 121    if blaze_angle is None:
 122        d1 = grating_pitch # axial length of upwards section of sawtooth
 123        d2 = 0 # axial length of downwards section of sawtooth
 124        back_angle = 90 # angle between grating normal and downwards section of sawtooth, degrees
 125    else:
 126        d1 = grating_height / np.tan(blaze_angle * np.pi / 180) # axial length of upwards section of sawtooth
 127        d2 = grating_pitch - d1 # axial length of downwards section of sawtooth
 128        back_angle = np.arctan(d2 / grating_height) / np.pi * 180 # angle between grating normal and downwards section of sawtooth, degrees
 129    
 130    pts = [
 131        [0,             0               ],
 132        [d1,            grating_height  ],
 133        [grating_pitch, 0               ]
 134    ]
 135
 136    while True:
 137        try:
 138            pts_inner = calculate_offset_vertices(np.array(pts[-3:]), -beam_dia)
 139        except ValueError:
 140            break
 141        for pt_inner in pts_inner:
 142            pts.append(pt_inner)
 143
 144    lines = []
 145
 146    for i in range(len(pts) // 3):
 147        lines.append((pts[i*3+0], pts[i*3+1]))
 148        lines.append((pts[i*3+1], pts[i*3+2]))
 149        lines.append((pts[i*3+2], pts[i*3+0]))
 150    lines = np.array(lines)
 151
 152    # Remove a bit of the line in the corner to prevent overwriting
 153    lines[2,1,0] += beam_dia
 154
 155    blazes_addition_x = np.repeat(np.arange(n_blazes) * grating_pitch, 3)
 156    blazes_addition_y = np.zeros(n_blazes * 3)
 157    blazes_addition = np.column_stack((blazes_addition_x, blazes_addition_y))
 158    pts = np.tile(pts, n_blazes) + blazes_addition
 159    
 160
 161    #points = np.tile(np.tile(single_blaze_points, (n_blazes, 1)) + blazes_addition, (n_passes, 1)) + passes_addition
 162    # points = np.tile(single_blaze_points, (n_passes, 1)) + passes_addition
 163    #points = np.tile(single_blaze_points, (n_blazes, 1)) + blazes_addition
 164    # print(points)
 165    
 166
 167    # points_x = []
 168    # points_y = []
 169    # for k in range(n_passes):
 170    #     for i in range(n_blazes):
 171    #         points_x_this_blaze = single_blaze_points[0] + i*grating_pitch + k*passes_transformation[0]
 172    #         points_y_this_blaze = single_blaze_points[1] + k*passes_transformation[1]
 173    #         for j in range(len(single_blaze_points[0])):
 174    #             points_x.append(points_x_this_blaze[j])
 175    #             points_y.append(points_y_this_blaze[j])
 176    # print(points_x)
 177    # print(points_y)
 178        
 179    fig, ax = plt.subplots()
 180    ax.set_aspect("equal")
 181    for pair in lines:
 182        ax.plot(pair[:,0], pair[:,1], '-', color='black')  
 183    plt.show()