import wx
import power
import time
-import os
-import datetime
from wx.lib import buttons
from Cura.util import profile
from Cura.util import resources
-from Cura.gui.util import webcam
class printWindow(wx.Frame):
"Main user interface window"
self.UpdateButtonStates()
- if webcam.hasWebcamSupport():
- #Need to call the camera class on the GUI thread, or else it won't work. Shame as it hangs the GUI for about 2 seconds.
- wx.CallAfter(self._webcamCheck)
-
self._printerConnection.addCallback(self._doPrinterConnectionUpdate)
- def _webcamCheck(self):
- self.cam = webcam.webcam()
- if self.cam.hasCamera():
- self.camPage = wx.Panel(self.tabs)
- sizer = wx.GridBagSizer(2, 2)
- self.camPage.SetSizer(sizer)
-
- self.timelapsEnable = wx.CheckBox(self.camPage, -1, _("Enable timelapse movie recording"))
- self.timelapsSavePath = wx.TextCtrl(self.camPage, -1, os.path.expanduser('~/timelaps_' + datetime.datetime.now().strftime('%Y-%m-%d_%H-%M') + '.mpg'))
- sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
- sizer.Add(self.timelapsSavePath, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
-
- pages = self.cam.propertyPages()
- self.cam.buttons = [self.timelapsEnable, self.timelapsSavePath]
- for page in pages:
- button = wx.Button(self.camPage, -1, page)
- button.index = pages.index(page)
- sizer.Add(button, pos=(2, pages.index(page)))
- button.Bind(wx.EVT_BUTTON, self.OnWebcamPropertyPageButton)
- self.cam.buttons.append(button)
-
- self.campreviewEnable = wx.CheckBox(self.camPage, -1, _("Show preview"))
- sizer.Add(self.campreviewEnable, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
-
- self.camPreview = wx.Panel(self.camPage)
- sizer.Add(self.camPreview, pos=(4, 0), span=(1, 2), flag=wx.EXPAND)
-
- self.tabs.AddPage(self.camPage, _("Camera"))
- self.camPreview.timer = wx.Timer(self)
- self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
- self.camPreview.timer.Start(500)
- self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
- else:
- self.cam = None
-
def OnPowerWarningChange(self, e):
type = self.powerManagement.get_providing_power_source_type()
if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
self.temperatureHeatUp.Enable(self._printerConnection.isAbleToSendDirectCommand())
self.termInput.Enable(self._printerConnection.isAbleToSendDirectCommand())
- def OnWebcamPropertyPageButton(self, e):
- self.cam.openPropertyPage(e.GetEventObject().index)
-
- def OnCameraTimer(self, e):
- if not self.campreviewEnable.GetValue():
- return
- if self.machineCom is not None and self.machineCom.isPrinting():
- return
- self.cam.takeNewImage()
- self.camPreview.Refresh()
-
- def OnCameraEraseBackground(self, e):
- dc = e.GetDC()
- if not dc:
- dc = wx.ClientDC(self)
- rect = self.GetUpdateRegion().GetBox()
- dc.SetClippingRect(rect)
- dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
- if self.cam.getLastImage() is not None:
- self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
- self.camPage.Fit()
- dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
- else:
- dc.Clear()
-
class PrintCommandButton(buttons.GenBitmapButton):
def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
self.bitmap = wx.Bitmap(resources.getPathForImage(bitmapFilename))
def OnPrintButton(self, button):
if button == 1:
- connectionEntry = self._printerConnectionManager.getAvailableConnection()
+ connectionGroup = self._printerConnectionManager.getAvailableGroup()
if machineCom.machineIsConnected():
self.showPrintWindow()
- elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
+ elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
drives = removableStorage.getPossibleSDcardDrives()
if len(drives) > 1:
dlg = wx.SingleChoiceDialog(self, "Select SD drive", "Multiple removable drives have been found,\nplease select your SD card drive", map(lambda n: n[0], drives))
drive = drives[0]
filename = self._scene._objectList[0].getName() + '.gcode'
threading.Thread(target=self._copyFile,args=(self._gcodeFilename, drive[1] + filename, drive[1])).start()
- elif connectionEntry is not None:
- connection = connectionEntry.connection
- if connectionEntry.window is None or not connectionEntry.window:
- connectionEntry.window = printWindow2.printWindow(connection)
- connectionEntry.window.Show()
- connectionEntry.window.Raise()
- if not connection.loadFile(self._gcodeFilename):
- if connection.isPrinting():
- self.notification.message("Cannot start print, because other print still running.")
- else:
- self.notification.message("Failed to start print...")
+ elif connectionGroup is not None:
+ connections = connectionGroup.getAvailableConnections()
+ if len(connections) < 2:
+ connection = connections[0]
+ else:
+ dlg = wx.SingleChoiceDialog(self, "Select the %s connection to use" % (connectionGroup.getName()), "Multiple %s connections found" % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
+ if dlg.ShowModal() != wx.ID_OK:
+ dlg.Destroy()
+ return
+ connection = connections[dlg.GetSelection()]
+ dlg.Destroy()
+ self._openPrintWindowForConnection(connection)
else:
self.showSaveGCode()
if button == 3:
menu = wx.Menu()
self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
+ connections = self._printerConnectionManager.getAvailableConnections()
+ menu.connectionMap = {}
+ for connection in connections:
+ i = menu.Append(-1, _("Print with %s") % (connection.getName()))
+ menu.connectionMap[i.GetId()] = connection
+ self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
self.PopupMenu(menu)
menu.Destroy()
+ def _openPrintWindowForConnection(self, connection):
+ print '_openPrintWindowForConnection', connection.getName()
+ if connection.window is None or not connection.window:
+ connection.window = printWindow2.printWindow(connection)
+ connection.window.Show()
+ connection.window.Raise()
+ if not connection.loadFile(self._gcodeFilename):
+ if connection.isPrinting():
+ self.notification.message("Cannot start print, because other print still running.")
+ else:
+ self.notification.message("Failed to start print...")
+
def showPrintWindow(self):
if self._gcodeFilename is None:
return
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
def OnPaint(self,e):
- connectionEntry = self._printerConnectionManager.getAvailableConnection()
+ connectionGroup = self._printerConnectionManager.getAvailableGroup()
if machineCom.machineIsConnected():
self.printButton._imageID = 6
self.printButton._tooltip = _("Print")
- elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
+ elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
self.printButton._imageID = 2
self.printButton._tooltip = _("Toolpath to SD")
- elif connectionEntry is not None:
- self.printButton._imageID = connectionEntry.icon
- self.printButton._tooltip = _("Print with %s") % (connectionEntry.name)
+ elif connectionGroup is not None:
+ self.printButton._imageID = connectionGroup.getIconID()
+ self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
else:
self.printButton._imageID = 3
self.printButton._tooltip = _("Save toolpath")
from Cura.util.printerConnection import printerConnectionBase
-#Class to connect and print files with the doodle3d.com wifi box
-# Auto-detects if the Doodle3D box is available with a printer
-class doodle3dConnect(printerConnectionBase.printerConnectionBase):
+class doodle3dConnectionGroup(printerConnectionBase.printerConnectionGroup):
PRINTER_LIST_HOST = 'connect.doodle3d.com'
PRINTER_LIST_PATH = '/api/list.php'
def __init__(self):
- super(doodle3dConnect, self).__init__()
+ super(doodle3dConnectionGroup, self).__init__("Doodle3D")
+ self._http = None
+ self._host = self.PRINTER_LIST_HOST
+ self._connectionMap = {}
+
+ self._thread = threading.Thread(target=self._doodle3DThread)
+ self._thread.daemon = True
+ self._thread.start()
+
+ def getAvailableConnections(self):
+ return filter(lambda c: c.isAvailable(), self._connectionMap.values())
+
+ def getIconID(self):
+ return 27
+
+ def getPriority(self):
+ return 100
+
+ def __cmp__(self, other):
+ return self.getPriority() - other.getPriority()
+
+ def __repr__(self):
+ return self.name
+
+ def _doodle3DThread(self):
+ self._waitDelay = 0
+ while True:
+ printerList = self._request('GET', self.PRINTER_LIST_PATH)
+ if not printerList or type(printerList) is not dict or 'data' not in printerList or type(printerList['data']) is not list:
+ #Check if we are connected to the Doodle3D box in access point mode, as this gives an
+ # invalid reply on the printer list API
+ printerList = {'data': [{'localip': 'draw.doodle3d.com'}]}
+
+ #Add the 192.168.5.1 IP to the list of printers to check, as this is the LAN port IP, which could also be available.
+ # (connect.doodle3d.com also checks for this IP in the javascript code)
+ printerList['data'].append({'localip': '192.168.5.1'})
+
+ #Check the status of each possible IP, if we find a valid box with a printer connected. Use that IP.
+ for possiblePrinter in printerList['data']:
+ if possiblePrinter['localip'] not in self._connectionMap:
+ status = self._request('GET', '/d3dapi/config/?network.cl.wifiboxid=', host=possiblePrinter['localip'])
+ if status and 'data' in status and 'network.cl.wifiboxid' in status['data']:
+ self._connectionMap[possiblePrinter['localip']] = doodle3dConnect(possiblePrinter['localip'], status['data']['network.cl.wifiboxid'], self)
+
+ # Delay a bit more after every request. This so we do not stress the connect.doodle3d.com api too much
+ if self._waitDelay < 10:
+ self._waitDelay += 1
+ time.sleep(self._waitDelay * 60)
+
+ def _request(self, method, path, postData = None, host = None):
+ if host is None:
+ host = self._host
+ if self._http is None or self._http.host != host:
+ self._http = httpclient.HTTPConnection(host, timeout=30)
+
+ try:
+ if postData is not None:
+ self._http.request(method, path, urllib.urlencode(postData), {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"})
+ else:
+ self._http.request(method, path, headers={"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"})
+ except:
+ self._http.close()
+ return None
+ try:
+ response = self._http.getresponse()
+ responseText = response.read()
+ except:
+ self._http.close()
+ return None
+ try:
+ response = json.loads(responseText)
+ except ValueError:
+ self._http.close()
+ return None
+ if response['status'] != 'success':
+ return False
+
+ return response
+
+#Class to connect and print files with the doodle3d.com wifi box
+# Auto-detects if the Doodle3D box is available with a printer
+class doodle3dConnect(printerConnectionBase.printerConnectionBase):
+ def __init__(self, host, name, group):
+ super(doodle3dConnect, self).__init__(name)
self._http = None
- self._host = None
+ self._group = group
+ self._host = host
+
self._isAvailable = False
self._printing = False
self._fileBlocks = []
self._hotendTemperature = [None] * 4
self._bedTemperature = None
self._errorCount = 0
+ self._interruptSleep = False
self.checkThread = threading.Thread(target=self._doodle3DThread)
self.checkThread.daemon = True
self._progressLine = 0
self._blockIndex = 0
self._printing = True
+ self._interruptSleep = True
#Abort the previously loaded print file
def cancelPrint(self):
if not self._isAvailable or self._printing:
return
self._commandList.append(command)
+ self._interruptSleep = True
# Get the connection status string. This is displayed to the user and can be used to communicate
# various information to the user.
return self._bedTemperature
def _doodle3DThread(self):
- waitDelay = 0
while True:
- while self._host is None:
- printerList = self._request('GET', self.PRINTER_LIST_PATH, host=self.PRINTER_LIST_HOST)
- if not printerList or type(printerList) is not dict or 'data' not in printerList or type(printerList['data']) is not list:
- #Check if we are connected to the Doodle3D box in access point mode, as this gives an
- # invalid reply on the printer list API
- printerList = {'data': [{'localip': 'draw.doodle3d.com'}]}
-
- #Add the 192.168.5.1 IP to the list of printers to check, as this is the LAN port IP, which could also be available.
- # (connect.doodle3d.com also checks for this IP in the javascript code)
- printerList['data'].append({'localip': '192.168.5.1'})
-
- #Check the status of each possible IP, if we find a valid box with a printer connected. Use that IP.
- for possiblePrinter in printerList['data']:
- status = self._request('GET', '/d3dapi/info/status', host=possiblePrinter['localip'])
- if status and 'data' in status:
- self._host = possiblePrinter['localip']
- break
-
- if self._host is None:
- #If we cannot find a doodle3d box, delay a minute and request the list again.
- # This so we do not stress the connect.doodle3d.com api too much
- if waitDelay < 10:
- waitDelay += 1
- time.sleep(waitDelay * 60)
- else:
- #If we found a doodle3D box, reset the wait delay, so we can find it again in case it gets lost
- self._errorCount = 0
- waitDelay = 0
-
stateReply = self._request('GET', '/d3dapi/info/status')
if stateReply is None or not stateReply:
- # No API, wait 15 seconds before looking for Doodle3D again.
+ # No API, wait 5 seconds before looking for Doodle3D again.
# API gave back an error (this can happen if the Doodle3D box is connecting to the printer)
+ # The Doodle3D box could also be offline, if we reach a high enough errorCount then assume the box is gone.
self._errorCount += 1
if self._errorCount > 10:
self._host = None
self._printing = False
self._isAvailable = False
self._doCallback()
- time.sleep(15)
+ self._sleep(15)
+ else:
+ self._sleep(3)
continue
if stateReply['data']['state'] == 'disconnected':
- # No printer connected
+ # No printer connected, we do not have a printer available, but the Doodle3D box is there.
+ # So keep trying to find a printer connected to it.
if self._isAvailable:
self._printing = False
self._isAvailable = False
self._doCallback()
- time.sleep(5)
+ self._sleep(15)
continue
self._errorCount = 0
if self._request('POST', '/d3dapi/printer/print', {'gcode': self._fileBlocks[self._blockIndex], 'start': 'True', 'first': 'True'}):
self._blockIndex += 1
else:
- time.sleep(1)
+ self._sleep(1)
else:
self._printing = False
else:
if self._request('POST', '/d3dapi/printer/print', {'gcode': self._commandList[0], 'start': 'True', 'first': 'True'}):
self._commandList.pop(0)
else:
- time.sleep(1)
+ self._sleep(1)
else:
- time.sleep(5)
+ self._sleep(5)
elif stateReply['data']['state'] == 'printing':
if self._printing:
if self._blockIndex < len(self._fileBlocks):
self._blockIndex += 1
else:
#Cannot send new block, wait a bit, so we do not overload the API
- time.sleep(15)
+ self._sleep(15)
break
else:
#If we are no longer sending new GCode delay a bit so we request the status less often.
- time.sleep(5)
+ self._sleep(5)
if 'current_line' in stateReply['data']:
self._progressLine = stateReply['data']['current_line']
else:
self._blockIndex = 1
self._progressLine = stateReply['data']['current_line']
self._lineCount = stateReply['data']['total_lines']
- time.sleep(5)
+ self._sleep(5)
self._doCallback()
+ def _sleep(self, timeOut):
+ while timeOut > 0.0:
+ if not self._interruptSleep:
+ time.sleep(0.1)
+ timeOut -= 0.1
+ self._interruptSleep = False
+
def _request(self, method, path, postData = None, host = None):
if host is None:
host = self._host
from Cura.util.printerConnection import printerConnectionBase
+class dummyConnectionGroup(printerConnectionBase.printerConnectionGroup):
+ def __init__(self):
+ super(dummyConnectionGroup, self).__init__("Dummy")
+ self._list = [dummyConnection("Dummy 1"), dummyConnection("Dummy 2")]
+
+ def getAvailableConnections(self):
+ return self._list
+
#Dummy printer class which is always
class dummyConnection(printerConnectionBase.printerConnectionBase):
- def __init__(self):
- super(dummyConnection, self).__init__()
+ def __init__(self, name):
+ super(dummyConnection, self).__init__(name)
self._printing = False
self._lineCount = 0
import traceback
+class printerConnectionGroup(object):
+ def __init__(self, name):
+ self._name = name
+
+ def getAvailableConnections(self):
+ return []
+
+ def getName(self):
+ return self._name
+
+ def getIconID(self):
+ return 5
+
+ def getPriority(self):
+ return -1
+
+ def __cmp__(self, other):
+ return self.getPriority() - other.getPriority()
+
+ def __repr__(self):
+ return self.name
+
#Base class for different printer connection implementations.
# A printer connection can connect to printers in different ways, trough network, USB or carrier pigeons.
# Each printer connection has different capabilities that you can query with the "has" functions.
# Each printer connection has callback objects that receive status updates from the printer when information changes.
class printerConnectionBase(object):
- def __init__(self):
+ def __init__(self, name):
self._callbackList = []
+ self._name = name
+ self.window = None
+
+ def getName(self):
+ return self._name
#Load the file into memory for printing, returns True on success
def loadFile(self, filename):
from Cura.util.printerConnection import dummyConnection
from Cura.util.printerConnection import doodle3dConnect
-class connectionEntry(object):
- def __init__(self, name, priority, icon, connection):
- self.name = name
- self.priority = priority
- self.icon = icon
- self.connection = connection
- self.window = None
-
- def __cmp__(self, other):
- return self.priority - other.priority
-
- def __repr__(self):
- return self.name
-
class PrinterConnectionManager(object):
def __init__(self):
- self._connectionList = []
+ self._groupList = []
if version.isDevVersion():
- self._connectionList.append(connectionEntry('Dummy', -1, 5, dummyConnection.dummyConnection()))
- self._connectionList.append(connectionEntry('Doodle3D', 100, 27, doodle3dConnect.doodle3dConnect()))
+ self._groupList.append(dummyConnection.dummyConnectionGroup())
+ #self._groupList.append(doodle3dConnect.doodle3dConnectionGroup())
#Sort the connections by highest priority first.
- self._connectionList.sort(reverse=True)
+ self._groupList.sort(reverse=True)
#Return the highest priority available connection.
- def getAvailableConnection(self):
- for e in self._connectionList:
- if e.connection.isAvailable():
- return e
+ def getAvailableGroup(self):
+ for g in self._groupList:
+ if len(g.getAvailableConnections()) > 0:
+ return g
return None
+
+ #Return all available connections.
+ def getAvailableConnections(self):
+ ret = []
+ for e in self._groupList:
+ ret += e.getAvailableConnections()
+ return ret