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()