BedMapper program

Having a problem? Post it here and someone will be along shortly to help
Post Reply
Dale Eason
Printmaster!
Posts: 191
Joined: Fri Mar 06, 2015 8:56 pm

BedMapper program

Post by Dale Eason »

bedmapper.png
Note sure where to post this. But it could help any that have a HE280 Hot end. I created a python program that can map the bed surface using the latest SeeMeCNC firmware and the G30 command. This sampled the bed every 5mm and took about 45 minutes to complete. You can change the sample area and step size among other things. I found it interesting to try different values of horizontal radius and see what that did compared to the callibration selected value.

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()
Attachments
surface.gif
johndeere6110
Plasticator
Posts: 8
Joined: Wed Nov 09, 2016 5:14 pm
Location: Hem, The Netherlands

Re: BedMapper program

Post by johndeere6110 »

Nice programme. :D
I gonna try it tomorrow
:D rostock max v3 owner :D
Post Reply

Return to “Troubleshooting”