Attached is a .gif file that I hope will play on this forum. It is a pan around the surface.
Here is the python code. You will need python 2.7 or 2.8 and also matplotlib, numpy, pyserial and wxpython python libraries. It has only been tried on windws system.
Code: Select all
#!/usr/bin/evn python
import serial
import matplotlib
from _winreg import SetValue
from _ast import Add
matplotlib.use('WXAgg')
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import math
import sys
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure
import wx.lib.scrolledpanel as scrolled
from scipy.interpolate import griddata
import wx
import scipy.linalg
from threading import Thread
import scipy.ndimage
from wx.lib.pubsub import pub
from matplotlib import cm
def wait(ser):
line = ser.readline()
while 'wait' not in line:
line = ser.readline()
def probeat(ser, x,y):
wait(ser)
ser.write('g30 x%d y%d\n'%(x,y))
line = ser.readline()
while 'PROBE-ZOFFSET' not in line:
line = ser.readline()
data = line.split(':')
print x,y ,float(data[1])
return float(data[1])
# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()
def EVT_RESULT(win, func):
"""Define Result Event."""
win.Connect(-1, -1, EVT_RESULT_ID, func)
class ResultEvent(wx.PyEvent):
"""Simple event to carry arbitrary result data."""
def __init__(self, data):
"""Init Result Event."""
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
self.data = data
class probThread(Thread):
"""Test Worker Thread Class."""
#----------------------------------------------------------------------
def __init__(self, wxObject):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.wxObject = wxObject
self.start() # start the thread
#----------------------------------------------------------------------
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
print 'started'
with serial.Serial( str(self.wxObject.combo_box_port.GetValue()), 250000, timeout=1) as ser:
line = ser.readline() # read one byte
while 'wait' not in line:
line = ser.readline()
print 'home'
ser.write('g28\n')
wait(ser)
ser.write('G0 x0 y0 z20\n')
wait(ser)
xval = list()
yval = list()
zval = list()
width = self.wxObject.maxRadius.Value
step = self.wxObject.stepSize.Value
with open(self.wxObject.fname, 'wt') as out:
for x in np.arange(-width ,width+step, step):
for y in range(-width, width + step, step):
r = math.sqrt(x * x + y * y)
if r <= width:
z = probeat(ser,x,y)
zval.append(z)
xval.append(x)
yval.append(y)
out.write("%d %d %lf\n"%(x,y,z))
if not self.wxObject.run:
break
if not self.wxObject.run:
break
ser.write('g28\n')
wx.PostEvent(self.wxObject, ResultEvent("Probing Done"))
# log to text control
class RedirectText:
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.WriteText(string)
class Frame(wx.Frame):
port = 'com3'
fname = 'surface.dat'
def getFileName(self, event):
# Create open file dialog
openFileDialog = wx.FileDialog(None, "Choose a file:",style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
if openFileDialog.ShowModal() == wx.ID_OK:
self.fname = openFileDialog.GetPath()
self.fileNameDisplay.SetValue(self.fname)
self.config.Write('fname',self.fname)
def fitSurface(self):
self.showRecent.Disable()
self.stopB.Disable()
self.startB.Disable()
data = np.genfromtxt(self.fname, delimiter = ' ', skip_header = 0)
#plt.contour(data)
#plt.show()
print self.fname
x = data[:,0]
y = data[:,1]
z = data[:,2]
# regular grid covering the domain of the data
X,Y = np.meshgrid(x,y)
order = 1 # 1: linear, 2: quadratic
if order == 1:
# best-fit linear plane
A = np.c_[x, y, np.ones(data.shape[0])]
C,_,_,_ = scipy.linalg.lstsq(A, z) # coefficients
plane = np.copy(z)
# subtract plane from data
for ndx in range(0, len(z)):
plane[ndx] = (x[ndx] * C[0] + y[ndx] * C[1] + C[2])
if self.removeTilt.IsChecked():
z -= plane
#z -= z.min()
x1 = np.linspace(x.min(), x.max(), len(np.unique(x)))
y1 = np.linspace(y.min(), y.max(), len(np.unique(y)))
x2, y2 = np.meshgrid(x1, y1)
z2 = griddata((x, y), z, (x2, y2), method='cubic')
# plot points and fitted surface
self.fig = plt.figure()
self.fig.canvas.mpl_connect('close_event', self.handle_close)
ax = self.fig.gca(projection='3d')
#ax.set_zlim([z.min(), z.max()])
if self.showSurface.Value:
#surf = ax.plot_trisurf(x,y,z, cmap = self.maps.GetString(self.maps.GetSelection()), linewidth = 0, shade = True)
if not self.contourOnly.Value:
c2 = ax.plot_surface(x2, y2, z2, rstride=1, cstride=1, cmap = self.maps.GetString(self.maps.GetSelection()),
vmin = z.min(), vmax = z.max(), linewidth=.2, antialiased=True)
c = plt.contour(x2,y2,z2, cmap = 'spectral', vmin = z.min(), vmax = z.max(), inline = 1,linewidths = 2)
self.fig.colorbar(c2, shrink=0.5, aspect=5)
self.fig.colorbar(c, shrink = .5, orientation = 'horizontal',label = 'contour lines mm')
else:
c = plt.contour(x2,y2,z2, cmap = self.maps.GetString(self.maps.GetSelection()) , vmin = z.min(), vmax = z.max())
self.fig.colorbar(c, shrink=0.5, aspect=5)
if self.showTiltPlane.Value:
ax.plot_trisurf(x,y,plane, alpha = .3, linewidth = .1)
plt.xlabel('X mm')
plt.ylabel('Y mm')
if self.removeTilt.IsChecked():
plt.title(' tilt removed ')
plt.suptitle('Bed Surface mm')
ax.set_zlabel('Z mm')
ax.axis('equal')
ax.axis('tight')
ax.set_zlim([z.min() - .1,(11 - self.zMag.Value) * z.max()])
fig_manager = plt.get_current_fig_manager()
fig_manager.frame.Maximize(True)
plt.show()
def showSurface(self, event):
self.fitSurface()
def clickedMap(self,evt):
self.fitSurface()
def __init__(self, title):
wx.Frame.__init__(self, None, title=title)
self.config = wx.Config('3DSurface')
self.fname = self.config.Read('fname','surface.dat')
gbs = self.gbs = wx.GridBagSizer(20, 6)
self.log = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE)
gbs.Add(self.log, (5,0),(10,6), wx.ALL|wx.EXPAND,5)
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quit application')
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.serial = serial.Serial()
gbs.Add(wx.StaticText(self, -1, "Port:"), (0,0),(1,1),wx.ALL, 5)
#port combo box
self.combo_box_port = wx.ComboBox(self, -1, choices=["dummy1", "dummy2", "dummy3", "dummy4", "dummy5"], style=wx.CB_DROPDOWN)
#fill in ports and select current setting
index = 0
self.combo_box_port.Clear()
for n in range(3,0,-1):
portname = serial.device(n)
self.combo_box_port.Append(portname)
if self.serial.portstr == portname:
index = n
if self.serial.portstr is not None:
self.combo_box_port.SetValue(str(self.serial.portstr))
else:
self.combo_box_port.SetSelection(index)
self.combo_box_port.SetStringSelection(self.config.Read('port', 'com3'))
self.Bind(wx.EVT_COMBOBOX, self.setPort, self.combo_box_port, -1, -1)
gbs.Add(self.combo_box_port, (0,1),(1,1), wx.ALL, 5)
self.maps = plt.cm.datad
gbs.Add(wx.StaticText(self,-1,'MaxRadius mm:'),(2,0), flag = wx.ALIGN_RIGHT)
self.maxRadius = wx.SpinCtrl(self, -1, min = 1, max = 180, initial = int(self.config.Read('maxRadius', '125')))
self.Bind(wx.EVT_SPINCTRL, self.spinchanged, self.maxRadius)
gbs.Add(self.maxRadius,(2,1))
gbs.Add(wx.StaticText(self,-1,'Step size mm:'),(2,2),flag = wx.ALIGN_RIGHT)
self.stepSize = wx.SpinCtrl(self, -1, "Step size mm")
self.stepSize.SetValue(int(self.config.Read('stepSize', '10')))
self.Bind(wx.EVT_SPINCTRL, self.spinchanged, self.stepSize)
self.stepSize.SetRange(1,50)
gbs.Add(self.stepSize, (2,3),flag = wx.RIGHT)
self.contourOnly = wx.CheckBox(self,-1,'Show Contour only')
gbs.Add(self.contourOnly,(1,4))
gbs.Add(wx.StaticText(self,-1, "Zheight mag:"),(2,4,),flag = wx.ALIGN_RIGHT)
self.zMag = wx.SpinCtrl(self, -1, min = 1 , max = 10, initial = 2)
gbs.Add(self.zMag, (2,5))
#=======================================================================
# z2 = wx.BoxSizer(wx.HORIZONTAL)
# z2.Add(wx.StaticText(self,-1,"ignore edge amount on plot:"))
# self.ignore = wx.SpinCtrl(self, -1)
# z2.Add(self.ignore)
# gbs.Add(z2, (1,4))
#=======================================================================
ll = sorted(matplotlib.cm.datad.keys())
z1 = wx.BoxSizer(wx.VERTICAL)
z1.Add(wx.StaticText(self,-1, "ColorMaps:"),flag = wx.ALIGN_BOTTOM)
self.maps = wx.ListBox(self, -1, choices = ll)
self.maps.SetStringSelection('paired')
self.Bind(wx.EVT_LISTBOX_DCLICK, self.clickedMap, self.maps)
z1.Add(self.maps)
gbs.Add(z1, (2,6),(10,1))
self.showRecent = wx.Button(self, -1, 'Plot Saved Surface')
self.Bind(wx.EVT_BUTTON, self.showSurface, self.showRecent)
gbs.Add(self.showRecent,(0,5),flag = wx.ALIGN_CENTER)
self.fileNameDisplay = wx.TextCtrl(self,-1, self.fname)
self.fileNameDisplay.Disable()
gbs.Add( self.fileNameDisplay,(0,2),(1,2), wx.ALL | wx.EXPAND, 5)
browse = wx.Button(self, -1, "...")
self.Bind(wx.EVT_BUTTON, self.getFileName, browse)
gbs.Add(browse,(0,4),(1,1), wx.ALL | wx.EXPAND, 5)
self.startB = wx.Button(self, -1, 'Start probing')
gbs.Add(self.startB,(3,2))
self.Bind(wx.EVT_BUTTON, self.start, self.startB)
self.stopB = wx.Button(self, -1, 'Stop probing')
gbs.Add(self.stopB,(3,3))
self.Bind(wx.EVT_BUTTON, self.stop, self.stopB)
self.removeTilt = wx.CheckBox(self, -1, 'remove tilt')
self.removeTilt.SetValue(True)
self.showTiltPlane = wx.CheckBox(self, -1, 'Show tilt')
gbs.Add(self.removeTilt,(1,1))
gbs.Add(self.showTiltPlane, (1,2))
self.showSurface = wx.CheckBox(self, -1, 'ShowSurface')
self.showSurface.SetValue(True)
gbs.Add(self.showSurface, (1,3))
gbs.AddGrowableCol(2)
self.SetSizer(gbs)
self.SetClientSize(self.GetBestSize())
# Set up event handler for any worker thread results
EVT_RESULT(self, self.threadMsg)
redir=RedirectText(self.log)
sys.stdout=redir
sys.stderr = redir
def spinchanged(self,event):
self.config.Write('maxRadius', str(self.maxRadius.Value))
self.config.Write('stepSize', str(self.stepSize.Value))
def start(self,event):
self.run = True
self.startB.Disable()
self.th = probThread(self)
def stop(self, event):
self.run = False
self.startB.Enable()
pass
def setPort(self,event):
self.config.Write('port',self.combo_box_port.GetValue())
def handle_close(self, evt):
self.stopB.Enable()
self.startB.Enable()
self.showRecent.Enable()
#----------------------------------------------------------------------
def threadMsg(self, msg):
"""
Receives data from thread and updates the display
"""
t = msg.data
if t == 'Probing Done':
self.startB.Enable()
self.fitSurface()
print 'done'
app = wx.App(redirect=False) # Error messages go to popup window
top = Frame("Measure 3D printer surface")
top.Show()
app.MainLoop()