chiark / gitweb /
Merge pull request #265 from GreatFruitOmsk/master
[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.Layout()\r
499                 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():\r
500                         self.powerWarningText.Show()\r
501                         self.Layout()\r
502 \r
503         def LoadGCodeFile(self, filename):\r
504                 if self.machineCom != None and self.machineCom.isPrinting():\r
505                         return\r
506                 #Send an initial M110 to reset the line counter to zero.\r
507                 prevLineType = lineType = 'CUSTOM'\r
508                 gcodeList = ["M110"]\r
509                 for line in open(filename, 'r'):\r
510                         if line.startswith(';TYPE:'):\r
511                                 lineType = line[6:].strip()\r
512                         if ';' in line:\r
513                                 line = line[0:line.find(';')]\r
514                         line = line.strip()\r
515                         if len(line) > 0:\r
516                                 if prevLineType != lineType:\r
517                                         gcodeList.append((line, lineType, ))\r
518                                 else:\r
519                                         gcodeList.append(line)\r
520                                 prevLineType = lineType\r
521                 gcode = gcodeInterpreter.gcode()\r
522                 gcode.loadList(gcodeList)\r
523                 #print "Loaded: %s (%d)" % (filename, len(gcodeList))\r
524                 self.filename = filename\r
525                 self.gcode = gcode\r
526                 self.gcodeList = gcodeList\r
527                 \r
528                 wx.CallAfter(self.progress.SetRange, len(gcodeList))\r
529                 wx.CallAfter(self.UpdateButtonStates)\r
530                 wx.CallAfter(self.UpdateProgress)\r
531                 wx.CallAfter(self.SetTitle, 'Printing: %s' % (filename))\r
532                 \r
533         def sendLine(self, lineNr):\r
534                 if lineNr >= len(self.gcodeList):\r
535                         return False\r
536                 line = self.gcodeList[lineNr]\r
537                 try:\r
538                         if ('M104' in line or 'M109' in line) and 'S' in line:\r
539                                 n = int(re.search('S([0-9]*)', line).group(1))\r
540                                 wx.CallAfter(self.temperatureSelect.SetValue, n)\r
541                         if ('M140' in line or 'M190' in line) and 'S' in line:\r
542                                 n = int(re.search('S([0-9]*)', line).group(1))\r
543                                 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)\r
544                 except:\r
545                         print "Unexpected error:", sys.exc_info()\r
546                 checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (lineNr, line)))\r
547                 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))\r
548                 return True\r
549 \r
550         def mcLog(self, message):\r
551                 #print message\r
552                 pass\r
553         \r
554         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):\r
555                 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)\r
556                 if self.temperatureSelect.GetValue() != targetTemp:\r
557                         wx.CallAfter(self.temperatureSelect.SetValue, targetTemp)\r
558                 if self.bedTemperatureSelect.GetValue() != bedTargetTemp:\r
559                         wx.CallAfter(self.bedTemperatureSelect.SetValue, bedTargetTemp)\r
560         \r
561         def mcStateChange(self, state):\r
562                 if self.machineCom != None:\r
563                         if state == self.machineCom.STATE_OPERATIONAL and self.cam != None:\r
564                                 self.cam.endTimelaps()\r
565                         if state == self.machineCom.STATE_OPERATIONAL:\r
566                                 taskbar.setBusy(self, False)\r
567                         if self.machineCom.isClosedOrError():\r
568                                 taskbar.setBusy(self, False)\r
569                         if self.machineCom.isPaused():\r
570                                 taskbar.setPause(self, True)\r
571                 wx.CallAfter(self.UpdateButtonStates)\r
572                 wx.CallAfter(self.UpdateProgress)\r
573         \r
574         def mcMessage(self, message):\r
575                 wx.CallAfter(self.AddTermLog, message)\r
576         \r
577         def mcProgress(self, lineNr):\r
578                 wx.CallAfter(self.UpdateProgress)\r
579         \r
580         def mcZChange(self, newZ):\r
581                 self.currentZ = newZ\r
582                 if self.cam != None:\r
583                         wx.CallAfter(self.cam.takeNewImage)\r
584                         wx.CallAfter(self.camPreview.Refresh)\r
585 \r
586 class temperatureGraph(wx.Panel):\r
587         def __init__(self, parent):\r
588                 super(temperatureGraph, self).__init__(parent)\r
589                 \r
590                 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)\r
591                 self.Bind(wx.EVT_SIZE, self.OnSize)\r
592                 self.Bind(wx.EVT_PAINT, self.OnDraw)\r
593                 \r
594                 self.lastDraw = time.time() - 1.0\r
595                 self.points = []\r
596                 self.backBuffer = None\r
597                 self.addPoint(0,0,0,0)\r
598                 self.SetMinSize((320,200))\r
599         \r
600         def OnEraseBackground(self, e):\r
601                 pass\r
602         \r
603         def OnSize(self, e):\r
604                 if self.backBuffer == None or self.GetSize() != self.backBuffer.GetSize():\r
605                         self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())\r
606                         self.UpdateDrawing(True)\r
607         \r
608         def OnDraw(self, e):\r
609                 dc = wx.BufferedPaintDC(self, self.backBuffer)\r
610         \r
611         def UpdateDrawing(self, force = False):\r
612                 now = time.time()\r
613                 if not force and now - self.lastDraw < 1.0:\r
614                         return\r
615                 self.lastDraw = now\r
616                 dc = wx.MemoryDC()\r
617                 dc.SelectObject(self.backBuffer)\r
618                 dc.Clear()\r
619                 w, h = self.GetSizeTuple()\r
620                 bgLinePen = wx.Pen('#A0A0A0')\r
621                 tempPen = wx.Pen('#FF4040')\r
622                 tempSPPen = wx.Pen('#FFA0A0')\r
623                 tempPenBG = wx.Pen('#FFD0D0')\r
624                 bedTempPen = wx.Pen('#4040FF')\r
625                 bedTempSPPen = wx.Pen('#A0A0FF')\r
626                 bedTempPenBG = wx.Pen('#D0D0FF')\r
627 \r
628                 #Draw the background up to the current temperatures.\r
629                 x0 = 0\r
630                 t0 = 0\r
631                 bt0 = 0\r
632                 tSP0 = 0\r
633                 btSP0 = 0\r
634                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:\r
635                         x1 = int(w - (now - t))\r
636                         for x in xrange(x0, x1 + 1):\r
637                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0\r
638                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0\r
639                                 dc.SetPen(tempPenBG)\r
640                                 dc.DrawLine(x, h, x, h - (t * h / 300))\r
641                                 dc.SetPen(bedTempPenBG)\r
642                                 dc.DrawLine(x, h, x, h - (bt * h / 300))\r
643                         t0 = temp\r
644                         bt0 = bedTemp\r
645                         tSP0 = tempSP\r
646                         btSP0 = bedTempSP\r
647                         x0 = x1 + 1\r
648 \r
649                 #Draw the grid\r
650                 for x in xrange(w, 0, -30):\r
651                         dc.SetPen(bgLinePen)\r
652                         dc.DrawLine(x, 0, x, h)\r
653                 for y in xrange(h-1, 0, -h * 50 / 300):\r
654                         dc.SetPen(bgLinePen)\r
655                         dc.DrawLine(0, y, w, y)\r
656                 dc.DrawLine(0, 0, w, 0)\r
657                 dc.DrawLine(0, 0, 0, h)\r
658                 \r
659                 #Draw the main lines\r
660                 x0 = 0\r
661                 t0 = 0\r
662                 bt0 = 0\r
663                 tSP0 = 0\r
664                 btSP0 = 0\r
665                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:\r
666                         x1 = int(w - (now - t))\r
667                         for x in xrange(x0, x1 + 1):\r
668                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0\r
669                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0\r
670                                 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP - tSP0) + tSP0\r
671                                 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0\r
672                                 dc.SetPen(tempSPPen)\r
673                                 dc.DrawPoint(x, h - (tSP * h / 300))\r
674                                 dc.SetPen(bedTempSPPen)\r
675                                 dc.DrawPoint(x, h - (btSP * h / 300))\r
676                                 dc.SetPen(tempPen)\r
677                                 dc.DrawPoint(x, h - (t * h / 300))\r
678                                 dc.SetPen(bedTempPen)\r
679                                 dc.DrawPoint(x, h - (bt * h / 300))\r
680                         t0 = temp\r
681                         bt0 = bedTemp\r
682                         tSP0 = tempSP\r
683                         btSP0 = bedTempSP\r
684                         x0 = x1 + 1\r
685                 \r
686                 del dc\r
687                 self.Refresh(eraseBackground=False)\r
688                 self.Update()\r
689                 \r
690                 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:\r
691                         self.points.pop(0)\r
692 \r
693         def addPoint(self, temp, tempSP, bedTemp, bedTempSP):\r
694                 if bedTemp == None:\r
695                         bedTemp = 0\r
696                 if bedTempSP == None:\r
697                         bedTempSP = 0\r
698                 self.points.append((temp, tempSP, bedTemp, bedTempSP, time.time()))\r
699                 wx.CallAfter(self.UpdateDrawing)\r
700 \r
701 class LogWindow(wx.Frame):\r
702         def __init__(self, logText):\r
703                 super(LogWindow, self).__init__(None, title="Machine log")\r
704                 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_READONLY)\r
705                 self.SetSize((500,400))\r
706                 self.Centre()\r
707                 self.Show(True)\r