123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707 |
- #!/usr/bin/env python
-
- #
- # bed-leveling.py
- #
- # Written in June 2016 by:
- # Thomas Buck <xythobuz@xythobuz.de>
- #
- # Manual Mesh Bed Leveling GUI utility for 3D printers with Marlin Firmware.
- # At the time of me writing this program, Marlin had a bug that caused the
- # manual mesh bed leveling process to not work using the connected LCD display,
- # leaving G-Codes sent over the serial port as the only option. Because manually
- # inserting these in a terminal program proved to be very tedious, and none of the
- # G-Code senders I've tested had the features I wanted (mainly easily configurable
- # step sizes), I've wrote this simple little program.
- #
- # After starting the program, select the serial port connected to your printer,
- # the correct baudrate for communicating with it and press Connect.
- # The X, Y and Z value displayed are periodically polled from the printer and
- # are not very accurate. Zc is what is manipulated using the up- and down-arrow
- # buttons or keys. The value shown there will be more accurate.
- #
- # Before starting with the leveling procedure, the current Mesh info has to be
- # polled from the printer. This will happen automatically after connecting.
- # When ready, press Start, then adjust the Z-height for each measuring point
- # and press Next or the right-arrow key to move on. After you're finished,
- # you should store the new mesh data in the EEPROM using the Save button.
- #
- # Dependencies:
- # - pySerial (>= v3.0)
- # - wxPython
- #
- # This has been developed and tested on a Mac OS X 10.10.5 machine
- # with Python 2.7 and dependencies installed with MacPorts,
- # connected to a modified Fabrikator Mini V1.5 with Marlin 1.1.0-RC6.
- #
-
- import wx
- import serial
- import serial.threaded
- import serial.tools.list_ports
- import re
-
- POLL_SERIAL_PORTS_INTERVALL = 4000
- POLL_COORDINATES_INTERVALL = 2500
- POLL_TEMPERATURE_INTERVALL = 3000
- MESH_DATA_RETRY_TIMEOUT = 2000
- BUSY_PROCESSING_STEPS = 6;
- AVAILABLE_BAUD_RATES = ["2400", "9600", "19200", "38400", "57600", "115200", "250000"]
- DEFAULT_BAUD_RATE = "115200"
- DEFAULT_SERIAL_PORTS = ["/dev/cu.usbserial-AI02LQH7", "/dev/cu.SLAB_USBtoUART"]
-
- GCODE_EEPROM_SAVE = "M500"
- GCODE_STOP_IDLE_HOLD = "M84"
- GCODE_MOVE_TO_ORIGIN = "G28"
- GCODE_GET_CURRENT_POSITION = "M114"
- GCODE_MESH_INFO = "G29 S0"
- GCODE_MESH_START = "G29 S1"
- GCODE_MESH_NEXT = "G29 S2"
- GCODE_MOVE_Z = "G1 Z{}"
- GCODE_SET_BED_TEMP = "M140 S{}"
- GCODE_GET_TEMP = "M105"
-
- PRINTER_RESPONSE_OK = "ok"
- PRINTER_RESPONSE_BUSY = "echo:busy: processing"
- PRINTER_REGEX_COORDINATES = "^X:\d+\.\d+ Y:\d+\.\d+ Z:\d+\.\d+ E:\d+\.\d+ Count X:\s*\d+ Y:\s*\d+ Z:\s*\d+$"
- PRINTER_REGEX_MESH_POINTS = "^Num X,Y: \d,\d$"
- PRINTER_REGEX_MESH_Z = "^Z search height: \d\.*\d*$"
- PRINTER_REGEX_TEMP = "^ok T:\d\d*\.*\d* \/\d\d*\.*\d* B:\d\d*\.*\d* \/\d\d*\.*\d* B@:\d\d*\.*\d* @:\d\d*\.*\d*$"
-
- # Event for sending new XYZ coordinates from the serial thread to the GUI thread
- myEVT_COORDINATES = wx.NewEventType()
- EVT_COORDINATES = wx.PyEventBinder(myEVT_COORDINATES, 1)
- class CoordinatesEvent(wx.PyCommandEvent):
- def __init__(self, etype, eid, value = None):
- wx.PyCommandEvent.__init__(self, etype, eid)
- self.value = value
-
- def GetValue(self):
- return self.value
-
- # Event for sending new mesh point info
- myEVT_MESH_POINTS = wx.NewEventType()
- EVT_MESH_POINTS = wx.PyEventBinder(myEVT_MESH_POINTS, 1)
- class MeshPointEvent(wx.PyCommandEvent):
- def __init__(self, etype, eid, x = None, y = None):
- wx.PyCommandEvent.__init__(self, etype, eid)
- self.x = x
- self.y = y
-
- def GetX(self):
- return self.x
-
- def GetY(self):
- return self.y
-
- # Event for sending new mesh point info
- myEVT_MESH_Z = wx.NewEventType()
- EVT_MESH_Z = wx.PyEventBinder(myEVT_MESH_Z, 1)
- class MeshZEvent(wx.PyCommandEvent):
- def __init__(self, etype, eid, z = None):
- wx.PyCommandEvent.__init__(self, etype, eid)
- self.z = z
-
- def GetZ(self):
- return self.z
-
- # Event for sending temperature info
- myEVT_TEMPERATURE = wx.NewEventType()
- EVT_TEMPERATURE = wx.PyEventBinder(myEVT_TEMPERATURE, 1)
- class TemperatureEvent(wx.PyCommandEvent):
- def __init__(self, etype, eid, bed = None):
- wx.PyCommandEvent.__init__(self, etype, eid)
- self.bed = bed
-
- def GetBed(self):
- return self.bed
-
- # Event for sending a new busy indicator status update
- myEVT_STATUS = wx.NewEventType()
- EVT_STATUS = wx.PyEventBinder(myEVT_STATUS, 1)
- class StatusEvent(wx.PyCommandEvent):
- def __init__(self, etype, eid, value = None):
- wx.PyCommandEvent.__init__(self, etype, eid)
- self.value = value
-
- def GetValue(self):
- return self.value
-
- # Serial Thread reading from and writing to the printer, line-based
- class GCodeReader(serial.threaded.LineReader):
-
- TERMINATOR = b'\n'
-
- regexCoordinates = re.compile(PRINTER_REGEX_COORDINATES, re.M)
- regexMeshPoints = re.compile(PRINTER_REGEX_MESH_POINTS, re.M)
- regexMeshZ = re.compile(PRINTER_REGEX_MESH_Z, re.M)
- regexTemp = re.compile(PRINTER_REGEX_TEMP, re.M)
-
- def setParent(self, parent):
- self.parent = parent
-
- def handle_line(self, data):
- if data == PRINTER_RESPONSE_OK:
- event = StatusEvent(myEVT_STATUS, -1, 0)
- wx.PostEvent(self.parent, event)
- return
-
- if data == PRINTER_RESPONSE_BUSY:
- event = StatusEvent(myEVT_STATUS, -1, 1)
- wx.PostEvent(self.parent, event)
- return
-
- if self.regexCoordinates.match(data):
- afterX = data.split("X:")[1]
- x = afterX.split(" ")[0]
- afterY = data.split("Y:")[1]
- y = afterY.split(" ")[0]
- afterZ = data.split("Z:")[1]
- z = afterZ.split(" ")[0]
- str = "X: {} Y: {} Z: {}".format(x, y, z)
- event = CoordinatesEvent(myEVT_COORDINATES, -1, str)
- wx.PostEvent(self.parent, event)
- return
-
- if self.regexMeshPoints.match(data):
- firstDigit = data.split("X,Y: ")[1]
- x = firstDigit.split(",")[0]
- y = firstDigit.split(",")[1]
- event = MeshPointEvent(myEVT_MESH_POINTS, -1, x, y)
- wx.PostEvent(self.parent, event)
- return
-
- if self.regexMeshZ.match(data):
- num = data.split("Z search height: ")[1]
- event = MeshZEvent(myEVT_MESH_Z, -1, float(num))
- wx.PostEvent(self.parent, event)
- return
-
- if self.regexTemp.match(data):
- bed = data.split(" B:")[1]
- temp = bed.split(" /")[0]
- event = TemperatureEvent(myEVT_TEMPERATURE, -1, float(temp))
- wx.PostEvent(self.parent, event)
- return
-
- # Main Window class
- class WizardFrame(wx.Frame):
- def __init__(self, *args, **kwargs):
- wx.Frame.__init__(self, *args, **kwargs)
-
- self.hasQuit = False
- self.isConnected = False
- self.serial = serial.Serial()
- self.thread = serial.threaded.ReaderThread(self.serial, GCodeReader)
- self.isLeveling = False
- self.currentPoint = 0
- self.meshPoints = -1
- self.step = 0.025
- self.currentZ = 0.0
- self.startZ = 0.0
-
- self.Bind(EVT_COORDINATES, self.OnCoordinates)
- self.Bind(EVT_STATUS, self.OnStatus)
- self.Bind(EVT_MESH_POINTS, self.OnMeshPoints)
- self.Bind(EVT_MESH_Z, self.OnMeshZ)
- self.Bind(EVT_TEMPERATURE, self.OnTemperature)
-
- # Menubar and Items
- MenuBar = wx.MenuBar()
- FileMenu = wx.Menu()
- MenuBar.Append(FileMenu, "&File")
- self.SetMenuBar(MenuBar)
- item = FileMenu.Append(wx.ID_EXIT, text = "&Exit")
- self.Bind(wx.EVT_MENU, self.OnQuit, item)
- self.Bind(wx.EVT_CLOSE, self.OnQuit)
- self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- outerPanelSizer = wx.BoxSizer(wx.VERTICAL)
-
- # Panel for serial control widgets
- self.panelSerial = wx.Panel(self)
- panelSerialSizer = wx.BoxSizer(wx.HORIZONTAL)
- self.panelSerial.SetSizer(panelSerialSizer)
- outerPanelSizer.Add(self.panelSerial, 0, wx.TOP | wx.EXPAND, 4)
- self.panelSerial.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # List all available serial ports
- serialPortList = serial.tools.list_ports.comports()
- serialPortNames = []
- for port in serialPortList:
- serialPortNames.append(port[0])
-
- # Combobox for serial ports
- self.comboBoxSerial = wx.ComboBox(parent = self.panelSerial, choices = serialPortNames, style = wx.CB_READONLY)
- self.comboBoxSerial.SetMinSize((200, -1))
- panelSerialSizer.Add(self.comboBoxSerial, 1, wx.LEFT, 5)
- self.comboBoxSerial.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Select default serial port(s)
- for port in serialPortList:
- if port[0] in DEFAULT_SERIAL_PORTS:
- self.comboBoxSerial.SetStringSelection(port[0])
-
- # Regularly update serial port list
- wx.FutureCall(POLL_SERIAL_PORTS_INTERVALL, self.EnumerateSerialPorts)
-
- # Combobox for baudrates
- self.comboBoxBaud = wx.ComboBox(parent = self.panelSerial, choices = AVAILABLE_BAUD_RATES, style = wx.CB_READONLY)
- self.comboBoxBaud.SetMinSize((80, -1))
- panelSerialSizer.Add(self.comboBoxBaud, 0)
- self.comboBoxBaud.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- for baud in AVAILABLE_BAUD_RATES:
- if baud == DEFAULT_BAUD_RATE:
- self.comboBoxBaud.SetStringSelection(DEFAULT_BAUD_RATE)
-
- # Button for connecting and disconnecting
- self.buttonSerial = wx.Button(parent = self.panelSerial, label = "Connect")
- self.buttonSerial.Bind(wx.EVT_BUTTON, self.OnConnectDisconnect)
- panelSerialSizer.Add(self.buttonSerial, 0, wx.TOP, 2)
- panelSerialSizer.AddSpacer(5)
- self.buttonSerial.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Panel for status labels
- self.panelStatus = wx.Panel(self)
- panelStatusSizer = wx.BoxSizer(wx.HORIZONTAL)
- self.panelStatus.SetSizer(panelStatusSizer)
- outerPanelSizer.Add(self.panelStatus, 0, wx.EXPAND | wx.TOP, 3)
- self.panelStatus.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Labels for coordinates
- self.labelX = wx.StaticText(parent = self.panelStatus, label = "X: ??.??")
- panelStatusSizer.Add(self.labelX, 0, wx.LEFT, 5)
- panelStatusSizer.AddStretchSpacer(1)
- self.labelX.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- self.labelY = wx.StaticText(parent = self.panelStatus, label = "Y: ??.??")
- panelStatusSizer.Add(self.labelY, 0)
- panelStatusSizer.AddStretchSpacer(1)
- self.labelY.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- self.labelZ = wx.StaticText(parent = self.panelStatus, label = "Z: ??.??")
- panelStatusSizer.Add(self.labelZ, 0, wx.RIGHT, 5)
- panelStatusSizer.AddStretchSpacer(1)
- self.labelZ.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- self.labelZc = wx.StaticText(parent = self.panelStatus, label = "Zc: ??.???")
- panelStatusSizer.Add(self.labelZc, 0, wx.RIGHT, 5)
- self.labelZc.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Panel for general actions
- self.panelAction = wx.Panel(self)
- panelActionSizer = wx.BoxSizer(wx.HORIZONTAL)
- self.panelAction.SetSizer(panelActionSizer)
- outerPanelSizer.Add(self.panelAction, 0, wx.EXPAND | wx.TOP, 5)
- self.panelAction.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Button for homing
- self.buttonHome = wx.Button(parent = self.panelAction, label = "Home")
- self.buttonHome.Bind(wx.EVT_BUTTON, self.OnHome)
- self.buttonHome.Enable(False)
- panelActionSizer.AddSpacer(5)
- panelActionSizer.Add(self.buttonHome, 0, wx.BOTTOM, 1)
- self.buttonHome.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Button for steppers off
- self.buttonOff = wx.Button(parent = self.panelAction, label = "Off")
- self.buttonOff.Bind(wx.EVT_BUTTON, self.OnStepperOff)
- self.buttonOff.Enable(False)
- panelActionSizer.AddSpacer(5)
- panelActionSizer.Add(self.buttonOff, 0, wx.BOTTOM, 1)
- self.buttonOff.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Status Gauge
- self.gauge = wx.Gauge(parent = self.panelAction)
- self.gauge.SetRange(BUSY_PROCESSING_STEPS)
- panelActionSizer.Add(self.gauge, 1, wx.LEFT | wx.RIGHT, 5)
- self.gauge.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Bottom panel
- self.panelBottom = wx.Panel(self)
- panelBottomSizer = wx.BoxSizer(wx.HORIZONTAL)
- self.panelBottom.SetSizer(panelBottomSizer)
- outerPanelSizer.Add(self.panelBottom, 0, wx.EXPAND | wx.TOP, 5)
- self.panelBottom.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Config panel
- self.panelConfig = wx.Panel(self.panelBottom)
- panelConfigSizer = wx.BoxSizer(wx.VERTICAL)
- self.panelConfig.SetSizer(panelConfigSizer)
- panelBottomSizer.Add(self.panelConfig, 2, wx.EXPAND | wx.LEFT, 5)
- self.panelConfig.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Step Size Label
- stepSizeLabel = wx.StaticText(self.panelConfig, label="Step:")
- panelConfigSizer.Add(stepSizeLabel, 0, wx.LEFT, 5)
- stepSizeLabel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Step Size Text Input
- self.stepSize = wx.TextCtrl(parent = self.panelConfig, value="0.025", style = wx.TE_DONTWRAP | wx.TE_PROCESS_ENTER)
- self.stepSize.SetMinSize((65, -1))
- self.stepSize.Enable(False)
- self.stepSize.Bind(wx.EVT_TEXT_ENTER, self.OnNewStepSize)
- panelConfigSizer.Add(self.stepSize, 0, wx.LEFT | wx.TOP, 5)
-
- # Bed Temperature Label
- self.bedTemperatureLabel = wx.StaticText(self.panelConfig, label="Bed: ??.?")
- panelConfigSizer.Add(self.bedTemperatureLabel, 0, wx.LEFT | wx.TOP, 5)
- self.bedTemperatureLabel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Bed Temperature Text Input
- self.bedTemperature = wx.TextCtrl(parent = self.panelConfig, value="0", style = wx.TE_DONTWRAP | wx.TE_PROCESS_ENTER)
- self.bedTemperature.SetMinSize((65, -1))
- self.bedTemperature.Enable(False)
- self.bedTemperature.Bind(wx.EVT_TEXT_ENTER, self.OnNewBedTemperature)
- panelConfigSizer.Add(self.bedTemperature, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 5)
-
- # Control-Buttons Panel
- self.panelControl = wx.Panel(self.panelBottom)
- panelControlSizer = wx.BoxSizer(wx.VERTICAL)
- self.panelControl.SetSizer(panelControlSizer)
- panelBottomSizer.Add(self.panelControl, 2, wx.EXPAND | wx.LEFT, 5)
- self.panelControl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Up Button
- self.buttonUp = wx.Button(parent = self.panelControl, label = "/\\")
- self.buttonUp.Bind(wx.EVT_BUTTON, self.OnUp)
- self.buttonUp.Enable(False)
- panelControlSizer.AddStretchSpacer(1)
- panelControlSizer.Add(self.buttonUp, 2, wx.ALIGN_CENTER_HORIZONTAL)
- self.buttonUp.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Down Button
- self.buttonDown = wx.Button(parent = self.panelControl, label = "\\/")
- self.buttonDown.Bind(wx.EVT_BUTTON, self.OnDown)
- self.buttonDown.Enable(False)
- panelControlSizer.Add(self.buttonDown, 2, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 5)
- panelControlSizer.AddStretchSpacer(1)
- self.buttonDown.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Level Status Panel
- self.panelLevelStatus = wx.Panel(self.panelBottom)
- panelLevelStatusSizer = wx.BoxSizer(wx.VERTICAL)
- self.panelLevelStatus.SetSizer(panelLevelStatusSizer)
- panelBottomSizer.Add(self.panelLevelStatus, 1, wx.EXPAND)
- self.panelLevelStatus.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Level Status Gauge
- self.gaugeLevel = wx.Gauge(parent = self.panelLevelStatus, style = wx.GA_VERTICAL)
- self.gaugeLevel.SetRange(1)
- self.gaugeLevel.SetValue(1)
- panelLevelStatusSizer.Add(self.gaugeLevel, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM | wx.LEFT, 10)
- self.gaugeLevel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Level Panel
- self.panelLevel = wx.Panel(self.panelBottom)
- panelLevelSizer = wx.BoxSizer(wx.VERTICAL)
- self.panelLevel.SetSizer(panelLevelSizer)
- panelBottomSizer.Add(self.panelLevel, 2, wx.EXPAND)
- self.panelLevel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Start Button
- self.buttonStart = wx.Button(parent = self.panelLevel, label = "Start")
- self.buttonStart.Bind(wx.EVT_BUTTON, self.OnStart)
- self.buttonStart.Enable(False)
- panelLevelSizer.Add(self.buttonStart, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, 5)
- self.buttonStart.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- # Next Button
- self.buttonNext = wx.Button(parent = self.panelLevel, label = "Next")
- self.buttonNext.Bind(wx.EVT_BUTTON, self.OnNext)
- self.buttonNext.Enable(False)
- panelLevelSizer.Add(self.buttonNext, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, 5)
- self.buttonNext.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- self.buttonSave = wx.Button(parent = self.panelLevel, label = "Save")
- self.buttonSave.Bind(wx.EVT_BUTTON, self.OnSave)
- self.buttonSave.Enable(False)
- panelLevelSizer.Add(self.buttonSave, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP | wx.BOTTOM, 5)
- self.buttonSave.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- self.SetSizerAndFit(outerPanelSizer)
- #self.SetSizer(outerPanelSizer)
-
- def EnumerateSerialPorts(self):
- # List all available serial ports
- serialPortList = serial.tools.list_ports.comports()
-
- # Fill Combobox, remember selected value
- selection = self.comboBoxSerial.GetValue()
- self.comboBoxSerial.Clear()
- for port in serialPortList:
- self.comboBoxSerial.Append(port[0])
- if port[0] == selection:
- self.comboBoxSerial.SetStringSelection(selection)
-
- # Select first port if nothing is selected
- if not selection:
- self.comboBoxSerial.SetStringSelection(serialPortList[0][0])
-
- self.panelSerial.Layout()
-
- # Check every 5 seconds for new available serial ports
- wx.FutureCall(POLL_SERIAL_PORTS_INTERVALL, self.EnumerateSerialPorts)
-
- def EnableDisableUI(self):
- if not self.isConnected:
- self.buttonSerial.SetLabel("Connect")
- self.labelX.SetLabel("X: ??.??")
- self.labelY.SetLabel("Y: ??.??")
- self.labelZ.SetLabel("Z: ??.??")
- self.labelZc.SetLabel("Zc: ??.???")
- else:
- self.buttonSerial.SetLabel("Disconnect")
-
- self.panelStatus.Layout()
- self.panelSerial.Layout()
-
- self.comboBoxSerial.Enable(not self.isConnected)
- self.comboBoxBaud.Enable(not self.isConnected)
-
- self.buttonHome.Enable(self.isConnected)
- self.buttonOff.Enable(self.isConnected)
- self.buttonUp.Enable(self.isConnected)
- self.buttonDown.Enable(self.isConnected)
- self.stepSize.Enable(self.isConnected)
- self.bedTemperature.Enable(self.isConnected)
-
- self.buttonStart.Enable(False)
- self.buttonNext.Enable(False)
- self.buttonSave.Enable(False)
- if self.isConnected:
- if not self.isLeveling:
- self.buttonSave.Enable(True)
- if self.meshPoints > 0:
- self.buttonStart.Enable(True)
-
- if self.isLeveling:
- self.buttonNext.Enable(True)
-
- def OnSave(self, event):
- if self.isLeveling:
- print "Leveling is already in progress!"
- return
-
- self.protocol.write_line(GCODE_EEPROM_SAVE)
-
- def OnStart(self, event):
- if self.isLeveling:
- print "Leveling is already in progress!"
- return
-
- self.isLeveling = True
- self.currentPoint = 0
- self.gauge.SetValue(0)
- self.protocol.write_line(GCODE_MESH_START)
- self.gaugeLevel.SetValue(0)
- self.EnableDisableUI()
-
- def OnNext(self, event):
- if not self.isLeveling:
- print "Start leveling before proceeding!"
- return
-
- self.gauge.SetValue(0)
- self.protocol.write_line(GCODE_MESH_NEXT)
- self.currentPoint += 1
- if self.currentPoint >= self.meshPoints:
- self.isLeveling = False
- self.gaugeLevel.SetValue(self.currentPoint)
- self.EnableDisableUI()
- self.currentZ = self.startZ
- self.labelZc.SetLabel("Zc: {}".format(self.currentZ))
- self.panelStatus.Layout()
-
- def OnNewStepSize(self, event):
- print "New Step Size: {}".format(self.stepSize.GetValue())
- self.step = float(self.stepSize.GetValue())
-
- def OnKeyDown(self, event):
- key = event.GetKeyCode()
-
- if self.isConnected:
- if key == wx.WXK_DOWN:
- self.OnDown(event)
- elif key == wx.WXK_UP:
- self.OnUp(event)
- elif key == wx.WXK_RIGHT:
- self.OnNext(event)
-
- def OnUp(self, event):
- self.currentZ += self.step
- print "Moving up to {}".format(self.currentZ)
- self.gauge.SetValue(0)
- self.protocol.write_line(GCODE_MOVE_Z.format(self.currentZ))
- self.labelZc.SetLabel("Zc: {}".format(self.currentZ))
- self.panelStatus.Layout()
-
- def OnDown(self, event):
- if self.currentZ <= 0.0:
- print "Can't move further down!"
- return
-
- self.currentZ -= self.step
- print "Moving down to {}".format(self.currentZ)
- self.gauge.SetValue(0)
- self.protocol.write_line(GCODE_MOVE_Z.format(self.currentZ))
- self.labelZc.SetLabel("Zc: {}".format(self.currentZ))
- self.panelStatus.Layout()
-
- def OnStepperOff(self, event):
- self.gauge.SetValue(0)
- self.protocol.write_line(GCODE_STOP_IDLE_HOLD)
-
- def OnHome(self, event):
- self.gauge.SetValue(0)
- self.protocol.write_line(GCODE_MOVE_TO_ORIGIN)
-
- def OnStatus(self, event):
- if event.GetValue() == 0:
- self.OnDone(event)
- else:
- self.OnBusy(event)
-
- def OnBusy(self, event):
- val = self.gauge.GetValue()
- if val < self.gauge.GetRange():
- self.gauge.SetValue(val + 1)
-
- def OnDone(self, event):
- self.gauge.SetValue(self.gauge.GetRange())
-
- def PollMeshData(self):
- if self.isConnected:
- self.protocol.write_line(GCODE_MESH_INFO)
-
- def PollMeshDataRetry(self):
- if self.meshPoints <= 0:
- self.PollMeshData()
- wx.FutureCall(MESH_DATA_RETRY_TIMEOUT, self.PollMeshDataRetry)
-
- def OnMeshPoints(self, event):
- x = int(event.GetX())
- y = int(event.GetY())
- self.meshPoints = x * y
- print "Mesh Points: X: {} Y: {} --> {}".format(x, y, self.meshPoints)
- self.gaugeLevel.SetRange(self.meshPoints)
- self.gaugeLevel.SetValue(0)
- self.EnableDisableUI()
-
- def OnMeshZ(self, event):
- self.startZ = float(event.GetZ())
- self.currentZ = float(event.GetZ())
- self.labelZc.SetLabel("Zc: {}".format(self.currentZ))
- self.gauge.SetValue(BUSY_PROCESSING_STEPS)
- print "Mesh starting height: {}".format(self.currentZ)
-
- def PollPosition(self):
- if self.isConnected:
- #if not self.isLeveling:
- self.protocol.write_line(GCODE_GET_CURRENT_POSITION)
- wx.FutureCall(POLL_COORDINATES_INTERVALL, self.PollPosition)
-
- def OnCoordinates(self, event):
- data = event.GetValue()
- print "New coordinates {}".format(data)
- afterX = data.split("X: ")[1]
- x = afterX.split(" ")[0]
- afterY = data.split("Y: ")[1]
- y = afterY.split(" ")[0]
- afterZ = data.split("Z: ")[1]
- z = afterZ.split(" ")[0]
-
- self.labelX.SetLabel("X: {}".format(x))
- self.labelY.SetLabel("Y: {}".format(y))
- self.labelZ.SetLabel("Z: {}".format(z))
-
- self.panelStatus.Layout()
-
- def OnTemperature(self, event):
- print "Current Bed Temperature: {}".format(event.GetBed())
- self.bedTemperatureLabel.SetLabel("Bed: {}".format(event.GetBed()))
-
- def OnNewBedTemperature(self, event):
- self.protocol.write_line(GCODE_SET_BED_TEMP.format(float(self.bedTemperature.GetValue())))
-
- def PollTemperature(self):
- if self.isConnected:
- self.protocol.write_line(GCODE_GET_TEMP)
- wx.FutureCall(POLL_TEMPERATURE_INTERVALL, self.PollTemperature)
-
- def OnConnectDisconnect(self, Event):
- self.isLeveling = False
- self.currentPoint = 0
- if self.isConnected:
- self.thread.close()
- self.serial.close()
- self.isConnected = False
- self.gauge.SetValue(0)
- self.gaugeLevel.SetRange(1)
- self.gaugeLevel.SetValue(1)
- self.EnableDisableUI()
-
- # Recreate thread, so we can start it again next time
- self.thread = serial.threaded.ReaderThread(self.serial, GCodeReader)
- else:
- self.serial.baudrate = self.comboBoxBaud.GetValue()
- self.serial.port = self.comboBoxSerial.GetValue()
- try:
- self.serial.open()
- except serial.SerialException as msg:
- wx.MessageBox("SerialException: {}".format(msg), "Error", wx.OK | wx.ICON_ERROR)
- except OSError as msg:
- wx.MessageBox("OSError: {}".format(msg), "Error", wx.OK | wx.ICON_ERROR)
- if self.serial.isOpen():
- self.isConnected = True
- self.thread.start()
- self.transport, self.protocol = self.thread.connect()
- self.protocol.setParent(self)
- self.meshPoints = -1
- self.EnableDisableUI()
- self.PollPosition()
- self.PollMeshData()
- self.PollTemperature()
- wx.FutureCall(MESH_DATA_RETRY_TIMEOUT, self.PollMeshDataRetry)
-
- def OnQuit(self, Event):
- if not self.hasQuit:
- self.hasQuit = True
- if self.isConnected:
- print("Closing serial port...")
- self.thread.close()
- self.serial.close()
- self.isConnected = False
- self.Destroy()
-
- # App wrapper class
- class WizardApp(wx.App):
- def __init__(self, *args, **kwargs):
- wx.App.__init__(self, *args, **kwargs)
- self.Bind(wx.EVT_ACTIVATE_APP, self.OnActivate)
-
- def OnInit(self):
- frame = WizardFrame(parent = None, title = "Bed Leveling Wizard", style = wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN)
- frame.Centre()
- frame.Show()
- return True
-
- def BringWindowToFront(self):
- self.GetTopWindow().Raise()
-
- def OnActivate(self, event):
- if event.GetActive():
- self.BringWindowToFront()
- event.Skip()
-
- def MacReopenApp(self):
- self.BringWindowToFront()
-
- def main():
- app = WizardApp(False)
- app.MainLoop()
-
- if __name__ == '__main__':
- main()
|