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