Advertisement
Guest User

Deltaprinter python script

a guest
May 8th, 2015
913
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.55 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. import matplotlib
  4. import matplotlib.pyplot as plt
  5. import numpy as np
  6. import copy
  7. import scipy.io
  8. import scipy.interpolate
  9. from matplotlib.mlab import griddata
  10.  
  11. #not necessarily radially symmetric, but rod lengths still maintain parallel sides
  12. class DeltaPrinter(object):
  13.     L = 270     #rod lengths, kept constant to maintain pantograph constraints
  14.     Offset_e = 36.27        #assuming round and constant effector offsets in Y only
  15.     Offset_c = 38       #assuming constant carriage offsets in Y only
  16.     R = 199.5           #assuming constant column radii
  17.    
  18.     #vectors from center of effector to attachment points (local view, y-axis outwards from effector towards carriage)
  19.     Ae = np.array([0,0])   
  20.     Be = np.array([0,0])
  21.     Ce = np.array([0,0])
  22.    
  23.     #carriage offsets from column to arm attachment (local view, y-axis towards effector, z-axis upwards) - should only be adjusted for error analysis purposes??
  24.     Ac = np.array([0,0])
  25.     Bc = np.array([0,0])
  26.     Cc = np.array([0,0])
  27.    
  28.     Hz = 0      #offset of nozzle end from effector plane (just additional vertical offset)
  29.    
  30.     #column radii from center of build plate - flexibility for independent assignment
  31.     Ar = 180;
  32.     Br = 180;
  33.     Cr = 180;
  34.    
  35.     #column angle offset, shouldn't be adjusted (ever?)
  36.     Aa = 0
  37.     Ba = 2*np.pi/3
  38.     Ca = 4*np.pi/3
  39.    
  40.     #virtual pivot locations - not set yet, calculated during ik setup
  41.     Av = None
  42.     Bv = None
  43.     Cv = None
  44.     dCol = None                 #combination of virtual pivots for easier indexing
  45.    
  46.     def __init__(self):
  47.         self.R = 199.5          #will trigger extra variable assignments via __setattr__
  48.         self.Offset_e = 36.27
  49.         self.Offset_c = 38
  50.         self._updatePrinter()   #will set the virtual pivot locations
  51.  
  52.     #helper function used to debug issues w/ fk/ik
  53.     def _rotateTowers(self,ang):
  54.         print "Towers at angles "+repr(np.array([self.Aa,self.Ba,self.Ca]))
  55.         self.Aa += ang
  56.         self.Ba += ang
  57.         self.Ca += ang
  58.         self._updatePrinter()
  59.         print "Towers now at angles "+repr(np.array([self.Aa,self.Ba,self.Ca]))
  60.        
  61.     #setter functions will make additional call to _updatePrinter() to re-generate virtual positions
  62.         #dangerous! need to watch out for loops in callback
  63.     def __setattr__(self,name,value):
  64.         if name=='Offset_e':
  65.             self.Ae=np.array([0.,value])
  66.             self.Be=np.array([0.,value])
  67.             self.Ce=np.array([0.,value])
  68.         elif name=='Offset_c':
  69.             self.Ac=np.array([0.,value])
  70.             self.Bc=np.array([0.,value])
  71.             self.Cc=np.array([0.,value])
  72.         elif name=='R':
  73.             self.Ar=value
  74.             self.Br=value
  75.             self.Cr=value
  76.        
  77.         super(DeltaPrinter,self).__setattr__(name,value)    #still make set changes as normal to bulk variables
  78.    
  79.     #calculate pivot and virtual positions by reducing system to point intersection >> changes system definition
  80.         #private function to make ik/fk calculations faster >> only called if system parameters changed
  81.     def _updatePrinter(self):
  82.         #rotation matrices for each column (offsets given per local frames)
  83.         Ra = np.matrix([[np.cos(np.pi/2-self.Aa), -np.sin(np.pi/2-self.Aa)],[np.sin(np.pi/2-self.Aa), np.cos(np.pi/2-self.Aa)]])
  84.         Rb = np.matrix([[np.cos(np.pi/2-self.Ba), -np.sin(np.pi/2-self.Ba)],[np.sin(np.pi/2-self.Ba), np.cos(np.pi/2-self.Ba)]])
  85.         Rc = np.matrix([[np.cos(np.pi/2-self.Ca), -np.sin(np.pi/2-self.Ca)],[np.sin(np.pi/2-self.Ca), np.cos(np.pi/2-self.Ca)]])
  86.    
  87.         #column locations (x,y in global frame)
  88.         A = np.array([self.Ar*np.cos(self.Aa),self.Ar*np.sin(self.Aa)])
  89.         B = np.array([self.Br*np.cos(self.Ba),self.Br*np.sin(self.Ba)])
  90.         C = np.array([self.Cr*np.cos(self.Ca),self.Cr*np.sin(self.Ca)])
  91.    
  92.         #calculate column pivot locations, applying carriage offsets (global frame)
  93.         Ap = A-self.Ac*Ra
  94.         Bp = B-self.Bc*Rb
  95.         Cp = C-self.Cc*Rc
  96.        
  97.         #calculate virtual pivot locations, applying effector offsets
  98.         self.Av = Ap-self.Ae*Ra
  99.         self.Bv = Bp-self.Be*Rb
  100.         self.Cv = Cp-self.Ce*Rc
  101.         self.dCol = np.vstack((self.Av,self.Bv,self.Cv));   #combines virtual tower locations for better indexing
  102.    
  103.     def getR(self):
  104.         R_min = np.array([self.Ar,self.Br,self.Cr]).min()
  105.         R_max = np.array([self.Ar,self.Br,self.Cr]).max()
  106.         if R_min==R_max:
  107.             self.R = R_max
  108.         return np.array([R_min,R_max])
  109.    
  110.     #forward kinematics: takes in control heights for each column
  111.         #via circumcenter approach that apparently doesn't always work
  112.     def fk2(self,Az,Bz,Cz):
  113.         self._updatePrinter()
  114.    
  115.         #calculate circumcenter from projected virtual triangle
  116.         Pa = np.array([self.Av[0,0], self.Av[0,1], Az])
  117.         Pb = np.array([self.Bv[0,0], self.Bv[0,1], Bz])
  118.         Pc = np.array([self.Cv[0,0], self.Cv[0,1], Cz])
  119.        
  120.         alpha = (np.power(np.linalg.norm(Pb-Pc),2)*np.vdot(Pa-Pb,Pa-Pc)) / (2*np.power(np.linalg.norm(np.cross(Pa-Pb,Pb-Pc)),2))
  121.         beta = (np.power(np.linalg.norm(Pa-Pc),2)*np.vdot(Pb-Pa,Pb-Pc)) / (2*np.power(np.linalg.norm(np.cross(Pa-Pb,Pb-Pc)),2))
  122.         gamma = (np.power(np.linalg.norm(Pa-Pb),2)*np.vdot(Pc-Pa,Pc-Pb)) / (2*np.power(np.linalg.norm(np.cross(Pa-Pb,Pb-Pc)),2))
  123.         r = (np.linalg.norm(Pa-Pb) * np.linalg.norm(Pb-Pc) * np.linalg.norm(Pc-Pa)) / (2 * np.linalg.norm(np.cross(Pa-Pb,Pb-Pc)))
  124.        
  125.         P = alpha*Pa + beta*Pb + gamma*Pc
  126.         N = np.cross(Pa-Pc,Pb-Pa)
  127.         nn = np.sqrt(np.power(self.L,2)-np.power(r,2))  #normal length from virtual carriage triangle to effector point
  128.        
  129.         E = P+N/np.linalg.norm(N) * nn
  130.         return E
  131.  
  132.     #using trilateration as suggested by Steve Graves document
  133.         #uses virtual tower placements and rod lengths (avoids effector size)
  134.     def fk(self,Az,Bz,Cz):
  135.         dZ = np.array([Az,Bz,Cz])
  136.         dColP = np.zeros([3,3])
  137.         for i in xrange(3):
  138.             dColP[i][0] = self.dCol[i,0]
  139.             dColP[i][1] = self.dCol[i,1]
  140.             dColP[i][2] = dZ[i]
  141.        
  142.         #using same notation as Steve Graves example:
  143.         p12 = dColP[1] - dColP[0]
  144.         d = np.linalg.norm(p12)
  145.         ex = p12 / d
  146.        
  147.         p13 = dColP[2] - dColP[0]
  148.         i = np.dot(ex,p13)
  149.         iex = i*ex
  150.        
  151.         ey = p13 - iex
  152.         j = np.linalg.norm(ey)
  153.         ey = ey / j
  154.        
  155.         ez = np.cross(ex,ey)
  156.        
  157.         Xnew = d/2
  158.         Ynew = ((i*i + j*j)/2 - i*Xnew)/j
  159.         Znew = np.sqrt(self.L*self.L - Xnew*Xnew - Ynew*Ynew)
  160.        
  161.         cartesian = dColP[0] + (Xnew * ex) + (Ynew * ey) + (-Znew * ez)
  162.         return cartesian
  163.        
  164.     #_updatePrinter() need to be called prior to ik if system parameters have been changed 
  165.     def ik(self,X,Y,Z):
  166.         #perform quick check to ensure that (x,y,z) is within valid envelope:
  167.         R_max = self.getR()[1]
  168.         if np.linalg.norm(np.array([X,Y])) > R_max or Z<0:
  169.             #print "ERROR: x,y,z beyond valid envelope"
  170.             return np.array([-1,-1,-1])
  171.    
  172.         Acz = np.sqrt(np.power(self.L,2) - np.power(X-self.Av[0,0],2) - np.power(Y-self.Av[0,1],2))
  173.         Bcz = np.sqrt(np.power(self.L,2) - np.power(X-self.Bv[0,0],2) - np.power(Y-self.Bv[0,1],2))
  174.         Ccz = np.sqrt(np.power(self.L,2) - np.power(X-self.Cv[0,0],2) - np.power(Y-self.Cv[0,1],2))
  175.        
  176.         Az = Z+Acz+self.Hz
  177.         Bz = Z+Bcz+self.Hz
  178.         Cz = Z+Ccz+self.Hz
  179.         return np.array([Az,Bz,Cz])
  180.    
  181.     #checks that the control inputs are all valid
  182.     def _isValid(self,abc):
  183.         return (abc>0).all()
  184.    
  185.     #randomize set of points and compare ik w/ fk
  186.         #don't particularly care about z-sampling, as offsets in ik/fk are trivial
  187.         #if issues arise here for any sampling, we are in trouble
  188.     def _validate(self,N=10):
  189.         self._updatePrinter()
  190.         z = 0       #arbitrary z value selection
  191.        
  192.         R_min = self.getR()[0]
  193.         err_sum = 0.
  194.         for i in xrange(N):
  195.             r = np.random.rand() * R_min
  196.             ang = np.random.rand() * np.pi * 2.0
  197.             x = r*np.cos(ang)
  198.             y = r*np.sin(ang)
  199.            
  200.             ABC = self.ik(x,y,z)
  201.             XYZ = self.fk(ABC[0],ABC[1],ABC[2])
  202.            
  203.             err = np.linalg.norm(XYZ-np.array([x,y,z]))
  204.             err_sum += err
  205.             print "Iteration "+repr(i+1)+": error "+repr(err)
  206.             print "Initial XYZ: "+repr(np.array([x,y,z]))
  207.             print "Column vals: "+repr(ABC)
  208.             print "Final XYZ: "+repr(XYZ)+"\n"
  209.  
  210.         print "Random Test Average error: "+repr(err_sum/N)+"\n"
  211.        
  212.         #perform additional rotational test w/ respect to towers to find potential errors:
  213.         tower_ang = np.array([self.Aa,self.Ba,self.Ca])
  214.         rot_err_sum = 0.
  215.         for i in xrange(N):
  216.             r = np.random.rand() * R_min
  217.             tower_abc = np.zeros([tower_ang.size,3])
  218.             for j in xrange(tower_ang.size):
  219.                 ang = tower_ang[j]
  220.                 x = r*np.cos(ang)
  221.                 y = r*np.sin(ang)
  222.                 tower_abc[j] = self.ik(x,y,z)
  223.             tower_abc_sum = tower_abc.sum(0)
  224.             print "Rotational test sum for radius "+repr(r)+": "+repr(tower_abc_sum)
  225.        
  226.     #determines how much control values should be modified for a given step change in cartesian space
  227.     #evaluated at (x,y,z) for a workspace step dx averaged over N number of evenly sampled radial directions
  228.     def evaluateLocalStep(self,x,y,z=0,dx=1,N=12):
  229.         abc = self.ik(x,y,z)
  230.         dabc = np.zeros(3)
  231.         dn = 0
  232.         if self._isValid(abc):
  233.             ang_step = 2*np.pi/N
  234.             for ni in xrange(N):
  235.                 ang = ni*ang_step
  236.                 dxi = dx * np.cos(ang)
  237.                 dyi = dx * np.sin(ang)
  238.                
  239.                 abc2 = self.ik(x+dxi,y+dyi,z)
  240.                 if self._isValid(abc2):
  241.                     dabc = dabc+abs(abc-abc2)
  242.                     dn += 1
  243.             if dn>0:
  244.                 dabc = dabc/dn
  245.         return dabc
  246.    
  247.     #N: angular intervals to sample (12 to accommodate 4 cartesian neighbors and 3 towers)
  248.     #dx:
  249.     def evaluateStep(self,points=None,dx=1,N=12,plot=False):
  250.         ang_step = 2*np.pi/N
  251.        
  252.         z = 0
  253.         R_min = self.getR()[0]
  254.        
  255.         if points is None:
  256.             points = genGrid(R_min)
  257.            
  258.         res = np.zeros([points.shape[0],6]) #[x,y,r,avg_da,avg_db,avg_dc]
  259.         ri = 0
  260.         for i in xrange(points.shape[0]):
  261.             x = points[i][0]
  262.             y = points[i][1]
  263.            
  264.             dabc = self.evaluateLocalStep(x,y,z,dx,N)
  265.            
  266.             if dabc.any():
  267.                 res[ri] = np.array([x,y,np.linalg.norm([x,y]),dabc[0],dabc[1],dabc[2]])
  268.                 ri+=1
  269.  
  270.         res = res[0:ri,:]   #trim the result array
  271.        
  272.         if plot:
  273.             #optional plotting for step sizes:
  274.            
  275.             #3 subplots for individual tower resolution requirements:
  276.             fig1 = plt.figure(1)
  277.            
  278.             plt.subplot(131)
  279.             cs11 = plotContourf(res[:,0],res[:,1],res[:,3])
  280.             plt.title('Tower A')
  281.            
  282.             plt.subplot(132)
  283.             cs12 = plotContourf(res[:,0],res[:,1],res[:,4])
  284.             plt.title('Tower B')
  285.            
  286.             plt.subplot(133)
  287.             cs13 = plotContourf(res[:,0],res[:,1],res[:,5])
  288.             plt.title('Tower C')
  289.            
  290.             fig1.subplots_adjust(bottom=0.2)
  291.             cbar1_ax = fig1.add_axes([0.05, 0.05, 0.9, 0.05])
  292.             fig1.colorbar(cs13,cax=cbar1_ax,orientation='horizontal')
  293.        
  294.             fig2 = plt.figure(2)
  295.            
  296.             plt.subplot(141)
  297.             t_min = np.amin(res[:,3:6],axis=1)
  298.             cs21 = plotContourf(res[:,0],res[:,1],t_min)
  299.             fig2.colorbar(cs21)
  300.             plt.title('Min Tower Resolution')
  301.            
  302.             plt.subplot(142)
  303.             t_max = np.amax(res[:,3:6],axis=1)
  304.             cs22 = plotContourf(res[:,0],res[:,1],t_max)
  305.             fig2.colorbar(cs22)
  306.             plt.title('Max Tower Resolution')
  307.            
  308.             plt.subplot(143)
  309.             t_mean = np.mean(res[:,3:6],axis=1)
  310.             cs23 = plotContourf(res[:,0],res[:,1],t_mean)
  311.             fig2.colorbar(cs23)
  312.             plt.title('Mean Tower Resolution')
  313.            
  314.             plt.subplot(144)
  315.             t_std = np.std(res[:,3:6],axis=1)
  316.             cs24 = plotContourf(res[:,0],res[:,1],t_std)
  317.             fig2.colorbar(cs24)
  318.             plt.title('Tower Std Deviation')
  319.            
  320.             fig1.tight_layout()
  321.             fig2.tight_layout()
  322.            
  323.             plt.show()
  324.         return res
  325.        
  326.     #determines the top curvature of the workspace that is achievable (essentially details the meniscus that forms)
  327.     #low points of meniscus found where arms are entirely vertical
  328.     def evaluateShape(self,points=None,plot=False):
  329.         if points is None:
  330.             points = genGrid(self.getR()[0])
  331.         z = 0
  332.         ri = 0
  333.         points = np.vstack((np.array([0,0]),points))    #initial seed is always center point
  334.         res = np.zeros([points.shape[0],11])
  335.         for i in xrange(points.shape[0]):
  336.             x = points[i][0]
  337.             y = points[i][1]
  338.            
  339.             abc = self.ik(x,y,z)    #tower heights
  340.             abc_elevation = np.zeros(3)
  341.             abc_azimuth = np.zeros(3)
  342.             #calculate effector angles (projected on rz, xz planes)
  343.             for j in xrange(3): #virtual tower positions found in dCol
  344.                 L_xy = np.linalg.norm(np.array([x,y])-np.array([self.dCol[j,0],self.dCol[j,1]]))
  345.                 L_z = abc[j]-(z+self.Hz)    #z specifies nozzle point
  346.                 abc_elevation[j] = np.arctan2(L_z,L_xy)
  347.                 abc_azimuth[j] = np.arctan2(self.dCol[j,1]-y,self.dCol[j,0]-x)
  348.            
  349.             if self._isValid(abc):
  350.                 res[ri] = np.array([x,y,abc[0],abc[1],abc[2],abc_elevation[0],abc_elevation[1],abc_elevation[2],abc_azimuth[0],abc_azimuth[1],abc_azimuth[2]])  #every index should have valid solutions
  351.                 ri+=1
  352.        
  353.         res = res[0:ri,:]
  354.        
  355.         if plot:
  356.             fig1 = plt.figure(1)
  357.             t_max = np.amax(res[:,2:5],axis=1)
  358.             cs = plotContourf(res[:,0],res[:,1],t_max)
  359.             fig1.colorbar(cs)
  360.             plt.title('Max Tower Position')
  361.  
  362.             fig2 = plt.figure(2)
  363.             theta_elevation = res[:,5:8]-res[0,5:8] #relative to initial center seed point
  364.             theta_azimuth = res[:,8:11]-res[0,8:11]
  365.  
  366.             plt.subplot(231)
  367.             cs_e0 = plotContourf(res[:,0],res[:,1],theta_elevation[:,0])
  368.             fig2.colorbar(cs_e0)
  369.             plt.title('Elevation A')
  370.             plt.subplot(232)
  371.             cs_e1 = plotContourf(res[:,0],res[:,1],theta_elevation[:,1])
  372.             fig2.colorbar(cs_e1)
  373.             plt.title('Elevation B')
  374.             plt.subplot(233)
  375.             cs_e2 = plotContourf(res[:,0],res[:,1],theta_elevation[:,2])
  376.             fig2.colorbar(cs_e2)
  377.             plt.title('Elevation C')
  378.  
  379.             plt.subplot(234)
  380.             cs_a0 = plotContourf(res[:,0],res[:,1],theta_azimuth[:,0])
  381.             fig2.colorbar(cs_a0)
  382.             plt.title('Azimuth A')
  383.             plt.subplot(235)
  384.             cs_a1 = plotContourf(res[:,0],res[:,1],theta_azimuth[:,1])
  385.             fig2.colorbar(cs_a1)
  386.             plt.title('Azimuth B')
  387.             plt.subplot(236)
  388.             cs_a2 = plotContourf(res[:,0],res[:,1],theta_azimuth[:,2])
  389.             fig2.colorbar(cs_a2)
  390.             plt.title('Azimuth C')
  391.                
  392.             plt.show()
  393.        
  394.         return res
  395.        
  396.     #returns basic, summarized results for a given delta design
  397.     def eval(self,points=None):
  398.         if points is None:
  399.             points = genGrid(self.getR()[0])
  400.             #for comparisons, might want to feed in
  401.            
  402.         res_shape = self.evaluateShape(points)
  403.         #find amount of vertical space that is lost for the given workspace points (should be capped at arm length value)
  404.         #tower_max = np.amax(res_shape[:,2:5])  #max tower position
  405.         tower_max = np.zeros(3)
  406.         tower_min = np.zeros(3)
  407.         travel_max = np.zeros(3)                #determine max amount of tower travel from center position (relative positioning) - no need for travel_min (just 0,0,0 for center)
  408.         elevation_max = np.zeros(3)
  409.         elevation_min = np.zeros(3)
  410.         azimuth_max = np.zeros(3)
  411.         azimuth_min = np.zeros(3)
  412.         elevation_range = np.zeros(3)
  413.         azimuth_range = np.zeros(3)
  414.        
  415.         print "\n"
  416.         print "-- Delta Shape Summary --"
  417.         for i in xrange(3):
  418.             tower_max[i] = res_shape[:,2+i].max()
  419.             tower_min[i] = res_shape[:,2+i].min()
  420.             tower_travel = res_shape[:,2+i]-res_shape[0,2+i]    #relative to center position for current tower
  421.             travel_max[i] = tower_travel.max()
  422.             elevation_max[i] = res_shape[:,5+i].max()
  423.             elevation_min[i] = res_shape[:,5+i].min()
  424.             azimuth_max[i] = res_shape[:,8+i].max()
  425.             azimuth_min[i] = res_shape[:,8+i].min()
  426.             elevation_range[i] = elevation_max[i]-elevation_min[i]
  427.             azimuth_range[i] = azimuth_max[i]-azimuth_min[i]
  428.        
  429.         print "Max Tower Position: "+repr(tower_max)
  430.         print "Min Tower Position: "+repr(tower_min)
  431.         print "Max Tower Travel from Center: "+repr(travel_max)
  432.        
  433.         res_step = self.evaluateStep(points)
  434.         #used to determine required resolution of tower motion for step motion in cartesian workspace
  435.         res_min = np.zeros(3)
  436.         res_max = np.zeros(3)
  437.         res_mean = np.zeros(3)
  438.         res_std = np.zeros(3)
  439.         for i in xrange(3):
  440.             res_min[i] = res_step[:,3+i].min()
  441.             res_max[i] = res_step[:,3+i].max()
  442.             res_mean[i] = res_step[:,3+i].mean()
  443.             res_std[i] = res_step[:,3+i].std()
  444.            
  445.         print "\n"
  446.         print "-- Delta Motion for Step Cartesian Move --"
  447.         print "Min Tower Motion: "+repr(res_min)
  448.         print "Max Tower Motion: "+repr(res_max)
  449.         print "Avg Tower Motion: "+repr(res_mean)
  450.         print "Tower Motion STD: "+repr(res_std)
  451.            
  452.         res = np.vstack((tower_max,tower_min,travel_max,elevation_max,elevation_min,elevation_range,azimuth_max,azimuth_min,azimuth_range,res_min,res_max,res_mean,res_std))
  453.         return res
  454.            
  455.        
  456. #will use error values/designations similar to what is found in Repetier firmware
  457. class DeltaError(object):
  458.     delta0 = None
  459.     delta1 = None
  460.  
  461.     dTower = np.array([0.,0.,0.])   #related to z-limit errors for each tower - used to introduce tower noise, poor zero-ing
  462.     dL = 0.
  463.    
  464.     #ASSEMBLY ERRORS:
  465.     dOffset_e = 0.
  466.     dOffset_c = 0.
  467.    
  468.     #effector offset error
  469.     dAe = np.array([0,0])
  470.     dBe = np.array([0,0])
  471.     dCe = np.array([0,0])
  472.    
  473.     #carriage offset error
  474.     dAc = np.array([0,0])
  475.     dBc = np.array([0,0])
  476.     dCc = np.array([0,0])
  477.    
  478.     #BASE STRUCTURE ERRORS:
  479.     #radii of base towers - related to setting PRINTER_RADIUS, DELTA_RADIUS_CORRECTION
  480.     dAr = 0.
  481.     dBr = 0.
  482.     dCr = 0.
  483.    
  484.     #angular offset of base towers - related to DELTA_ALPHA_[A-C]
  485.     dAa = 0.
  486.     dBa = 0.
  487.     dCa = 0.
  488.  
  489.     def __init__(self,delta):
  490.         self.delta0 = copy.copy(delta)      #control delta
  491.         self.delta1 = copy.copy(delta)      #error delta - initially same as source
  492.        
  493.     def __setattr__(self,name,value):
  494.         rname = name[1:]    #remove possible 'd' prefix
  495.         if hasattr(self.delta0,rname):
  496.             setattr(self.delta1, rname, getattr(self.delta0,rname)+value)
  497.             print "Control Delta "+rname+": "+repr(getattr(self.delta0,rname))
  498.             print "Error Delta "+rname+": "+repr(getattr(self.delta1,rname))
  499.             self.delta1._updatePrinter()    #update virtual tower model internally
  500.        
  501.         super(DeltaError,self).__setattr__(name,value)
  502.        
  503.     #returns baseline path and corresponding path in error model
  504.     def eval(self,path=None,plot=False):
  505.         if path is None:
  506.             path = genGrid(self.delta0.getR()[0])
  507.         z = 0
  508.         ri = 0
  509.         res = np.zeros([path.shape[0],5])
  510.         for i in xrange(path.shape[0]):
  511.             x = path[i][0]
  512.             y = path[i][1]
  513.             abc = self.delta0.ik(x,y,z)
  514.             if self.delta0._isValid(abc):
  515.                 xyz = self.delta1.fk(abc[0]+self.dTower[0],abc[1]+self.dTower[1],abc[2]+self.dTower[2])     #implement tower offsets here
  516.                 res[ri] = np.array([x,y,xyz[0]-x,xyz[1]-y,xyz[2]-z])
  517.                 ri += 1
  518.         res = res[0:ri,:]
  519.        
  520.         if plot:
  521.             fig1 = plt.figure(1)
  522.             plt.subplot(131)    #x discrepancy
  523.             cs1 = plotContourf(res[:,0],res[:,1],res[:,2])
  524.             fig1.colorbar(cs1)
  525.             plt.title('x error')
  526.            
  527.             plt.subplot(132)    #y discrepancy
  528.             cs2 = plotContourf(res[:,0],res[:,1],res[:,3])
  529.             fig1.colorbar(cs2)
  530.             plt.title('y error')
  531.            
  532.             plt.subplot(133)    #z discrepancy
  533.             cs3 = plotContourf(res[:,0],res[:,1],res[:,4])
  534.             fig1.colorbar(cs3)
  535.             plt.title('z error')
  536.            
  537.             fig1.tight_layout()
  538.            
  539.             fig2 = plt.figure(2)
  540.             r = np.sqrt(res[:,0]*res[:,0]+res[:,1]*res[:,1])
  541.             r_err = np.sqrt(res[:,2]*res[:,2]+res[:,3]*res[:,3])
  542.            
  543.             ri = np.nonzero(r)      #removing the 0 radii components
  544.             r_err = r_err[ri]
  545.             r = r[ri]
  546.             r_ratio = r_err/r
  547.            
  548.             plt.subplot(121)    #r discrepancy w/ respect to distance from center
  549.             fit1 = np.polyfit(r,r_err,1)
  550.             fit1_fn = np.poly1d(fit1)
  551.             plt.plot(r,r_err,'.k')
  552.             plt.plot(r,fit1_fn(r),'-r')
  553.             plt.title('Error')
  554.             plt.xlabel('Radius')
  555.             plt.ylabel('Error')
  556.            
  557.             plt.subplot(122)    #ratio of r discrepancy to r
  558.             fit2 = np.polyfit(r,r_ratio,1)
  559.             plt.plot(r,r_ratio,'.k')
  560.             fit2_fn = np.poly1d(fit2)
  561.             plt.plot(r,fit2_fn(r),'-r')
  562.             plt.title('Error/Radius')
  563.             plt.ylabel('Error/Radius')
  564.             plt.xlabel('Radius')
  565.            
  566.             fig2.tight_layout()
  567.            
  568.             plt.show()
  569.            
  570.         return res
  571.  
  572. #HELPER FUNCTIONS:
  573. def plotContourf(x,y,z,contour_n=10,xyz_step=1):
  574.     xi = np.arange(x.min(),x.max(),xyz_step)
  575.     yi = np.arange(y.min(),y.max(),xyz_step)
  576.     zi = griddata(x,y,z,xi,yi,interp='linear')
  577.     cs = plt.contourf(xi,yi,zi,contour_n,cmap=plt.cm.gray)
  578.     plt.xlim(x.min()-10,x.max()+10)     #generate some gap along the edges for viewing
  579.     plt.ylim(y.min()-10,y.max()+10)
  580.     plt.xlabel('x')
  581.     plt.ylabel('y')
  582.     plt.axis('equal')
  583.     return cs
  584.    
  585. #generates a 2d, x-y path for a spiral path
  586.     #- r: max radius of spiral
  587.     #- nloop: number of loops in spiral
  588.     #- n: number of points
  589. def genSpiral(r=100,nloop=1,n=25):
  590.     rs = np.linspace(0,r,n)
  591.     ts = np.linspace(0,nloop*np.pi*2,n)
  592.    
  593.     x = rs*np.cos(ts)
  594.     y = rs*np.sin(ts)
  595.    
  596.     return np.vstack((x,y)).transpose()
  597.  
  598. #takes delta parameters to generate voxelized grid w/ given sampling step  
  599. #note that this generates a cartesian grid not necessarily centered in radial build space, so evaluation functions may not show symmetric results depending on sampling
  600. def genGrid(R,xy_step=5):
  601.     xx = np.arange(-R,R+xy_step,xy_step)
  602.     yy = np.arange(-R,R+xy_step,xy_step)
  603.    
  604.     g = np.zeros([xx.size*yy.size,2])   #we only care about xy grid making (z is usually constant in our tests)
  605.     ri = 0                                      #index-ing for results array
  606.     for xi in xrange(xx.size):
  607.         for yi in xrange(yy.size):
  608.             if np.linalg.norm(np.array([xx[xi],yy[yi]])) > R:           #invalid or nonexistent ik result
  609.                 continue
  610.             else:
  611.                 g[ri] = np.array([xx[xi],yy[yi]])
  612.                 ri+=1
  613.    
  614.     print "Grid generated w/ x ["+repr(g[:,0].min())+", "+repr(g[:,0].max())+"], y ["+repr(g[:,1].min())+", "+repr(g[:,1].max())+"]"
  615.     return g[0:ri,:]
  616.    
  617. if __name__=="__main__":
  618.     font = {'size':9}
  619.     matplotlib.rc('font',**font)
  620.    
  621.     d = DeltaPrinter()
  622.     de = DeltaError(d)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement