chiark / gitweb /
Show a warning if computer is not connected to AC power.
[cura.git] / Cura / gui / printWindow.py
1 from __future__ import absolute_import\r
2 import __init__\r
3 \r
4 import wx, threading, re, subprocess, sys, os, time, platform\r
5 from wx.lib import buttons\r
6 \r
7 from gui import icon\r
8 from gui import toolbarUtil\r
9 from gui import webcam\r
10 from gui import taskbar\r
11 from util import machineCom\r
12 from util import profile\r
13 from util import gcodeInterpreter\r
14 from util import power\r
15 \r
16 printWindowMonitorHandle = None\r
17 \r
18 def printFile(filename):\r
19         global printWindowMonitorHandle\r
20         if printWindowMonitorHandle == None:\r
21                 printWindowMonitorHandle = printProcessMonitor()\r
22         printWindowMonitorHandle.loadFile(filename)\r
23 \r
24 def startPrintInterface(filename):\r
25         #startPrintInterface is called from the main script when we want the printer interface to run in a seperate process.\r
26         # It needs to run in a seperate process, as any running python code blocks the GCode sender pyton code (http://wiki.python.org/moin/GlobalInterpreterLock).\r
27         app = wx.App(False)\r
28         printWindowHandle = printWindow()\r
29         printWindowHandle.Show(True)\r
30         printWindowHandle.Raise()\r
31         printWindowHandle.OnConnect(None)\r
32         t = threading.Thread(target=printWindowHandle.LoadGCodeFile,args=(filename,))\r
33         t.daemon = True\r
34         t.start()\r
35         app.MainLoop()\r
36 \r
37 class printProcessMonitor():\r
38         def __init__(self):\r
39                 self.handle = None\r
40         \r
41         def loadFile(self, filename):\r
42                 if self.handle == None:
43                         cmdList = [sys.executable, sys.argv[0], '-r', filename]
44                         if platform.system() == "Darwin":
45                                 if platform.machine() == 'i386':
46                                         cmdList.insert(0, 'arch')
47                                         cmdList.insert(1, '-i386')
48                         self.handle = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
49                         self.thread = threading.Thread(target=self.Monitor)\r
50                         self.thread.start()\r
51                 else:\r
52                         self.handle.stdin.write(filename + '\n')\r
53         \r
54         def Monitor(self):\r
55                 p = self.handle\r
56                 line = p.stdout.readline()\r
57                 while(len(line) > 0):\r
58                         #print line.rstrip()\r
59                         line = p.stdout.readline()\r
60                 p.communicate()\r
61                 self.handle = None\r
62                 self.thread = None\r
63 \r
64 class PrintCommandButton(buttons.GenBitmapButton):\r
65         def __init__(self, parent, commandList, bitmapFilename, size=(20,20)):\r
66                 self.bitmap = toolbarUtil.getBitmapImage(bitmapFilename)\r
67                 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)\r
68 \r
69                 self.commandList = commandList\r
70                 self.parent = parent\r
71 \r
72                 self.SetBezelWidth(1)\r
73                 self.SetUseFocusIndicator(False)\r
74 \r
75                 self.Bind(wx.EVT_BUTTON, self.OnClick)\r
76 \r
77         def OnClick(self, e):\r
78                 if self.parent.machineCom == None or self.parent.machineCom.isPrinting():\r
79                         return;\r
80                 for cmd in self.commandList:\r
81                         self.parent.machineCom.sendCommand(cmd)\r
82                 e.Skip()\r
83 \r
84 class printWindow(wx.Frame):\r
85         "Main user interface window"\r
86         def __init__(self):\r
87                 super(printWindow, self).__init__(None, -1, title='Printing')\r
88                 self.machineCom = None\r
89                 self.gcode = None\r
90                 self.gcodeList = None\r
91                 self.sendList = []\r
92                 self.temp = None\r
93                 self.bedTemp = None\r
94                 self.bufferLineCount = 4\r
95                 self.sendCnt = 0\r
96                 self.feedrateRatioOuterWall = 1.0\r
97                 self.feedrateRatioInnerWall = 1.0\r
98                 self.feedrateRatioFill = 1.0\r
99                 self.feedrateRatioSupport = 1.0\r
100                 self.pause = False\r
101                 self.termHistory = []\r
102                 self.termHistoryIdx = 0\r
103                 \r
104                 self.cam = None\r
105                 if webcam.hasWebcamSupport():\r
106                         self.cam = webcam.webcam()\r
107                         if not self.cam.hasCamera():\r
108                                 self.cam = None\r
109 \r
110                 #self.SetIcon(icon.getMainIcon())\r
111                 \r
112                 self.SetSizer(wx.BoxSizer())\r
113                 self.panel = wx.Panel(self)\r
114                 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)\r
115                 self.sizer = wx.GridBagSizer(2, 2)\r
116                 self.panel.SetSizer(self.sizer)\r
117                 \r
118                 sb = wx.StaticBox(self.panel, label="Statistics")\r
119                 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)\r
120 \r
121                 p = power.PowerManagement()\r
122                 if p.get_providing_power_source_type() != power.POWER_TYPE_AC:\r
123                         self.powerWarningText = wx.StaticText(parent=self.panel,\r
124                                 id=-1,\r
125                                 label="Connect your computer to AC power\nIf it shuts down during printing, the product will be lost.",\r
126                                 style=wx.ALIGN_CENTER)\r
127                         self.powerWarningText.SetBackgroundColour('red')\r
128                         self.powerWarningText.SetForegroundColour('white')\r
129                         boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))\r
130 \r
131                 self.statsText = wx.StaticText(self.panel, -1, "Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")\r
132                 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)\r
133                 \r
134                 self.sizer.Add(boxsizer, pos=(0,0), span=(7,1), flag=wx.EXPAND)\r
135                 \r
136                 self.connectButton = wx.Button(self.panel, -1, 'Connect')\r
137                 #self.loadButton = wx.Button(self.panel, -1, 'Load')\r
138                 self.printButton = wx.Button(self.panel, -1, 'Print')\r
139                 self.pauseButton = wx.Button(self.panel, -1, 'Pause')\r
140                 self.cancelButton = wx.Button(self.panel, -1, 'Cancel print')\r
141                 self.machineLogButton = wx.Button(self.panel, -1, 'Error log')\r
142                 self.progress = wx.Gauge(self.panel, -1)\r
143                 \r
144                 self.sizer.Add(self.connectButton, pos=(1,1), flag=wx.EXPAND)\r
145                 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)\r
146                 self.sizer.Add(self.printButton, pos=(2,1), flag=wx.EXPAND)\r
147                 self.sizer.Add(self.pauseButton, pos=(3,1), flag=wx.EXPAND)\r
148                 self.sizer.Add(self.cancelButton, pos=(4,1), flag=wx.EXPAND)\r
149                 self.sizer.Add(self.machineLogButton, pos=(5,1), flag=wx.EXPAND)\r
150                 self.sizer.Add(self.progress, pos=(7,0), span=(1,7), flag=wx.EXPAND)\r
151 \r
152                 nb = wx.Notebook(self.panel)\r
153                 self.sizer.Add(nb, pos=(0,2), span=(7,4), flag=wx.EXPAND)\r
154                 \r
155                 self.temperaturePanel = wx.Panel(nb)\r
156                 sizer = wx.GridBagSizer(2, 2)\r
157                 self.temperaturePanel.SetSizer(sizer)\r
158 \r
159                 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21*3,21), style=wx.SP_ARROW_KEYS)\r
160                 self.temperatureSelect.SetRange(0, 400)\r
161                 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, "BedTemp:")\r
162                 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21*3,21), style=wx.SP_ARROW_KEYS)\r
163                 self.bedTemperatureSelect.SetRange(0, 400)\r
164                 self.bedTemperatureLabel.Show(False)\r
165                 self.bedTemperatureSelect.Show(False)\r
166                 \r
167                 self.temperatureGraph = temperatureGraph(self.temperaturePanel)\r
168                 \r
169                 sizer.Add(wx.StaticText(self.temperaturePanel, -1, "Temp:"), pos=(0,0))\r
170                 sizer.Add(self.temperatureSelect, pos=(0,1))\r
171                 sizer.Add(self.bedTemperatureLabel, pos=(1,0))\r
172                 sizer.Add(self.bedTemperatureSelect, pos=(1,1))\r
173                 sizer.Add(self.temperatureGraph, pos=(2,0), span=(1,2), flag=wx.EXPAND)\r
174                 sizer.AddGrowableRow(2)\r
175                 sizer.AddGrowableCol(1)\r
176 \r
177                 nb.AddPage(self.temperaturePanel, 'Temp')\r
178 \r
179                 self.directControlPanel = wx.Panel(nb)\r
180                 \r
181                 sizer = wx.GridBagSizer(2, 2)\r
182                 self.directControlPanel.SetSizer(sizer)\r
183                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0,3))\r
184                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1,3))\r
185                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2,3))\r
186 \r
187                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4,3))\r
188                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5,3))\r
189                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6,3))\r
190 \r
191                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3,0))\r
192                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3,1))\r
193                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3,2))\r
194 \r
195                 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3,3))\r
196 \r
197                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3,4))\r
198                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3,5))\r
199                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3,6))\r
200 \r
201                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0,8))\r
202                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1,8))\r
203                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2,8))\r
204 \r
205                 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3,8))\r
206 \r
207                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4,8))\r
208                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5,8))\r
209                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6,8))\r
210 \r
211                 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60,20)), pos=(1,10), span=(1,3), flag=wx.EXPAND)\r
212                 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60,20)), pos=(2,10), span=(1,3), flag=wx.EXPAND)\r
213 \r
214                 nb.AddPage(self.directControlPanel, 'Jog')\r
215 \r
216                 self.speedPanel = wx.Panel(nb)\r
217                 sizer = wx.GridBagSizer(2, 2)\r
218                 self.speedPanel.SetSizer(sizer)\r
219 \r
220                 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21*3,21), style=wx.SP_ARROW_KEYS)\r
221                 self.outerWallSpeedSelect.SetRange(5, 1000)\r
222                 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21*3,21), style=wx.SP_ARROW_KEYS)\r
223                 self.innerWallSpeedSelect.SetRange(5, 1000)\r
224                 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21*3,21), style=wx.SP_ARROW_KEYS)\r
225                 self.fillSpeedSelect.SetRange(5, 1000)\r
226                 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21*3,21), style=wx.SP_ARROW_KEYS)\r
227                 self.supportSpeedSelect.SetRange(5, 1000)\r
228                 \r
229                 sizer.Add(wx.StaticText(self.speedPanel, -1, "Outer wall:"), pos=(0,0))\r
230                 sizer.Add(self.outerWallSpeedSelect, pos=(0,1))\r
231                 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0,2))\r
232                 sizer.Add(wx.StaticText(self.speedPanel, -1, "Inner wall:"), pos=(1,0))\r
233                 sizer.Add(self.innerWallSpeedSelect, pos=(1,1))\r
234                 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1,2))\r
235                 sizer.Add(wx.StaticText(self.speedPanel, -1, "Fill:"), pos=(2,0))\r
236                 sizer.Add(self.fillSpeedSelect, pos=(2,1))\r
237                 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2,2))\r
238                 sizer.Add(wx.StaticText(self.speedPanel, -1, "Support:"), pos=(3,0))\r
239                 sizer.Add(self.supportSpeedSelect, pos=(3,1))\r
240                 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3,2))\r
241 \r
242                 nb.AddPage(self.speedPanel, 'Speed')\r
243                 \r
244                 self.termPanel = wx.Panel(nb)\r
245                 sizer = wx.GridBagSizer(2, 2)\r
246                 self.termPanel.SetSizer(sizer)\r
247                 \r
248                 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)\r
249                 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE|wx.TE_DONTWRAP)\r
250                 self.termLog.SetFont(f)\r
251                 self.termLog.SetEditable(0)\r
252                 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)\r
253                 self.termInput.SetFont(f)\r
254 \r
255                 sizer.Add(self.termLog, pos=(0,0),flag=wx.EXPAND)\r
256                 sizer.Add(self.termInput, pos=(1,0),flag=wx.EXPAND)\r
257                 sizer.AddGrowableCol(0)\r
258                 sizer.AddGrowableRow(0)\r
259 \r
260                 nb.AddPage(self.termPanel, 'Term')\r
261                 \r
262                 if self.cam != None:\r
263                         self.camPage = wx.Panel(nb)\r
264                         sizer = wx.GridBagSizer(2, 2)\r
265                         self.camPage.SetSizer(sizer)\r
266                         \r
267                         self.timelapsEnable = wx.CheckBox(self.camPage, -1, 'Enable timelaps movie recording')\r
268                         sizer.Add(self.timelapsEnable, pos=(0,0), span=(1,2), flag=wx.EXPAND)\r
269                         \r
270                         pages = self.cam.propertyPages()\r
271                         self.cam.buttons = [self.timelapsEnable]\r
272                         for page in pages:\r
273                                 button = wx.Button(self.camPage, -1, page)\r
274                                 button.index = pages.index(page)\r
275                                 sizer.Add(button, pos=(1, pages.index(page)))\r
276                                 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)\r
277                                 self.cam.buttons.append(button)\r
278 \r
279                         self.campreviewEnable = wx.CheckBox(self.camPage, -1, 'Show preview')\r
280                         sizer.Add(self.campreviewEnable, pos=(2,0), span=(1,2), flag=wx.EXPAND)\r
281                         \r
282                         self.camPreview = wx.Panel(self.camPage)\r
283                         sizer.Add(self.camPreview, pos=(3,0), span=(1,2), flag=wx.EXPAND)\r
284                         \r
285                         nb.AddPage(self.camPage, 'Camera')\r
286                         self.camPreview.timer = wx.Timer(self)\r
287                         self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)\r
288                         self.camPreview.timer.Start(500)\r
289                         self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)\r
290 \r
291                 self.sizer.AddGrowableRow(5)\r
292                 self.sizer.AddGrowableCol(3)\r
293                 \r
294                 self.Bind(wx.EVT_CLOSE, self.OnClose)\r
295                 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)\r
296                 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)\r
297                 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)\r
298                 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)\r
299                 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)\r
300                 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)\r
301                 \r
302                 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)\r
303                 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)\r
304 \r
305                 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)\r
306                 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)\r
307                 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)\r
308                 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)\r
309                 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)\r
310                 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)\r
311                 \r
312                 self.Layout()\r
313                 self.Fit()\r
314                 self.Centre()\r
315                 \r
316                 self.statsText.SetMinSize(self.statsText.GetSize())\r
317
318                 self.UpdateButtonStates()\r
319                 #self.UpdateProgress()\r
320         \r
321         def OnCameraTimer(self, e):\r
322                 if not self.campreviewEnable.GetValue():\r
323                         return\r
324                 if self.machineCom != None and self.machineCom.isPrinting():\r
325                         return\r
326                 self.cam.takeNewImage()\r
327                 self.camPreview.Refresh()\r
328         \r
329         def OnCameraEraseBackground(self, e):\r
330                 dc = e.GetDC()\r
331                 if not dc:\r
332                         dc = wx.ClientDC(self)\r
333                         rect = self.GetUpdateRegion().GetBox()\r
334                         dc.SetClippingRect(rect)\r
335                 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))\r
336                 if self.cam.getLastImage() != None:\r
337                         self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))\r
338                         self.camPage.Fit()\r
339                         dc.DrawBitmap(self.cam.getLastImage(), 0, 0)\r
340                 else:\r
341                         dc.Clear()\r
342                 \r
343         def OnPropertyPageButton(self, e):\r
344                 self.cam.openPropertyPage(e.GetEventObject().index)\r
345 \r
346         def UpdateButtonStates(self):\r
347                 self.connectButton.Enable(self.machineCom == None or self.machineCom.isClosedOrError())\r
348                 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))\r
349                 self.printButton.Enable(self.machineCom != None and self.machineCom.isOperational() and not (self.machineCom.isPrinting() or self.machineCom.isPaused()))\r
350                 self.pauseButton.Enable(self.machineCom != None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))\r
351                 if self.machineCom != None and self.machineCom.isPaused():\r
352                         self.pauseButton.SetLabel('Resume')\r
353                 else:\r
354                         self.pauseButton.SetLabel('Pause')\r
355                 self.cancelButton.Enable(self.machineCom != None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))\r
356                 self.temperatureSelect.Enable(self.machineCom != None and self.machineCom.isOperational())\r
357                 self.bedTemperatureSelect.Enable(self.machineCom != None and self.machineCom.isOperational())\r
358                 self.directControlPanel.Enable(self.machineCom != None and self.machineCom.isOperational() and not self.machineCom.isPrinting())\r
359                 self.machineLogButton.Show(self.machineCom != None and self.machineCom.isClosedOrError())\r
360                 if self.cam != None:\r
361                         for button in self.cam.buttons:\r
362                                 button.Enable(self.machineCom == None or not self.machineCom.isPrinting())\r
363         \r
364         def UpdateProgress(self):\r
365                 status = ""\r
366                 if self.gcode == None:\r
367                         status += "Loading gcode...\n"\r
368                 else:\r
369                         status += "Filament: %.2fm %.2fg\n" % (self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)\r
370                         cost = self.gcode.calculateCost()\r
371                         if cost != False:\r
372                                 status += "Filament cost: %s\n" % (cost)\r
373                         status += "Estimated print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))\r
374                 if self.machineCom == None or not self.machineCom.isPrinting():\r
375                         self.progress.SetValue(0)\r
376                         if self.gcodeList != None:\r
377                                 status += 'Line: -/%d\n' % (len(self.gcodeList))\r
378                 else:\r
379                         printTime = self.machineCom.getPrintTime() / 60\r
380                         printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()\r
381                         status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList), self.machineCom.getPrintPos() * 100 / len(self.gcodeList))\r
382                         if self.currentZ > 0:\r
383                                 status += 'Height: %0.1f\n' % (self.currentZ)\r
384                         status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))\r
385                         if printTimeLeft == None:\r
386                                 status += 'Print time left: Unknown\n'\r
387                         else:\r
388                                 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))\r
389                         self.progress.SetValue(self.machineCom.getPrintPos())\r
390                         taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))\r
391                 if self.machineCom != None:\r
392                         if self.machineCom.getTemp() > 0:\r
393                                 status += 'Temp: %d\n' % (self.machineCom.getTemp())\r
394                         if self.machineCom.getBedTemp() > 0:\r
395                                 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())\r
396                                 self.bedTemperatureLabel.Show(True)\r
397                                 self.bedTemperatureSelect.Show(True)\r
398                                 self.temperaturePanel.Layout()\r
399                         status += 'Machine state:%s\n' % (self.machineCom.getStateString())\r
400                 \r
401                 self.statsText.SetLabel(status.strip())\r
402         \r
403         def OnConnect(self, e):\r
404                 if self.machineCom != None:\r
405                         self.machineCom.close()\r
406                 self.machineCom = machineCom.MachineCom(callbackObject=self)\r
407                 self.UpdateButtonStates()\r
408                 taskbar.setBusy(self, True)\r
409         \r
410         def OnLoad(self, e):\r
411                 pass\r
412         \r
413         def OnPrint(self, e):\r
414                 if self.machineCom == None or not self.machineCom.isOperational():\r
415                         return\r
416                 if self.gcodeList == None:\r
417                         return\r
418                 if self.machineCom.isPrinting():\r
419                         return\r
420                 self.currentZ = -1\r
421                 if self.cam != None and self.timelapsEnable.GetValue():\r
422                         self.cam.startTimelaps(self.filename[: self.filename.rfind('.')] + ".mpg")\r
423                 self.machineCom.printGCode(self.gcodeList)\r
424                 self.UpdateButtonStates()\r
425         \r
426         def OnCancel(self, e):\r
427                 self.pauseButton.SetLabel('Pause')\r
428                 self.machineCom.cancelPrint()\r
429                 self.machineCom.sendCommand("M84")\r
430                 self.UpdateButtonStates()\r
431         \r
432         def OnPause(self, e):\r
433                 if self.machineCom.isPaused():\r
434                         self.machineCom.setPause(False)\r
435                 else:\r
436                         self.machineCom.setPause(True)\r
437         \r
438         def OnMachineLog(self, e):\r
439                 LogWindow('\n'.join(self.machineCom.getLog()))\r
440         \r
441         def OnClose(self, e):\r
442                 global printWindowHandle\r
443                 printWindowHandle = None\r
444                 if self.machineCom != None:\r
445                         self.machineCom.close()\r
446                 self.Destroy()\r
447 \r
448         def OnTempChange(self, e):\r
449                 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))\r
450 \r
451         def OnBedTempChange(self, e):\r
452                 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))\r
453         \r
454         def OnSpeedChange(self, e):\r
455                 if self.machineCom == None:\r
456                         return\r
457                 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)\r
458                 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)\r
459                 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)\r
460                 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)\r
461         \r
462         def AddTermLog(self, line):\r
463                 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))\r
464                 l = len(self.termLog.GetValue())\r
465                 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))\r
466         \r
467         def OnTermEnterLine(self, e):\r
468                 line = self.termInput.GetValue()\r
469                 if line == '':\r
470                         return\r
471                 self.termLog.AppendText('>%s\n' % (line))\r
472                 self.machineCom.sendCommand(line)\r
473                 self.termHistory.append(line)\r
474                 self.termHistoryIdx = len(self.termHistory)\r
475                 self.termInput.SetValue('')\r
476 \r
477         def OnTermKey(self, e):\r
478                 if len(self.termHistory) > 0:\r
479                         if e.GetKeyCode() == wx.WXK_UP:\r
480                                 self.termHistoryIdx = self.termHistoryIdx - 1\r
481                                 if self.termHistoryIdx < 0:\r
482                                         self.termHistoryIdx = len(self.termHistory) - 1\r
483                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])\r
484                         if e.GetKeyCode() == wx.WXK_DOWN:\r
485                                 self.termHistoryIdx = self.termHistoryIdx - 1\r
486                                 if self.termHistoryIdx >= len(self.termHistory):\r
487                                         self.termHistoryIdx = 0\r
488                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])\r
489                 e.Skip()\r
490 \r
491         def LoadGCodeFile(self, filename):\r
492                 if self.machineCom != None and self.machineCom.isPrinting():\r
493                         return\r
494                 #Send an initial M110 to reset the line counter to zero.\r
495                 prevLineType = lineType = 'CUSTOM'\r
496                 gcodeList = ["M110"]\r
497                 for line in open(filename, 'r'):\r
498                         if line.startswith(';TYPE:'):\r
499                                 lineType = line[6:].strip()\r
500                         if ';' in line:\r
501                                 line = line[0:line.find(';')]\r
502                         line = line.strip()\r
503                         if len(line) > 0:\r
504                                 if prevLineType != lineType:\r
505                                         gcodeList.append((line, lineType, ))\r
506                                 else:\r
507                                         gcodeList.append(line)\r
508                                 prevLineType = lineType\r
509                 gcode = gcodeInterpreter.gcode()\r
510                 gcode.loadList(gcodeList)\r
511                 #print "Loaded: %s (%d)" % (filename, len(gcodeList))\r
512                 self.filename = filename\r
513                 self.gcode = gcode\r
514                 self.gcodeList = gcodeList\r
515                 \r
516                 wx.CallAfter(self.progress.SetRange, len(gcodeList))\r
517                 wx.CallAfter(self.UpdateButtonStates)\r
518                 wx.CallAfter(self.UpdateProgress)\r
519                 wx.CallAfter(self.SetTitle, 'Printing: %s' % (filename))\r
520                 \r
521         def sendLine(self, lineNr):\r
522                 if lineNr >= len(self.gcodeList):\r
523                         return False\r
524                 line = self.gcodeList[lineNr]\r
525                 try:\r
526                         if ('M104' in line or 'M109' in line) and 'S' in line:\r
527                                 n = int(re.search('S([0-9]*)', line).group(1))\r
528                                 wx.CallAfter(self.temperatureSelect.SetValue, n)\r
529                         if ('M140' in line or 'M190' in line) and 'S' in line:\r
530                                 n = int(re.search('S([0-9]*)', line).group(1))\r
531                                 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)\r
532                 except:\r
533                         print "Unexpected error:", sys.exc_info()\r
534                 checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (lineNr, line)))\r
535                 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))\r
536                 return True\r
537 \r
538         def mcLog(self, message):\r
539                 #print message\r
540                 pass\r
541         \r
542         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):\r
543                 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)\r
544                 if self.temperatureSelect.GetValue() != targetTemp:\r
545                         wx.CallAfter(self.temperatureSelect.SetValue, targetTemp)\r
546                 if self.bedTemperatureSelect.GetValue() != bedTargetTemp:\r
547                         wx.CallAfter(self.bedTemperatureSelect.SetValue, bedTargetTemp)\r
548         \r
549         def mcStateChange(self, state):\r
550                 if self.machineCom != None:\r
551                         if state == self.machineCom.STATE_OPERATIONAL and self.cam != None:\r
552                                 self.cam.endTimelaps()\r
553                         if state == self.machineCom.STATE_OPERATIONAL:\r
554                                 taskbar.setBusy(self, False)\r
555                         if self.machineCom.isClosedOrError():\r
556                                 taskbar.setBusy(self, False)\r
557                         if self.machineCom.isPaused():\r
558                                 taskbar.setPause(self, True)\r
559                 wx.CallAfter(self.UpdateButtonStates)\r
560                 wx.CallAfter(self.UpdateProgress)\r
561         \r
562         def mcMessage(self, message):\r
563                 wx.CallAfter(self.AddTermLog, message)\r
564         \r
565         def mcProgress(self, lineNr):\r
566                 wx.CallAfter(self.UpdateProgress)\r
567         \r
568         def mcZChange(self, newZ):\r
569                 self.currentZ = newZ\r
570                 if self.cam != None:\r
571                         wx.CallAfter(self.cam.takeNewImage)\r
572                         wx.CallAfter(self.camPreview.Refresh)\r
573 \r
574 class temperatureGraph(wx.Panel):\r
575         def __init__(self, parent):\r
576                 super(temperatureGraph, self).__init__(parent)\r
577                 \r
578                 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)\r
579                 self.Bind(wx.EVT_SIZE, self.OnSize)\r
580                 self.Bind(wx.EVT_PAINT, self.OnDraw)\r
581                 \r
582                 self.lastDraw = time.time() - 1.0\r
583                 self.points = []\r
584                 self.backBuffer = None\r
585                 self.addPoint(0,0,0,0)\r
586                 self.SetMinSize((320,200))\r
587         \r
588         def OnEraseBackground(self, e):\r
589                 pass\r
590         \r
591         def OnSize(self, e):\r
592                 if self.backBuffer == None or self.GetSize() != self.backBuffer.GetSize():\r
593                         self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())\r
594                         self.UpdateDrawing(True)\r
595         \r
596         def OnDraw(self, e):\r
597                 dc = wx.BufferedPaintDC(self, self.backBuffer)\r
598         \r
599         def UpdateDrawing(self, force = False):\r
600                 now = time.time()\r
601                 if not force and now - self.lastDraw < 1.0:\r
602                         return\r
603                 self.lastDraw = now\r
604                 dc = wx.MemoryDC()\r
605                 dc.SelectObject(self.backBuffer)\r
606                 dc.Clear()\r
607                 w, h = self.GetSizeTuple()\r
608                 bgLinePen = wx.Pen('#A0A0A0')\r
609                 tempPen = wx.Pen('#FF4040')\r
610                 tempSPPen = wx.Pen('#FFA0A0')\r
611                 tempPenBG = wx.Pen('#FFD0D0')\r
612                 bedTempPen = wx.Pen('#4040FF')\r
613                 bedTempSPPen = wx.Pen('#A0A0FF')\r
614                 bedTempPenBG = wx.Pen('#D0D0FF')\r
615 \r
616                 #Draw the background up to the current temperatures.\r
617                 x0 = 0\r
618                 t0 = 0\r
619                 bt0 = 0\r
620                 tSP0 = 0\r
621                 btSP0 = 0\r
622                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:\r
623                         x1 = int(w - (now - t))\r
624                         for x in xrange(x0, x1 + 1):\r
625                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0\r
626                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0\r
627                                 dc.SetPen(tempPenBG)\r
628                                 dc.DrawLine(x, h, x, h - (t * h / 300))\r
629                                 dc.SetPen(bedTempPenBG)\r
630                                 dc.DrawLine(x, h, x, h - (bt * h / 300))\r
631                         t0 = temp\r
632                         bt0 = bedTemp\r
633                         tSP0 = tempSP\r
634                         btSP0 = bedTempSP\r
635                         x0 = x1 + 1\r
636 \r
637                 #Draw the grid\r
638                 for x in xrange(w, 0, -30):\r
639                         dc.SetPen(bgLinePen)\r
640                         dc.DrawLine(x, 0, x, h)\r
641                 for y in xrange(h-1, 0, -h * 50 / 300):\r
642                         dc.SetPen(bgLinePen)\r
643                         dc.DrawLine(0, y, w, y)\r
644                 dc.DrawLine(0, 0, w, 0)\r
645                 dc.DrawLine(0, 0, 0, h)\r
646                 \r
647                 #Draw the main lines\r
648                 x0 = 0\r
649                 t0 = 0\r
650                 bt0 = 0\r
651                 tSP0 = 0\r
652                 btSP0 = 0\r
653                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:\r
654                         x1 = int(w - (now - t))\r
655                         for x in xrange(x0, x1 + 1):\r
656                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0\r
657                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0\r
658                                 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP - tSP0) + tSP0\r
659                                 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0\r
660                                 dc.SetPen(tempSPPen)\r
661                                 dc.DrawPoint(x, h - (tSP * h / 300))\r
662                                 dc.SetPen(bedTempSPPen)\r
663                                 dc.DrawPoint(x, h - (btSP * h / 300))\r
664                                 dc.SetPen(tempPen)\r
665                                 dc.DrawPoint(x, h - (t * h / 300))\r
666                                 dc.SetPen(bedTempPen)\r
667                                 dc.DrawPoint(x, h - (bt * h / 300))\r
668                         t0 = temp\r
669                         bt0 = bedTemp\r
670                         tSP0 = tempSP\r
671                         btSP0 = bedTempSP\r
672                         x0 = x1 + 1\r
673                 \r
674                 del dc\r
675                 self.Refresh(eraseBackground=False)\r
676                 self.Update()\r
677                 \r
678                 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:\r
679                         self.points.pop(0)\r
680 \r
681         def addPoint(self, temp, tempSP, bedTemp, bedTempSP):\r
682                 if bedTemp == None:\r
683                         bedTemp = 0\r
684                 if bedTempSP == None:\r
685                         bedTempSP = 0\r
686                 self.points.append((temp, tempSP, bedTemp, bedTempSP, time.time()))\r
687                 wx.CallAfter(self.UpdateDrawing)\r
688 \r
689 class LogWindow(wx.Frame):\r
690         def __init__(self, logText):\r
691                 super(LogWindow, self).__init__(None, title="Machine log")\r
692                 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_READONLY)\r
693                 self.SetSize((500,400))\r
694                 self.Centre()\r
695                 self.Show(True)\r