# XYZ-Stage Class
import threading
import numpy as np
import logging
import math
# helper functions to add/subtract tuples
[docs]def t_add(tuple1, tuple2):
return tuple(np.add(tuple1, tuple2))
[docs]def t_sub(tuple1, tuple2):
return tuple(np.subtract(tuple1, tuple2))
##
[docs]class XYZ_Stage(object):
def __init__(self, mtr_list,off_angle=0):
'''
Constructor
ser_list (list): a list with serial objects corresponding to
the x, y and z motors respectively
'''
# # new threading approach
# def init_x_axis():
# self.x_axis = StepMotor(ser_list[0])
# def init_y_axis():
# self.y_axis = StepMotor(ser_list[1])
# def init_z_axis():
# self.z_axis = StepMotor(ser_list[2])
#
# t_x = threading.Thread(target=init_x_axis)
# t_y = threading.Thread(target=init_y_axis)
# t_z = threading.Thread(target=init_z_axis)
#
# t_x.start(), t_y.start(), t_z.start()
# # t_x.join(), t_y.join(), t_z.join()
self.x_axis = mtr_list[0]
self.y_axis = mtr_list[1]
self.z_axis = mtr_list[2]
self.x, self.y, self.z = (0, 0, 0)
self.zeros = (0, 0, 0)
self.stepsize = 0.001
# virtual leveling helpers
self.ang_x = None
self.ang_y = None
# offset angle of x and y stages from optimal axis
self.off_angle = float(off_angle)
def set_off_angle(self,off_angle):
self.off_angle = float(off_angle)
def get_off_angle(self):
return self.off_angle
def get_calibrated(self):
'''
Check whether the stage was calibrated to chip by checking whether zeros are unequal 0
'''
if self.zeros == (0,0,0):
return False
else:
return True
[docs] def home(self):
'''Sets all of step-motors to home position'''
# Create threads for homing
x = threading.Thread(self.x_axis.home())
y = threading.Thread(self.y_axis.home())
z = threading.Thread(self.z_axis.home())
# Starts x-direction, then y-direction and z-direction
x.start(); y.start(), z.start()
# Reset isntance variables
self.x, self.y, self.z = (0, 0, 0)
self.zeros = (0, 0, 0)
self.stepsize = 0.001
[docs] def check_coordinates_2d(self, target_pos):
'''
Check whather move to a point in space is possible within actuator range
target_pos (2-tuple): (x,y) coordinates of the final point
'''
return self.check_coordinates((target_pos[0], target_pos[1], self.z))
[docs] def check_coordinates(self, target_pos):
'''
Check whather move to a point in space is possible within actuator range
target_pos (3-tuple): (x,y,z) coordinates of the final point
'''
act_x, act_y, act_z = t_add(target_pos, self.zeros)
if self.x_axis.check_abs_transl(act_x) and self.y_axis.check_abs_transl(act_y) and self.z_axis.check_abs_transl(act_z):
return True
else:
return False
[docs] def set_coordinates(self, target_pos, long=False):
'''
Move to a point in the plane
target_pos (3-tuple): (x,y,z) coordinates of the final point
'''
# actual distances sent to StepMotor
act_x, act_y, act_z = t_add(target_pos, self.zeros)
if not self.x_axis.moving and not self.y_axis.moving and not self.z_axis.moving and self.x_axis.check_abs_transl(act_x) and self.y_axis.check_abs_transl(act_y) and self.z_axis.check_abs_transl(act_z):
x_d = target_pos[0] - self.x
y_d = target_pos[1] - self.y
self.x, self.y, self.z = target_pos
z_extra = 0
if long :
if self.leveled():
z_extra += x_d * math.tan(math.radians(self.ang_x)) + y_d * math.tan(math.radians(self.ang_y))
self.z += z_extra
act_z += z_extra
# threading approach
t_x = threading.Thread(target=self.x_axis.abs_transl, args=(act_x,))
t_y = threading.Thread(target=self.y_axis.abs_transl, args=(act_y,))
t_z = threading.Thread(target=self.z_axis.abs_transl, args=(act_z,))
if z_extra > 0:
t_x.start(), t_y.start()
t_x.join(), t_y.join()
t_z.start()
t_z.join()
elif z_extra < 0:
t_z.start()
t_z.join()
t_x.start(), t_y.start()
t_x.join(), t_y.join()
else:
t_x.start(), t_y.start(), t_z.start()
t_x.join(), t_y.join(), t_z.join()
else:
print 'XYZ_stage::error: Attempted move out of bounds (abs_pos:{},{},{})'.format(act_x, act_y, act_z)
[docs] def set_coor_2d(self, target_pos, long=False):
'''
Move to a point in the plane
target_pos (2-tuple): (x,y) coordinates of the final point
'''
self.set_coordinates((target_pos[0], target_pos[1], self.z), long)
[docs] def set_stepsize(self,stepsize):
'''
Updates the stepsize used in step() function
'''
self.stepsize = stepsize
[docs] def step(self, direction):
'''
- Move Stage in either direction by one step
- Step Size defined in set_stepsize()
- direction expected to be char: U(p),D(own) L(eft),R(ight) F(orward),B(ackward)
'''
if self.x_axis.moving == False:
if direction == 'F':
if self.x_axis.delta_transl(self.stepsize):
self.x += self.stepsize
if direction == 'B':
if self.x_axis.delta_transl(-self.stepsize):
self.x -= self.stepsize
if self.y_axis.moving == False:
if direction == 'L':
if self.y_axis.delta_transl(self.stepsize):
self.y += self.stepsize
if direction == 'R':
if self.y_axis.delta_transl(-self.stepsize):
self.y -= self.stepsize
if self.z_axis.moving == False:
if direction == 'U':
if self.z_axis.delta_transl(-self.stepsize):
self.z -= self.stepsize
if direction == 'D':
if self.z_axis.delta_transl(self.stepsize):
self.z += self.stepsize
[docs] def get_coordinates(self):
'''
Returns the current position of the XYZ-Stage relative to the set origin
'''
return (self.x, self.y, self.z)
[docs] def get_coor_2d(self):
'''
Returns the current position of the XYZ-Stage relative to the set origin
'''
return (self.x, self.y)
[docs] def get_real_coordinates(self):
'''
Returns the actual position of the XYZ-Stage (relative to homing)
'''
return t_add((self.x, self.y, self.z), self.zeros)
[docs] def set_as_zero(self, new_zero):
'''
Change the origin
new_zero (3-tuple): (x,y,z) coordinates of the new origin
'''
actual = t_add((self.x, self.y, self.z), self.zeros)
self.x, self.y, self.z = t_sub(actual, new_zero)
self.zeros = new_zero
[docs] def set_cur_as(self, correct_pos):
'''
Changes the coordinates of the current position, esentially
computing the new origin. Does NOT involve any motor movement
correct_pos (3-tuple): (x,y,z) coordinates of the "corect" position
'''
zero_trans = t_sub((self.x, self.y, self.z), correct_pos)
self.set_as_zero(t_add(self.zeros, zero_trans))
[docs] def set_cur_as_2d(self, correct_pos):
'''
Changes the coordinates of the current position, esentially
computing the new origin. Does NOT involve any motor movement
correct_pos (2-tuple): (x,y) coordinates of the "corect" position
'''
correct_pos_3 = (correct_pos[0], correct_pos[1], self.z)
self.set_cur_as(correct_pos_3)
[docs] def set_level(self, ang_x, ang_y):
'''
set variables for virtual leveling
Note: the axes are relative to the stage
ang_x (float): angle in x-axis
ang_y (float): anfle in y-axis
'''
self.ang_x = ang_x
self.ang_y = ang_y
[docs] def leveled(self):
'''
Check if virtual leveling parameters have been set
'''
x = self.ang_x == None
y = self.ang_y == None
if not x :
if y:
raise AssertionError('Incorrect virtual level initialization')
else:
return True
else:
return False
[docs] def close(self):
'''
Closes the underlying motors
'''
self.x_axis.close()
self.y_axis.close()
self.z_axis.close()
'''
Copyright (C) 2017 Robert Polster
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''