Merge branch 'devel' into events

Conflicts:
	octoprint/printer.py
	octoprint/server.py
	octoprint/util/comm.py
master
Gina Häußge 2013-06-16 11:50:52 +02:00
commit ed9e93f379
13 changed files with 742 additions and 503 deletions

View File

@ -6,17 +6,17 @@ OctoPrint
OctoPrint provides a responsive web interface for controlling a 3D printer (RepRap, Ultimaker, ...). It currently OctoPrint provides a responsive web interface for controlling a 3D printer (RepRap, Ultimaker, ...). It currently
allows allows
* uploading .gcode files to the server and managing them via the UI * uploading .gcode files to the server plus optionally the printer's SD card and managing them via the UI
* selecting a file for printing, getting the usual stats regarding filament length etc (stats can be disabled for * selecting a file for printing, getting the usual stats regarding filament length etc (stats not available for SD files)
faster initial processing)
* starting, pausing and canceling a print job * starting, pausing and canceling a print job
* while connected to the printer, gaining information regarding the current temperature of both head and bed (if available) in a nice shiny javascript-y temperature graph * while connected to the printer, gaining information regarding the current temperature of both head and bed (if available) in a nice shiny javascript-y temperature graph
* while printing, gaining information regarding the current progress of the print job (height, percentage etc) * while printing, gaining information regarding the current progress of the print job (height, percentage etc)
* reading the communication log and send arbitrary codes to be executed by the printer * reading the communication log and send arbitrary codes to be executed by the printer
* moving the X, Y and Z axis (jog controls), extruding, retracting and custom controls * moving the X, Y and Z axis (jog controls), extruding, retracting and custom controls
* optional: previewing the GCODE of the selected model to print (via gCodeVisualizer), including rendering of the progress during printing * previewing the GCODE of the selected model to print (via gCodeVisualizer), including rendering of the progress during printing (not available when SD printing)
* optional: visual monitoring of the printer via webcam stream integrated into the UI (using e.g. MJPG-Streamer) * optional: visual monitoring of the printer via webcam stream integrated into the UI (using e.g. MJPG-Streamer)
* optional: creation of timelapse recordings of the printjob via webcam stream (using e.g. MJPG-Streamer) -- currently two timelaspe methods are implemented, triggering a shot on z-layer change or every "n" seconds * optional: creation of timelapse recordings of the printjob via webcam stream (using e.g. MJPG-Streamer) -- currently two timelaspe methods are implemented, triggering a shot on z-layer change or every "n" seconds
* optional: access control to provide a read-only mode on the web interface, allowing any actions only to logged in users
The intended usecase is to run OctoPrint on a single-board computer like the Raspberry Pi and a WiFi module, The intended usecase is to run OctoPrint on a single-board computer like the Raspberry Pi and a WiFi module,
connect the printer to the server and therefore create a WiFi-enabled 3D printer. If you want to add a webcam for visual connect the printer to the server and therefore create a WiFi-enabled 3D printer. If you want to add a webcam for visual
@ -54,7 +54,7 @@ Alternatively, the host and port on which to bind can be defined via the configu
If you want to run OctoPrint as a daemon (only supported on Linux), use If you want to run OctoPrint as a daemon (only supported on Linux), use
./run --daemon {start|stop|restart} [--pid PIDFILE] ./run --daemon {start|stop|restart} [--pid PIDFILE]
If you do not supply a custom pidfile location via `--pid PIDFILE`, it will be created at `/tmp/octoprint.pid`. If you do not supply a custom pidfile location via `--pid PIDFILE`, it will be created at `/tmp/octoprint.pid`.
@ -106,8 +106,3 @@ The following software is recommended for Webcam support on the Raspberry Pi:
* MJPG-Streamer: http://sourceforge.net/apps/mediawiki/mjpg-streamer/index.php?title=Main_Page * MJPG-Streamer: http://sourceforge.net/apps/mediawiki/mjpg-streamer/index.php?title=Main_Page
I also want to thank [Janina Himmen](http://jhimmen.de/) for providing the kick-ass logo! I also want to thank [Janina Himmen](http://jhimmen.de/) for providing the kick-ass logo!
Why is it called OctoPrint and what's with the crystal ball in the logo?
------------------------------------------------------------------------
It so happens that I needed a favicon and also OctoPrint's first name -- Printer WebUI -- simply lacked a certain coolness to it. So I asked The Internet(tm) for advise. After some brainstorming, the idea of a cute Octopus watching his print job remotely through a crystal ball was born... [or something like that](https://plus.google.com/u/0/106003970953341660077/posts/UmLD5mW8yBQ).

View File

@ -61,18 +61,17 @@ class Printer():
self._printTime = None self._printTime = None
self._printTimeLeft = None self._printTimeLeft = None
# gcode handling self._printAfterSelect = False
self._gcodeList = None
self._filename = None
self._gcodeLoader = None
# sd handling # sd handling
self._sdPrinting = False self._sdPrinting = False
self._sdStreaming = False
# TODO Still needed?
self._sdFile = None self._sdFile = None
self._sdStreamer = None self._sdStreamer = None
# feedrate self._selectedFile = None
self._feedrateModifierMapping = {"outerWall": "WALL-OUTER", "innerWall": "WALL_INNER", "fill": "FILL", "support": "SUPPORT"}
# timelapse # timelapse
self._timelapse = None self._timelapse = None
@ -93,10 +92,8 @@ class Printer():
) )
self._stateMonitor.reset( self._stateMonitor.reset(
state={"state": None, "stateString": self.getStateString(), "flags": self._getStateFlags()}, state={"state": None, "stateString": self.getStateString(), "flags": self._getStateFlags()},
jobData={"filename": None, "lines": None, "estimatedPrintTime": None, "filament": None}, jobData={"filename": None, "filesize": None, "estimatedPrintTime": None, "filament": None},
gcodeData={"filename": None, "progress": None}, progress={"progress": None, "filepos": None, "printTime": None, "printTimeLeft": None},
sdUploadData={"filename": None, "progress": None},
progress={"progress": None, "printTime": None, "printTimeLeft": None},
currentZ=None currentZ=None
) )
@ -168,52 +165,25 @@ class Printer():
for command in commands: for command in commands:
self._comm.sendCommand(command) self._comm.sendCommand(command)
def setFeedrateModifier(self, structure, percentage): def selectFile(self, filename, sd, printAfterSelect=False):
if (not self._feedrateModifierMapping.has_key(structure)) or percentage < 0: if self._comm is not None and (self._comm.isBusy() or self._comm.isStreaming()):
return return
self._comm.setFeedrateModifier(self._feedrateModifierMapping[structure], percentage / 100.0) self._printAfterSelect = printAfterSelect
self._comm.selectFile(filename, sd)
def loadGcode(self, file, printAfterLoading=False):
"""
Loads the gcode from the given file as the new print job.
Aborts if the printer is currently printing or another gcode file is currently being loaded.
"""
if (self._comm is not None and self._comm.isPrinting()) or (self._gcodeLoader is not None):
return
self._sdFile = None
self._setJobData(None, None)
onGcodeLoadedCallback = self._onGcodeLoaded
if printAfterLoading:
onGcodeLoadedCallback = self._onGcodeLoadedToPrint
self._gcodeLoader = GcodeLoader(file, self._onGcodeLoadingProgress, onGcodeLoadedCallback)
self._gcodeLoader.start()
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
def startPrint(self): def startPrint(self):
""" """
Starts the currently loaded print job. Starts the currently loaded print job.
Only starts if the printer is connected and operational, not currently printing and a printjob is loaded Only starts if the printer is connected and operational, not currently printing and a printjob is loaded
""" """
if self._comm is None or not self._comm.isOperational(): if self._comm is None or not self._comm.isOperational() or self._comm.isPrinting():
return return
if self._gcodeList is None and self._sdFile is None: if self._selectedFile is None:
return
if self._comm.isPrinting():
return return
self._setCurrentZ(-1) self._setCurrentZ(None)
if self._sdFile is not None: self._comm.startPrint()
# we are working in sd mode
self._sdPrinting = True
self._comm.printSdFile()
else:
# we are working in local mode
self._comm.printGCode(self._gcodeList)
def togglePausePrint(self): def togglePausePrint(self):
""" """
@ -221,6 +191,7 @@ class Printer():
""" """
if self._comm is None: if self._comm is None:
return return
self._comm.setPause(not self._comm.isPaused()) self._comm.setPause(not self._comm.isPaused())
def cancelPrint(self, disableMotorsAndHeater=True): def cancelPrint(self, disableMotorsAndHeater=True):
@ -230,23 +201,19 @@ class Printer():
if self._comm is None: if self._comm is None:
return return
if self._sdPrinting:
self._sdPrinting = False
self._comm.cancelPrint() self._comm.cancelPrint()
if disableMotorsAndHeater: if disableMotorsAndHeater:
self.commands(["M84", "M104 S0", "M140 S0", "M106 S0"]) # disable motors, switch off heaters and fan self.commands(["M84", "M104 S0", "M140 S0", "M106 S0"]) # disable motors, switch off heaters and fan
# reset line, height, print time # reset progress, height, print time
self._setCurrentZ(None) self._setCurrentZ(None)
self._setProgressData(None, None, None, None) self._setProgressData(None, None, None, None)
# mark print as failure # mark print as failure
if self._filename is not None: if self._selectedFile is not None:
self._gcodeManager.printFailed(self._filename) self._gcodeManager.printFailed(self._selectedFile["filename"])
eventManager().fire("PrintFailed", self._filename) eventManager().fire("PrintFailed", self._filename)
#~~ state monitoring
def setTimelapse(self, timelapse): def setTimelapse(self, timelapse):
if self._timelapse is not None and self.isPrinting(): if self._timelapse is not None and self.isPrinting():
@ -257,6 +224,8 @@ class Printer():
def getTimelapse(self): def getTimelapse(self):
return self._timelapse return self._timelapse
#~~ state monitoring
def _setCurrentZ(self, currentZ): def _setCurrentZ(self, currentZ):
self._currentZ = currentZ self._currentZ = currentZ
@ -279,7 +248,7 @@ class Printer():
self._messages = self._messages[-300:] self._messages = self._messages[-300:]
self._stateMonitor.addMessage(message) self._stateMonitor.addMessage(message)
def _setProgressData(self, progress, currentLine, printTime, printTimeLeft): def _setProgressData(self, progress, filepos, printTime, printTimeLeft):
self._progress = progress self._progress = progress
self._printTime = printTime self._printTime = printTime
self._printTimeLeft = printTimeLeft self._printTimeLeft = printTimeLeft
@ -292,7 +261,11 @@ class Printer():
if (self._printTimeLeft): if (self._printTimeLeft):
formattedPrintTimeLeft = util.getFormattedTimeDelta(datetime.timedelta(minutes=self._printTimeLeft)) formattedPrintTimeLeft = util.getFormattedTimeDelta(datetime.timedelta(minutes=self._printTimeLeft))
self._stateMonitor.setProgress({"progress": self._progress, "currentLine": currentLine, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft}) formattedFilePos = None
if (filepos):
formattedFilePos = util.getFormattedSize(filepos)
self._stateMonitor.setProgress({"progress": self._progress, "filepos": formattedFilePos, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft})
def _addTemperatureData(self, temp, bedTemp, targetTemp, bedTargetTemp): def _addTemperatureData(self, temp, bedTemp, targetTemp, bedTargetTemp):
currentTimeUtc = int(time.time() * 1000) currentTimeUtc = int(time.time() * 1000)
@ -316,19 +289,25 @@ class Printer():
self._stateMonitor.addTemperature({"currentTime": currentTimeUtc, "temp": self._temp, "bedTemp": self._bedTemp, "targetTemp": self._targetTemp, "targetBedTemp": self._targetBedTemp}) self._stateMonitor.addTemperature({"currentTime": currentTimeUtc, "temp": self._temp, "bedTemp": self._bedTemp, "targetTemp": self._targetTemp, "targetBedTemp": self._targetBedTemp})
def _setJobData(self, filename, gcodeList): def _setJobData(self, filename, filesize, sd):
self._filename = filename if filename is not None:
self._gcodeList = gcodeList self._selectedFile = {
"filename": filename,
lines = None "filesize": filesize,
if self._gcodeList: "sd": sd
lines = len(self._gcodeList) }
else:
self._selectedFile = None
formattedFilename = None formattedFilename = None
formattedFilesize = None
estimatedPrintTime = None estimatedPrintTime = None
filament = None filament = None
if self._filename: if filename:
formattedFilename = os.path.basename(self._filename) formattedFilename = os.path.basename(filename)
if filesize:
formattedFilesize = util.getFormattedSize(filesize)
fileData = self._gcodeManager.getFileData(filename) fileData = self._gcodeManager.getFileData(filename)
if fileData is not None and "gcodeAnalysis" in fileData.keys(): if fileData is not None and "gcodeAnalysis" in fileData.keys():
@ -337,7 +316,7 @@ class Printer():
if "filament" in fileData["gcodeAnalysis"].keys(): if "filament" in fileData["gcodeAnalysis"].keys():
filament = fileData["gcodeAnalysis"]["filament"] filament = fileData["gcodeAnalysis"]["filament"]
self._stateMonitor.setJobData({"filename": formattedFilename, "lines": lines, "estimatedPrintTime": estimatedPrintTime, "filament": filament}) self._stateMonitor.setJobData({"filename": formattedFilename, "filesize": formattedFilesize, "estimatedPrintTime": estimatedPrintTime, "filament": filament, "sd": sd})
def _sendInitialStateUpdate(self, callback): def _sendInitialStateUpdate(self, callback):
try: try:
@ -364,7 +343,6 @@ class Printer():
"printing": self.isPrinting(), "printing": self.isPrinting(),
"closedOrError": self.isClosedOrError(), "closedOrError": self.isClosedOrError(),
"error": self.isError(), "error": self.isError(),
"loading": self.isLoading(),
"paused": self.isPaused(), "paused": self.isPaused(),
"ready": self.isReady(), "ready": self.isReady(),
"sdReady": sdReady "sdReady": sdReady
@ -395,14 +373,15 @@ class Printer():
if oldState == self._comm.STATE_PRINTING and state != self._comm.STATE_PAUSED: if oldState == self._comm.STATE_PRINTING and state != self._comm.STATE_PAUSED:
self._timelapse.onPrintjobStopped() self._timelapse.onPrintjobStopped()
elif state == self._comm.STATE_PRINTING and oldState != self._comm.STATE_PAUSED: elif state == self._comm.STATE_PRINTING and oldState != self._comm.STATE_PAUSED:
self._timelapse.onPrintjobStarted(self._filename) self._timelapse.onPrintjobStarted(self._selectedFile["filename"])
# forward relevant state changes to gcode manager # forward relevant state changes to gcode manager
if self._comm is not None and oldState == self._comm.STATE_PRINTING: if self._comm is not None and oldState == self._comm.STATE_PRINTING:
if state == self._comm.STATE_OPERATIONAL: if self._selectedFile is not None:
self._gcodeManager.printSucceeded(self._filename) if state == self._comm.STATE_OPERATIONAL:
elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR: self._gcodeManager.printSucceeded(self._selectedFile["filename"])
self._gcodeManager.printFailed(self._filename) elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR:
self._gcodeManager.printFailed(self._selectedFile["filename"])
self._gcodeManager.resumeAnalysis() # printing done, put those cpu cycles to good use self._gcodeManager.resumeAnalysis() # printing done, put those cpu cycles to good use
elif self._comm is not None and state == self._comm.STATE_PRINTING: elif self._comm is not None and state == self._comm.STATE_PRINTING:
self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing
@ -419,25 +398,10 @@ class Printer():
def mcProgress(self): def mcProgress(self):
""" """
Callback method for the comm object, called upon any change in progress of the printjob. Callback method for the comm object, called upon any change in progress of the printjob.
Triggers storage of new values for printTime, printTimeLeft and the current line. Triggers storage of new values for printTime, printTimeLeft and the current progress.
""" """
oldProgress = self._progress
if self._sdPrinting: self._setProgressData(self._comm.getPrintProgress(), self._comm.getPrintFilepos(), self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
newLine = None
(filePos, fileSize) = self._comm.getSdProgress()
if fileSize > 0:
newProgress = float(filePos) / float(fileSize)
else:
newProgress = 0.0
else:
newLine = self._comm.getPrintPos()
if self._gcodeList is not None:
newProgress = float(newLine) / float(len(self._gcodeList))
else:
newProgress = 0.0
self._setProgressData(newProgress, newLine, self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
def mcZChange(self, newZ): def mcZChange(self, newZ):
""" """
@ -460,33 +424,49 @@ class Printer():
def mcSdFiles(self, files): def mcSdFiles(self, files):
self._sendTriggerUpdateCallbacks("gcodeFiles") self._sendTriggerUpdateCallbacks("gcodeFiles")
def mcSdSelected(self, filename, filesize): def mcFileSelected(self, filename, filesize, sd):
self._sdFile = filename self._setJobData(filename, filesize, sd)
self._setJobData(filename, None)
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
if self._sdPrintAfterSelect: if self._printAfterSelect:
self.startPrint() self.startPrint()
def mcSdPrintingDone(self): def mcPrintjobDone(self):
self._sdPrinting = False self._setProgressData(1.0, self._selectedFile["filesize"], self._comm.getPrintTime(), 0)
self._setProgressData(1.0, None, self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
#~~ sd file handling def mcFileTransferStarted(self, filename, filesize):
self._sdStreaming = True
self._selectedFile = {
"filename": filename,
"filesize": filesize,
"sd": True
}
self._setJobData(filename, filesize, True)
self._setProgressData(0.0, 0, 0, None)
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
def mcFileTransferDone(self):
self._sdStreaming = False
self._selectedFile = None
self._setCurrentZ(None)
self._setJobData(None, None, None)
self._setProgressData(None, None, None, None)
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
#~~ sd file handling
def getSdFiles(self): def getSdFiles(self):
if self._comm is None: if self._comm is None:
return return
return self._comm.getSdFiles() return self._comm.getSdFiles()
def addSdFile(self, filename, file): def addSdFile(self, filename, path):
if not self._comm: if not self._comm or self._comm.isBusy():
return return
self._comm.startFileTransfer(path, filename[:8].lower() + ".gco")
self._sdStreamer = SdFileStreamer(self._comm, filename, file, self._onSdFileStreamProgress, self._onSdFileStreamFinish)
self._sdStreamer.start()
def deleteSdFile(self, filename): def deleteSdFile(self, filename):
if not self._comm: if not self._comm:
@ -496,13 +476,6 @@ class Printer():
self._sdFile = None self._sdFile = None
self._comm.deleteSdFile(filename) self._comm.deleteSdFile(filename)
def selectSdFile(self, filename, printAfterSelect):
if not self._comm:
return
self._sdPrintAfterSelect = printAfterSelect
self._comm.selectSdFile(filename)
def initSdCard(self): def initSdCard(self):
if not self._comm: if not self._comm:
return return
@ -518,57 +491,8 @@ class Printer():
return return
self._comm.refreshSdFiles() self._comm.refreshSdFiles()
#~~ callbacks triggered by sdFileStreamer
def _onSdFileStreamProgress(self, filename, progress):
self._stateMonitor.setSdUploadData({"filename": filename, "progress": progress})
def _onSdFileStreamFinish(self, filename):
self._setCurrentZ(None)
self._setProgressData(None, None, None, None)
self._sdStreamer = None
self._stateMonitor.setSdUploadData({"filename": None, "progress": None})
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
#~~ callbacks triggered by gcodeLoader
def _onGcodeLoadingProgress(self, filename, progress, mode):
formattedFilename = None
if filename is not None:
formattedFilename = os.path.basename(filename)
self._stateMonitor.setGcodeData({"filename": formattedFilename, "progress": progress, "mode": mode})
def _onGcodeLoaded(self, filename, gcodeList):
self._setJobData(filename, gcodeList)
self._setCurrentZ(None)
self._setProgressData(None, None, None, None)
self._gcodeLoader = None
self._stateMonitor.setGcodeData({"filename": None, "progress": None})
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
eventManager().fire("LoadDone", filename)
def _onGcodeLoadedToPrint(self, filename, gcodeList):
self._onGcodeLoaded(filename, gcodeList)
self.startPrint()
#~~ state reports #~~ state reports
def feedrateState(self):
if self._comm is not None:
feedrateModifiers = self._comm.getFeedrateModifiers()
result = {}
for structure in self._feedrateModifierMapping.keys():
if (feedrateModifiers.has_key(self._feedrateModifierMapping[structure])):
result[structure] = int(round(feedrateModifiers[self._feedrateModifierMapping[structure]] * 100))
else:
result[structure] = 100
return result
else:
return None
def getStateString(self): def getStateString(self):
""" """
Returns a human readable string corresponding to the current communication state. Returns a human readable string corresponding to the current communication state.
@ -578,6 +502,21 @@ class Printer():
else: else:
return self._comm.getStateString() return self._comm.getStateString()
def getCurrentData(self):
return self._stateMonitor.getCurrentData()
def getCurrentTemperatures(self):
return {
"extruder": {
"current": self._temp,
"target": self._targetTemp
},
"bed": {
"current": self._bedTemp,
"target": self._targetBedTemp
}
}
def isClosedOrError(self): def isClosedOrError(self):
return self._comm is None or self._comm.isClosedOrError() return self._comm is None or self._comm.isClosedOrError()
@ -594,13 +533,11 @@ class Printer():
return self._comm is not None and self._comm.isError() return self._comm is not None and self._comm.isError()
def isReady(self): def isReady(self):
return self._gcodeLoader is None and self._sdStreamer is None and ((self._gcodeList and len(self._gcodeList) > 0) or self._sdFile) return self.isOperational() and not self._comm.isStreaming()
def isLoading(self): def isLoading(self):
return self._gcodeLoader is not None or self._sdStreamer is not None return self._gcodeLoader is not None or self._sdStreamer is not None
class GcodeLoader(threading.Thread): class GcodeLoader(threading.Thread):
""" """
The GcodeLoader takes care of loading a gcode-File from disk and parsing it into a gcode object in a separate The GcodeLoader takes care of loading a gcode-File from disk and parsing it into a gcode object in a separate
@ -661,7 +598,7 @@ class SdFileStreamer(threading.Thread):
return return
name = self._filename[:self._filename.rfind(".")] name = self._filename[:self._filename.rfind(".")]
sdFilename = name[:8] + ".GCO" sdFilename = name[:8].lower() + ".gco"
try: try:
size = os.stat(self._file).st_size size = os.stat(self._file).st_size
with open(self._file, "r") as f: with open(self._file, "r") as f:
@ -701,11 +638,9 @@ class StateMonitor(object):
self._worker.daemon = True self._worker.daemon = True
self._worker.start() self._worker.start()
def reset(self, state=None, jobData=None, gcodeData=None, sdUploadData=None, progress=None, currentZ=None): def reset(self, state=None, jobData=None, progress=None, currentZ=None):
self.setState(state) self.setState(state)
self.setJobData(jobData) self.setJobData(jobData)
self.setGcodeData(gcodeData)
self.setSdUploadData(sdUploadData)
self.setProgress(progress) self.setProgress(progress)
self.setCurrentZ(currentZ) self.setCurrentZ(currentZ)
@ -733,14 +668,6 @@ class StateMonitor(object):
self._jobData = jobData self._jobData = jobData
self._changeEvent.set() self._changeEvent.set()
def setGcodeData(self, gcodeData):
self._gcodeData = gcodeData
self._changeEvent.set()
def setSdUploadData(self, uploadData):
self._sdUploadData = uploadData
self._changeEvent.set()
def setProgress(self, progress): def setProgress(self, progress):
self._progress = progress self._progress = progress
self._changeEvent.set() self._changeEvent.set()
@ -764,8 +691,6 @@ class StateMonitor(object):
return { return {
"state": self._state, "state": self._state,
"job": self._jobData, "job": self._jobData,
"gcode": self._gcodeData,
"sdUpload": self._sdUploadData,
"currentZ": self._currentZ, "currentZ": self._currentZ,
"progress": self._progress "progress": self._progress
} }

View File

@ -240,23 +240,6 @@ def jog():
return jsonify(SUCCESS) return jsonify(SUCCESS)
@app.route(BASEURL + "control/speed", methods=["GET"])
def getSpeedValues():
return jsonify(feedrate=printer.feedrateState())
@app.route(BASEURL + "control/speed", methods=["POST"])
@login_required
def speed():
if not printer.isOperational():
return jsonify(SUCCESS)
for key in ["outerWall", "innerWall", "fill", "support"]:
if key in request.values.keys():
value = int(request.values[key])
printer.setFeedrateModifier(key, value)
return getSpeedValues()
@app.route(BASEURL + "control/custom", methods=["GET"]) @app.route(BASEURL + "control/custom", methods=["GET"])
def getCustomControls(): def getCustomControls():
customControls = settings().get(["controls"]) customControls = settings().get(["controls"])
@ -279,6 +262,17 @@ def sdCommand():
return jsonify(SUCCESS) return jsonify(SUCCESS)
#~~ Printer State
@app.route(BASEURL + "state", methods=["GET"])
@login_required
def getPrinterState():
currentData = printer.getCurrentData()
currentData.update({
"temperatures": printer.getCurrentTemperatures()
})
return jsonify(currentData)
#~~ GCODE file handling #~~ GCODE file handling
@app.route(BASEURL + "gcodefiles", methods=["GET"]) @app.route(BASEURL + "gcodefiles", methods=["GET"])
@ -323,16 +317,14 @@ def loadGcodeFile():
if "print" in request.values.keys() and request.values["print"] in valid_boolean_trues: if "print" in request.values.keys() and request.values["print"] in valid_boolean_trues:
printAfterLoading = True printAfterLoading = True
sd = False
filename = None
if "target" in request.values.keys() and request.values["target"] == "sd": if "target" in request.values.keys() and request.values["target"] == "sd":
filename = request.values["filename"] filename = request.values["filename"]
printer.selectSdFile(filename, printAfterLoading) sd = True
else: else:
filename = gcodeManager.getAbsolutePath(request.values["filename"]) filename = gcodeManager.getAbsolutePath(request.values["filename"])
if filename is not None: printer.selectFile(filename, sd, printAfterLoading)
printer.loadGcode(filename, printAfterLoading)
global eventManager
eventManager.fire("LoadStart", filename)
return jsonify(SUCCESS) return jsonify(SUCCESS)
@app.route(BASEURL + "gcodefiles/delete", methods=["POST"]) @app.route(BASEURL + "gcodefiles/delete", methods=["POST"])

View File

@ -43,7 +43,7 @@ default_settings = {
"waitForWaitOnConnect": False, "waitForWaitOnConnect": False,
"alwaysSendChecksum": False, "alwaysSendChecksum": False,
"resetLineNumbersWithPrefixedN": False, "resetLineNumbersWithPrefixedN": False,
"sdSupport": False "sdSupport": True
}, },
"folder": { "folder": {
"uploads": None, "uploads": None,

View File

@ -525,4 +525,10 @@ ul.dropdown-menu li a {
} }
} }
} }
}
.icon-sd-black-14 {
background: url("../img/icon-sd-black-14.png") 0 3px no-repeat;
width: 11px;
height: 17px;
} }

View File

@ -223,42 +223,31 @@
var assumeNonDC = false; var assumeNonDC = false;
for(var i=0;i<gcode.length;i++){ for(var i=0;i<gcode.length;i++){
// for(var len = gcode.length- 1, i=0;i!=len;i++){
x=undefined; x=undefined;
y=undefined; y=undefined;
z=undefined; z=undefined;
retract = 0; retract = 0;
var line = gcode[i].line;
var percentage = gcode[i].percentage;
extrude=false; extrude=false;
gcode[i] = gcode[i].split(/[\(;]/)[0]; line = line.split(/[\(;]/)[0];
// prevRetract=0; if(reg.test(line)){
// retract=0; var args = line.split(/\s/);
// if(gcode[i].match(/^(?:G0|G1)\s+/i)){
if(reg.test(gcode[i])){
var args = gcode[i].split(/\s/);
for(j=0;j<args.length;j++){ for(j=0;j<args.length;j++){
// console.log(args);
// if(!args[j])continue;
switch(argChar = args[j].charAt(0).toLowerCase()){ switch(argChar = args[j].charAt(0).toLowerCase()){
case 'x': case 'x':
x=args[j].slice(1); x=args[j].slice(1);
// if(x === prevX){
// x=undefined;
// }
break; break;
case 'y': case 'y':
y=args[j].slice(1); y=args[j].slice(1);
// if(y===prevY){
// y=undefined;
// }
break; break;
case 'z': case 'z':
z=args[j].slice(1); z=args[j].slice(1);
z = Number(z); z = Number(z);
if(z == prevZ)continue; if(z == prevZ) continue;
// z = Number(z);
if(z_heights.hasOwnProperty(z)){ if(z_heights.hasOwnProperty(z)){
layer = z_heights[z]; layer = z_heights[z];
}else{ }else{
@ -267,9 +256,6 @@
} }
sendLayer = layer; sendLayer = layer;
sendLayerZ = z; sendLayerZ = z;
// if(parseFloat(prevZ) < )
// if(args[j].charAt(1) === "-")layer--;
// else layer++;
prevZ = z; prevZ = z;
break; break;
case 'e': case 'e':
@ -292,13 +278,11 @@
retract = -1; retract = -1;
} }
else if(prev_extrude["abs"]==0){ else if(prev_extrude["abs"]==0){
// if(prevRetract <0 )prevRetract=retract;
retract = 0; retract = 0;
}else if(prev_extrude["abs"]>0&&prevRetract < 0){ }else if(prev_extrude["abs"]>0&&prevRetract < 0){
prevRetract = 0; prevRetract = 0;
retract = 1; retract = 1;
} else { } else {
// prevRetract = retract;
retract = 0; retract = 0;
} }
prev_extrude[argChar] = numSlice; prev_extrude[argChar] = numSlice;
@ -317,24 +301,24 @@
prev_extrude["abs"] = Math.sqrt((prevX-x)*(prevX-x)+(prevY-y)*(prevY-y)); prev_extrude["abs"] = Math.sqrt((prevX-x)*(prevX-x)+(prevY-y)*(prevY-y));
} }
if(!model[layer])model[layer]=[]; if(!model[layer])model[layer]=[];
if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined'||retract!=0) model[layer][model[layer].length] = {x: Number(x), y: Number(y), z: Number(z), extrude: extrude, retract: Number(retract), noMove: false, extrusion: (extrude||retract)?Number(prev_extrude["abs"]):0, prevX: Number(prevX), prevY: Number(prevY), prevZ: Number(prevZ), speed: Number(lastF), gcodeLine: Number(i)}; if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined'||retract!=0) model[layer][model[layer].length] = {x: Number(x), y: Number(y), z: Number(z), extrude: extrude, retract: Number(retract), noMove: false, extrusion: (extrude||retract)?Number(prev_extrude["abs"]):0, prevX: Number(prevX), prevY: Number(prevY), prevZ: Number(prevZ), speed: Number(lastF), gcodeLine: Number(i), percentage: percentage};
//{x: x, y: y, z: z, extrude: extrude, retract: retract, noMove: false, extrusion: (extrude||retract)?prev_extrude["abs"]:0, prevX: prevX, prevY: prevY, prevZ: prevZ, speed: lastF, gcodeLine: i}; //{x: x, y: y, z: z, extrude: extrude, retract: retract, noMove: false, extrusion: (extrude||retract)?prev_extrude["abs"]:0, prevX: prevX, prevY: prevY, prevZ: prevZ, speed: lastF, gcodeLine: i};
if(typeof(x) !== 'undefined') prevX = x; if(typeof(x) !== 'undefined') prevX = x;
if(typeof(y) !== 'undefined') prevY = y; if(typeof(y) !== 'undefined') prevY = y;
} else if(gcode[i].match(/^(?:M82)/i)){ } else if(line.match(/^(?:M82)/i)){
extrudeRelative = false; extrudeRelative = false;
}else if(gcode[i].match(/^(?:G91)/i)){ }else if(line.match(/^(?:G91)/i)){
extrudeRelative=true; extrudeRelative=true;
}else if(gcode[i].match(/^(?:G90)/i)){ }else if(line.match(/^(?:G90)/i)){
extrudeRelative=false; extrudeRelative=false;
}else if(gcode[i].match(/^(?:M83)/i)){ }else if(line.match(/^(?:M83)/i)){
extrudeRelative=true; extrudeRelative=true;
}else if(gcode[i].match(/^(?:M101)/i)){ }else if(line.match(/^(?:M101)/i)){
dcExtrude=true; dcExtrude=true;
}else if(gcode[i].match(/^(?:M103)/i)){ }else if(line.match(/^(?:M103)/i)){
dcExtrude=false; dcExtrude=false;
}else if(gcode[i].match(/^(?:G92)/i)){ }else if(line.match(/^(?:G92)/i)){
var args = gcode[i].split(/\s/); var args = line.split(/\s/);
for(j=0;j<args.length;j++){ for(j=0;j<args.length;j++){
switch(argChar = args[j].charAt(0).toLowerCase()){ switch(argChar = args[j].charAt(0).toLowerCase()){
case 'x': case 'x':
@ -354,16 +338,15 @@
else { else {
prev_extrude[argChar] = numSlice; prev_extrude[argChar] = numSlice;
} }
// prevZ = z;
break; break;
default: default:
break; break;
} }
} }
if(!model[layer])model[layer]=[]; if(!model[layer])model[layer]=[];
if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined') model[layer][model[layer].length] = {x: parseFloat(x), y: parseFloat(y), z: parseFloat(z), extrude: extrude, retract: parseFloat(retract), noMove: true, extrusion: (extrude||retract)?parseFloat(prev_extrude["abs"]):0, prevX: parseFloat(prevX), prevY: parseFloat(prevY), prevZ: parseFloat(prevZ), speed: parseFloat(lastF),gcodeLine: parseFloat(i)}; if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined') model[layer][model[layer].length] = {x: parseFloat(x), y: parseFloat(y), z: parseFloat(z), extrude: extrude, retract: parseFloat(retract), noMove: true, extrusion: (extrude||retract)?parseFloat(prev_extrude["abs"]):0, prevX: parseFloat(prevX), prevY: parseFloat(prevY), prevZ: parseFloat(prevZ), speed: parseFloat(lastF),gcodeLine: parseFloat(i), percentage: percentage};
}else if(gcode[i].match(/^(?:G28)/i)){ }else if(line.match(/^(?:G28)/i)){
var args = gcode[i].split(/\s/); var args = line.split(/\s/);
for(j=0;j<args.length;j++){ for(j=0;j<args.length;j++){
switch(argChar = args[j].charAt(0).toLowerCase()){ switch(argChar = args[j].charAt(0).toLowerCase()){
case 'x': case 'x':
@ -405,17 +388,11 @@
} }
prevZ = z; prevZ = z;
} }
// x=0, y=0,z=0,prevZ=0, extrude=false;
// if(typeof(prevX) === 'undefined'){prevX=0;}
// if(typeof(prevY) === 'undefined'){prevY=0;}
if(!model[layer])model[layer]=[]; if(!model[layer])model[layer]=[];
if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined'||retract!=0) model[layer][model[layer].length] = {x: Number(x), y: Number(y), z: Number(z), extrude: extrude, retract: Number(retract), noMove: false, extrusion: (extrude||retract)?Number(prev_extrude["abs"]):0, prevX: Number(prevX), prevY: Number(prevY), prevZ: Number(prevZ), speed: Number(lastF), gcodeLine: Number(i)}; if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined'||retract!=0) model[layer][model[layer].length] = {x: Number(x), y: Number(y), z: Number(z), extrude: extrude, retract: Number(retract), noMove: false, extrusion: (extrude||retract)?Number(prev_extrude["abs"]):0, prevX: Number(prevX), prevY: Number(prevY), prevZ: Number(prevZ), speed: Number(lastF), gcodeLine: Number(i), percentage: percentage};
// if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined') model[layer][model[layer].length] = {x: x, y: y, z: z, extrude: extrude, retract: retract, noMove:false, extrusion: (extrude||retract)?prev_extrude["abs"]:0, prevX: prevX, prevY: prevY, prevZ: prevZ, speed: lastF, gcodeLine: parseFloat(i)};
} }
if(typeof(sendLayer) !== "undefined"){ if(typeof(sendLayer) !== "undefined"){
// sendLayerToParent(sendLayer, sendLayerZ, i/gcode.length*100);
// sendLayer = undefined;
if(i-lastSend > gcode.length*0.02 && sendMultiLayer.length != 0){ if(i-lastSend > gcode.length*0.02 && sendMultiLayer.length != 0){
lastSend = i; lastSend = i;
@ -429,13 +406,7 @@
sendLayerZ = undefined; sendLayerZ = undefined;
} }
} }
// sendMultiLayer[sendMultiLayer.length] = layer;
// sendMultiLayerZ[sendMultiLayerZ.length] = z;
sendMultiLayerToParent(sendMultiLayer, sendMultiLayerZ, i/gcode.length*100); sendMultiLayerToParent(sendMultiLayer, sendMultiLayerZ, i/gcode.length*100);
// if(gCodeOptions["sortLayers"])sortLayers();
// if(gCodeOptions["purgeEmptyLayers"])purgeLayers();
}; };

View File

@ -23,57 +23,73 @@ GCODE.gCodeReader = (function(){
purgeEmptyLayers: true, purgeEmptyLayers: true,
analyzeModel: false analyzeModel: false
}; };
var linesCmdIndex = {};
var prepareGCode = function(){ var percentageTree = undefined;
var prepareGCode = function(totalSize){
if(!lines)return; if(!lines)return;
gcode = []; gcode = [];
var i, tmp; var i, tmp, byteCount;
byteCount = 0;
for(i=0;i<lines.length;i++){ for(i=0;i<lines.length;i++){
// if(lines[i].match(/^(G0|G1|G90|G91|G92|M82|M83|G28)/i))gcode.push(lines[i]); byteCount += lines[i].length + 1; // line length + \n
tmp = lines[i].indexOf(";"); tmp = lines[i].indexOf(";");
if(tmp > 1 || tmp === -1) { if(tmp > 1 || tmp === -1) {
gcode.push(lines[i]); gcode.push({line: lines[i], percentage: byteCount * 100 / totalSize});
} }
} }
lines = []; lines = [];
// console.log("GCode prepared");
}; };
var sortLayers = function(){ var sortLayers = function(){
var sortedZ = []; var sortedZ = [];
var tmpModel = []; var tmpModel = [];
// var cnt = 0;
// console.log(z_heights);
for(var layer in z_heights){ for(var layer in z_heights){
sortedZ[z_heights[layer]] = layer; sortedZ[z_heights[layer]] = layer;
// cnt++;
} }
// console.log("cnt is " + cnt);
sortedZ.sort(function(a,b){ sortedZ.sort(function(a,b){
return a-b; return a-b;
}); });
// console.log(sortedZ);
// console.log(model.length);
for(var i=0;i<sortedZ.length;i++){ for(var i=0;i<sortedZ.length;i++){
// console.log("i is " + i +" and sortedZ[i] is " + sortedZ[i] + "and z_heights[] is " + z_heights[sortedZ[i]] );
if(typeof(z_heights[sortedZ[i]]) === 'undefined')continue; if(typeof(z_heights[sortedZ[i]]) === 'undefined')continue;
tmpModel[i] = model[z_heights[sortedZ[i]]]; tmpModel[i] = model[z_heights[sortedZ[i]]];
} }
model = tmpModel; model = tmpModel;
// console.log(model.length);
delete tmpModel; delete tmpModel;
}; };
var prepareLinesIndex = function(){ var prepareLinesIndex = function(){
linesCmdIndex = {}; percentageTree = undefined;
for (var l in model){ for (var l in model) {
for (var i=0; i< model[l].length; i++){ for (var i=0; i< model[l].length; i++) {
linesCmdIndex[model[l][i].gcodeLine] = {layer: l, cmd: i}; var percentage = model[l][i].percentage;
var value = {layer: l, cmd: i};
if (!percentageTree) {
percentageTree = new AVLTree({key: percentage, value: value}, "key");
} else {
percentageTree.add({key: percentage, value: value});
}
} }
} }
} };
var searchInPercentageTree = function(key) {
if (percentageTree === undefined) {
return undefined;
}
var elements = percentageTree.findBest(key);
if (elements.length == 0) {
return undefined;
}
return elements[0];
};
var purgeLayers = function(){ var purgeLayers = function(){
var purge=true; var purge=true;
@ -102,13 +118,13 @@ GCODE.gCodeReader = (function(){
return { return {
loadFile: function(reader){ loadFile: function(reader){
// console.log("loadFile");
model = []; model = [];
z_heights = []; z_heights = [];
var totalSize = reader.target.result.length;
lines = reader.target.result.split(/\n/); lines = reader.target.result.split(/\n/);
reader.target.result = null; reader.target.result = null;
prepareGCode(); prepareGCode(totalSize);
worker.postMessage({ worker.postMessage({
"cmd":"parseGCode", "cmd":"parseGCode",
@ -129,32 +145,21 @@ GCODE.gCodeReader = (function(){
} }
}, },
passDataToRenderer: function(){ passDataToRenderer: function(){
// console.log(model);
if(gCodeOptions["sortLayers"])sortLayers(); if(gCodeOptions["sortLayers"])sortLayers();
// console.log(model);
if(gCodeOptions["purgeEmptyLayers"])purgeLayers(); if(gCodeOptions["purgeEmptyLayers"])purgeLayers();
prepareLinesIndex(); prepareLinesIndex();
// console.log(model);
GCODE.renderer.doRender(model, 0); GCODE.renderer.doRender(model, 0);
// GCODE.renderer3d.setModel(model);
}, },
processLayerFromWorker: function(msg){ processLayerFromWorker: function(msg){
// var cmds = msg.cmds;
// var layerNum = msg.layerNum;
// var zHeightObject = msg.zHeightObject;
// var isEmpty = msg.isEmpty;
// console.log(zHeightObject);
model[msg.layerNum] = msg.cmds; model[msg.layerNum] = msg.cmds;
z_heights[msg.zHeightObject.zValue] = msg.zHeightObject.layer; z_heights[msg.zHeightObject.zValue] = msg.zHeightObject.layer;
// GCODE.renderer.doRender(model, msg.layerNum);
}, },
processMultiLayerFromWorker: function(msg){ processMultiLayerFromWorker: function(msg){
for(var i=0;i<msg.layerNum.length;i++){ for(var i=0;i<msg.layerNum.length;i++){
model[msg.layerNum[i]] = msg.model[msg.layerNum[i]]; model[msg.layerNum[i]] = msg.model[msg.layerNum[i]];
z_heights[msg.zHeightObject.zValue[i]] = msg.layerNum[i]; z_heights[msg.zHeightObject.zValue[i]] = msg.layerNum[i];
} }
// console.log(model);
}, },
processAnalyzeModelDone: function(msg){ processAnalyzeModelDone: function(msg){
min = msg.min; min = msg.min;
@ -190,8 +195,13 @@ GCODE.gCodeReader = (function(){
var result = {first: model[layer][fromSegments].gcodeLine, last: model[layer][toSegments].gcodeLine}; var result = {first: model[layer][fromSegments].gcodeLine, last: model[layer][toSegments].gcodeLine};
return result; return result;
}, },
getLinesCmdIndex: function(line){ getCmdIndexForPercentage: function(percentage) {
return linesCmdIndex[line]; var command = searchInPercentageTree(percentage);
if (command === undefined) {
return undefined
} else {
return command.value;
}
} }
} }
}()); }());

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

View File

@ -0,0 +1,169 @@
// AVLTree ///////////////////////////////////////////////////////////////////
// self file is originally from the Concentré XML project (version 0.2.1)
// Licensed under GPL and LGPL
//
// Modified by Jeremy Stephens.
//
// Taken from: https://gist.github.com/viking/2424106, modified to not only use string literals when searching
// Pass in the attribute you want to use for comparing
function AVLTree(n, attr) {
var self = this;
self.attr = attr;
self.left = null;
self.right = null;
self.node = n;
self.depth = 1;
self.elements = [n];
self.balance = function() {
var ldepth = self.left == null ? 0 : self.left.depth;
var rdepth = self.right == null ? 0 : self.right.depth;
if (ldepth > rdepth + 1) {
// LR or LL rotation
var lldepth = self.left.left == null ? 0 : self.left.left.depth;
var lrdepth = self.left.right == null ? 0 : self.left.right.depth;
if (lldepth < lrdepth) {
// LR rotation consists of a RR rotation of the left child
self.left.rotateRR();
// plus a LL rotation of self node, which happens anyway
}
self.rotateLL();
} else if (ldepth + 1 < rdepth) {
// RR or RL rorarion
var rrdepth = self.right.right == null ? 0 : self.right.right.depth;
var rldepth = self.right.left == null ? 0 : self.right.left.depth;
if (rldepth > rrdepth) {
// RR rotation consists of a LL rotation of the right child
self.right.rotateLL();
// plus a RR rotation of self node, which happens anyway
}
self.rotateRR();
}
}
self.rotateLL = function() {
// the left side is too long => rotate from the left (_not_ leftwards)
var nodeBefore = self.node;
var elementsBefore = self.elements;
var rightBefore = self.right;
self.node = self.left.node;
self.elements = self.left.elements;
self.right = self.left;
self.left = self.left.left;
self.right.left = self.right.right;
self.right.right = rightBefore;
self.right.node = nodeBefore;
self.right.elements = elementsBefore;
self.right.updateInNewLocation();
self.updateInNewLocation();
}
self.rotateRR = function() {
// the right side is too long => rotate from the right (_not_ rightwards)
var nodeBefore = self.node;
var elementsBefore = self.elements;
var leftBefore = self.left;
self.node = self.right.node;
self.elements = self.right.elements;
self.left = self.right;
self.right = self.right.right;
self.left.right = self.left.left;
self.left.left = leftBefore;
self.left.node = nodeBefore;
self.left.elements = elementsBefore;
self.left.updateInNewLocation();
self.updateInNewLocation();
}
self.updateInNewLocation = function() {
self.getDepthFromChildren();
}
self.getDepthFromChildren = function() {
self.depth = self.node == null ? 0 : 1;
if (self.left != null) {
self.depth = self.left.depth + 1;
}
if (self.right != null && self.depth <= self.right.depth) {
self.depth = self.right.depth + 1;
}
}
self.compare = function(n1, n2) {
var v1 = n1[self.attr];
var v2 = n2[self.attr];
if (v1 == v2) {
return 0;
}
if (v1 < v2) {
return -1;
}
return 1;
}
self.add = function(n) {
var o = self.compare(n, self.node);
if (o == 0) {
self.elements.push(n);
return false;
}
var ret = false;
if (o == -1) {
if (self.left == null) {
self.left = new AVLTree(n, self.attr);
ret = true;
} else {
ret = self.left.add(n);
if (ret) {
self.balance();
}
}
} else if (o == 1) {
if (self.right == null) {
self.right = new AVLTree(n, self.attr);
ret = true;
} else {
ret = self.right.add(n);
if (ret) {
self.balance();
}
}
}
if (ret) {
self.getDepthFromChildren();
}
return ret;
}
self.findBest = function(value) {
if (value < self.node[self.attr]) {
if (self.left != null) {
return self.left.findBest(value);
}
} else if (value > self.node[self.attr]) {
if (self.right != null) {
return self.right.findBest(value);
}
}
return self.elements;
}
self.find = function(value) {
var elements = self.findBest(value);
for (var i = 0; i < elements.length; i++) {
if (elements[i][self.attr] == value) {
return elements;
}
}
return false;
}
}

View File

@ -216,21 +216,29 @@ function PrinterStateViewModel(loginStateViewModel) {
self.isSdReady = ko.observable(undefined); self.isSdReady = ko.observable(undefined);
self.filename = ko.observable(undefined); self.filename = ko.observable(undefined);
self.filament = ko.observable(undefined); self.progress = ko.observable(undefined);
self.estimatedPrintTime = ko.observable(undefined); self.filesize = ko.observable(undefined);
self.filepos = ko.observable(undefined);
self.printTime = ko.observable(undefined); self.printTime = ko.observable(undefined);
self.printTimeLeft = ko.observable(undefined); self.printTimeLeft = ko.observable(undefined);
self.progress = ko.observable(undefined); self.sd = ko.observable(undefined);
self.currentLine = ko.observable(undefined);
self.totalLines = ko.observable(undefined); self.filament = ko.observable(undefined);
self.estimatedPrintTime = ko.observable(undefined);
self.currentHeight = ko.observable(undefined); self.currentHeight = ko.observable(undefined);
self.lineString = ko.computed(function() { self.byteString = ko.computed(function() {
if (!self.totalLines()) if (!self.filesize())
return "-"; return "-";
var currentLine = self.currentLine() ? self.currentLine() : "-"; var filepos = self.filepos() ? self.filepos() : "-";
return currentLine + " / " + self.totalLines(); return filepos + " / " + self.filesize();
}); });
self.heightString = ko.computed(function() {
if (!self.currentHeight())
return "-";
return self.currentHeight();
})
self.progressString = ko.computed(function() { self.progressString = ko.computed(function() {
if (!self.progress()) if (!self.progress())
return 0; return 0;
@ -254,8 +262,6 @@ function PrinterStateViewModel(loginStateViewModel) {
self._fromData = function(data) { self._fromData = function(data) {
self._processStateData(data.state) self._processStateData(data.state)
self._processJobData(data.job); self._processJobData(data.job);
self._processGcodeData(data.gcode);
self._processSdUploadData(data.sdUpload);
self._processProgressData(data.progress); self._processProgressData(data.progress);
self._processZData(data.currentZ); self._processZData(data.currentZ);
} }
@ -268,33 +274,15 @@ function PrinterStateViewModel(loginStateViewModel) {
self.isPrinting(data.flags.printing); self.isPrinting(data.flags.printing);
self.isError(data.flags.error); self.isError(data.flags.error);
self.isReady(data.flags.ready); self.isReady(data.flags.ready);
self.isLoading(data.flags.loading);
self.isSdReady(data.flags.sdReady); self.isSdReady(data.flags.sdReady);
} }
self._processJobData = function(data) { self._processJobData = function(data) {
self.filename(data.filename); self.filename(data.filename);
self.totalLines(data.lines); self.filesize(data.filesize);
self.estimatedPrintTime(data.estimatedPrintTime); self.estimatedPrintTime(data.estimatedPrintTime);
self.filament(data.filament); self.filament(data.filament);
} self.sd(data.sd);
self._processGcodeData = function(data) {
if (self.isLoading()) {
var progress = Math.round(data.progress * 100);
if (data.mode == "loading") {
self.filename("Loading... (" + progress + "%)");
} else if (data.mode == "parsing") {
self.filename("Parsing... (" + progress + "%)");
}
}
}
self._processSdUploadData = function(data) {
if (self.isLoading()) {
var progress = Math.round(data.progress * 100);
self.filename("Streaming... (" + progress + "%)");
}
} }
self._processProgressData = function(data) { self._processProgressData = function(data) {
@ -303,7 +291,7 @@ function PrinterStateViewModel(loginStateViewModel) {
} else { } else {
self.progress(undefined); self.progress(undefined);
} }
self.currentLine(data.currentLine); self.filepos(data.filepos);
self.printTime(data.printTime); self.printTime(data.printTime);
self.printTimeLeft(data.printTimeLeft); self.printTimeLeft(data.printTimeLeft);
} }
@ -1093,13 +1081,16 @@ function GcodeViewModel(loginStateViewModel) {
} }
self._processData = function(data) { self._processData = function(data) {
if(!self.enabled)return; if (!self.enabled) return;
if (!data.job.filename) return;
if(self.loadedFilename == data.job.filename) { if(self.loadedFilename && self.loadedFilename == data.job.filename) {
var cmdIndex = GCODE.gCodeReader.getLinesCmdIndex(data.progress.progress); if (data.state.flags && (data.state.flags.printing || data.state.flags.paused)) {
if(cmdIndex){ var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.progress * 100);
GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd); if(cmdIndex){
GCODE.ui.updateLayerInfo(cmdIndex.layer); GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
GCODE.ui.updateLayerInfo(cmdIndex.layer);
}
} }
self.errorCount = 0 self.errorCount = 0
} else if (data.job.filename) { } else if (data.job.filename) {
@ -1745,43 +1736,48 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
//~~ local storage //~~ local storage
self._saveCurrentSortingToLocalStorage = function() { self._saveCurrentSortingToLocalStorage = function() {
self._initializeLocalStorage(); if ( self._initializeLocalStorage() ) {
var currentSorting = self.currentSorting();
var currentSorting = self.currentSorting(); if (currentSorting !== undefined)
if (currentSorting !== undefined) localStorage[self.listType + "." + "currentSorting"] = currentSorting;
localStorage[self.listType + "." + "currentSorting"] = currentSorting; else
else localStorage[self.listType + "." + "currentSorting"] = undefined;
localStorage[self.listType + "." + "currentSorting"] = undefined; }
} }
self._loadCurrentSortingFromLocalStorage = function() { self._loadCurrentSortingFromLocalStorage = function() {
self._initializeLocalStorage(); if ( self._initializeLocalStorage() ) {
if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"]))
if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"])) self.currentSorting(localStorage[self.listType + "." + "currentSorting"]);
self.currentSorting(localStorage[self.listType + "." + "currentSorting"]); else
else self.currentSorting(defaultSorting);
self.currentSorting(defaultSorting); }
} }
self._saveCurrentFiltersToLocalStorage = function() { self._saveCurrentFiltersToLocalStorage = function() {
self._initializeLocalStorage(); if ( self._initializeLocalStorage() ) {
var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters());
var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters()); localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters);
localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters); }
} }
self._loadCurrentFiltersFromLocalStorage = function() { self._loadCurrentFiltersFromLocalStorage = function() {
self._initializeLocalStorage(); if ( self._initializeLocalStorage() ) {
self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"])));
self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"]))); }
} }
self._initializeLocalStorage = function() { self._initializeLocalStorage = function() {
if (!Modernizr.localstorage)
return false;
if (localStorage[self.listType + "." + "currentSorting"] !== undefined && localStorage[self.listType + "." + "currentFilters"] !== undefined && JSON.parse(localStorage[self.listType + "." + "currentFilters"]) instanceof Array) if (localStorage[self.listType + "." + "currentSorting"] !== undefined && localStorage[self.listType + "." + "currentFilters"] !== undefined && JSON.parse(localStorage[self.listType + "." + "currentFilters"]) instanceof Array)
return; return true;
localStorage[self.listType + "." + "currentSorting"] = self.defaultSorting; localStorage[self.listType + "." + "currentSorting"] = self.defaultSorting;
localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(self.defaultFilters); localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(self.defaultFilters);
return true;
} }
self._loadCurrentFiltersFromLocalStorage(); self._loadCurrentFiltersFromLocalStorage();

View File

@ -108,13 +108,13 @@
<div class="accordion-body collapse in" id="state"> <div class="accordion-body collapse in" id="state">
<div class="accordion-inner"> <div class="accordion-inner">
Machine State: <strong data-bind="text: stateString"></strong><br> Machine State: <strong data-bind="text: stateString"></strong><br>
File: <strong data-bind="text: filename"></strong><br> File: <strong data-bind="text: filename"></strong>&nbsp;<strong data-bind="visible: sd">(SD)</strong><br>
Filament: <strong data-bind="text: filament"></strong><br> Filament: <strong data-bind="text: filament"></strong><br>
Estimated Print Time: <strong data-bind="text: estimatedPrintTime"></strong><br> Estimated Print Time: <strong data-bind="text: estimatedPrintTime"></strong><br>
Line: <strong data-bind="text: lineString"></strong><br> Height: <strong data-bind="text: heightString"></strong><br>
Height: <strong data-bind="text: currentHeight"></strong><br>
Print Time: <strong data-bind="text: printTime"></strong><br> Print Time: <strong data-bind="text: printTime"></strong><br>
Print Time Left: <strong data-bind="text: printTimeLeft"></strong><br> Print Time Left: <strong data-bind="text: printTimeLeft"></strong><br>
Printed: <strong data-bind="text: byteString"></strong><br>
<div class="progress"> <div class="progress">
<div class="bar" id="job_progressBar" data-bind="style: { width: progressString() + '%' }"></div> <div class="bar" id="job_progressBar" data-bind="style: { width: progressString() + '%' }"></div>
@ -153,7 +153,7 @@
{% if enableSdSupport %} {% if enableSdSupport %}
<div class="sd-trigger accordion-heading-button btn-group"> <div class="sd-trigger accordion-heading-button btn-group">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"> <a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span class="icon-hdd"></span> <span class="icon-sd-black-14"></span>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li data-bind="visible: !isSdReady()"><a href="#" data-bind="click: function() { $root.initSdCard(); }"><i class="icon-flag"></i> Initialize SD card</a></li> <li data-bind="visible: !isSdReady()"><a href="#" data-bind="click: function() { $root.initSdCard(); }"><i class="icon-flag"></i> Initialize SD card</a></li>
@ -197,22 +197,22 @@
<div style="display: none;" data-bind="visible: loginState.isUser"> <div style="display: none;" data-bind="visible: loginState.isUser">
<div class="row-fluid upload-buttons"> <div class="row-fluid upload-buttons">
{% if enableSdSupport %} {% if enableSdSupport %}
<button class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px"> <span class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
<i class="icon-upload-alt icon-white"></i> <i class="icon-upload-alt icon-white"></i>
<span>Upload</span> <span>Upload</span>
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()"> <input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
</button> </span>
<button class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px"> <span class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
<i class="icon-upload-alt icon-white"></i> <i class="icon-upload-alt icon-white"></i>
<span>Upload to SD</span> <span>Upload to SD</span>
<input id="gcode_upload_sd" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()"> <input id="gcode_upload_sd" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
</button> </span>
{% else %} {% else %}
<button class="btn btn-primary fileinput-button span12" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px"> <span class="btn btn-primary fileinput-button span12" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
<i class="icon-upload-alt icon-white"></i> <i class="icon-upload-alt icon-white"></i>
<span>Upload</span> <span>Upload</span>
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()"> <input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
</button> </span>
{% endif %} {% endif %}
</div> </div>
<div id="gcode_upload_progress" class="progress" style="width: 100%;"> <div id="gcode_upload_progress" class="progress" style="width: 100%;">
@ -579,6 +579,7 @@
<script type="text/javascript" src="{{ url_for('static', filename='js/modernizr.custom.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/modernizr.custom.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/underscore.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/underscore.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/knockout.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/knockout.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/avltree.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/bootstrap/bootstrap.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/bootstrap/bootstrap.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/bootstrap/bootstrap-modalmanager.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/bootstrap/bootstrap-modalmanager.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/bootstrap/bootstrap-modal.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/bootstrap/bootstrap-modal.js') }}"></script>

View File

@ -16,6 +16,8 @@ import serial
from octoprint.util.avr_isp import stk500v2 from octoprint.util.avr_isp import stk500v2
from octoprint.util.avr_isp import ispBase from octoprint.util.avr_isp import ispBase
from octoprint.util import matchesGcode
from octoprint.settings import settings from octoprint.settings import settings
from octoprint.events import eventManager from octoprint.events import eventManager
@ -102,6 +104,7 @@ class VirtualPrinter():
if self._writingToSd and not self._selectedSdFile is None and not "M29" in data: if self._writingToSd and not self._selectedSdFile is None and not "M29" in data:
with open(self._selectedSdFile, "a") as f: with open(self._selectedSdFile, "a") as f:
f.write(data) f.write(data)
self.readList.append("ok")
return return
#print "Send: %s" % (data.rstrip()) #print "Send: %s" % (data.rstrip())
@ -217,6 +220,7 @@ class VirtualPrinter():
self._writingToSd = True self._writingToSd = True
self._selectedSdFile = file self._selectedSdFile = file
self.readList.append("Writing to file: %s" % filename)
self.readList.append("ok") self.readList.append("ok")
def _finishSdFile(self): def _finishSdFile(self):
@ -314,16 +318,19 @@ class MachineComPrintCallback(object):
def mcZChange(self, newZ): def mcZChange(self, newZ):
pass pass
def mcFileSelected(self, filename, filesize, sd):
pass
def mcSdStateChange(self, sdReady): def mcSdStateChange(self, sdReady):
pass pass
def mcSdFiles(self, files): def mcSdFiles(self, files):
pass pass
def mcSdSelected(self, filename, size): def mcSdPrintingDone(self):
pass pass
def mcSdPrintingDone(self): def mcFileTransferStarted(self, filename, filesize):
pass pass
class MachineCom(object): class MachineCom(object):
@ -366,15 +373,11 @@ class MachineCom(object):
self._bedTemp = 0 self._bedTemp = 0
self._targetTemp = 0 self._targetTemp = 0
self._bedTargetTemp = 0 self._bedTargetTemp = 0
self._gcodeList = None
self._gcodePos = 0
self._commandQueue = queue.Queue() self._commandQueue = queue.Queue()
self._logQueue = queue.Queue(256) self._logQueue = queue.Queue(256)
self._feedRateModifier = {} self._currentZ = None
self._currentZ = -1
self._heatupWaitStartTime = 0 self._heatupWaitStartTime = 0
self._heatupWaitTimeLost = 0.0 self._heatupWaitTimeLost = 0.0
self._printStartTime = None
self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"]) self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"])
self._currentLine = 1 self._currentLine = 1
@ -388,25 +391,21 @@ class MachineCom(object):
self.thread.daemon = True self.thread.daemon = True
self.thread.start() self.thread.start()
# SD status data
self._sdAvailable = False self._sdAvailable = False
self._sdPrinting = False
self._sdFileList = False self._sdFileList = False
self._sdFile = None
self._sdFilePos = None
self._sdFileSize = None
self._sdFiles = [] self._sdFiles = []
# print job
self._currentFile = None
def _changeState(self, newState): def _changeState(self, newState):
if self._state == newState: if self._state == newState:
return return
if newState == self.STATE_CLOSED or newState == self.STATE_CLOSED_WITH_ERROR: if newState == self.STATE_CLOSED or newState == self.STATE_CLOSED_WITH_ERROR:
if settings().get(["feature", "sdSupport"]): if settings().get(["feature", "sdSupport"]):
self._sdPrinting = False
self._sdFileList = False self._sdFileList = False
self._sdFile = None
self._sdFilePos = None
self._sdFileSize = None
self._sdFiles = [] self._sdFiles = []
self._callback.mcSdFiles([]) self._callback.mcSdFiles([])
@ -432,8 +431,10 @@ class MachineCom(object):
if self._state == self.STATE_OPERATIONAL: if self._state == self.STATE_OPERATIONAL:
return "Operational" return "Operational"
if self._state == self.STATE_PRINTING: if self._state == self.STATE_PRINTING:
if self._sdPrinting: if self.isSdFileSelected():
return "Printing from SD" return "Printing from SD"
elif self.isStreaming():
return "Sending file to SD"
else: else:
return "Printing" return "Printing"
if self._state == self.STATE_PAUSED: if self._state == self.STATE_PAUSED:
@ -469,53 +470,51 @@ class MachineCom(object):
return self._state == self.STATE_PRINTING return self._state == self.STATE_PRINTING
def isSdPrinting(self): def isSdPrinting(self):
return self._sdPrinting return self.isSdFileSelected() and self.isPrinting()
def isSdFileSelected(self):
return self._currentFile is not None and isinstance(self._currentFile, PrintingSdFileInformation)
def isStreaming(self):
return self._currentFile is not None and isinstance(self._currentFile, StreamingGcodeFileInformation)
def isPaused(self): def isPaused(self):
return self._state == self.STATE_PAUSED return self._state == self.STATE_PAUSED
def isBusy(self): def isBusy(self):
return self.isPrinting() or self._state == self.STATE_RECEIVING_FILE return self.isPrinting() or self.isPaused()
def isSdReady(self): def isSdReady(self):
return self._sdAvailable return self._sdAvailable
def getPrintPos(self): def getPrintProgress(self):
if self._sdPrinting: if self._currentFile is None:
return self._sdFilePos return None
else: return self._currentFile.getProgress()
return self._gcodePos
def getPrintFilepos(self):
if self._currentFile is None:
return None
return self._currentFile.getFilepos()
def getPrintTime(self): def getPrintTime(self):
if self._printStartTime == None: if self._currentFile is None or self._currentFile.getStartTime() is None:
return 0 return None
else: else:
return time.time() - self._printStartTime return time.time() - self._currentFile.getStartTime()
def getPrintTimeRemainingEstimate(self): def getPrintTimeRemainingEstimate(self):
if self._printStartTime == None: printTime = self.getPrintTime()
if printTime is None:
return None return None
if self._sdPrinting: printTime /= 60
printTime = (time.time() - self._printStartTime) / 60 progress = self._currentFile.getProgress()
if self._sdFilePos > 0: if progress:
printTimeTotal = printTime * self._sdFileSize / self._sdFilePos printTimeTotal = printTime / progress
else: return printTimeTotal - printTime
printTimeTotal = printTime * self._sdFileSize
printTimeLeft = printTimeTotal - printTime
return printTimeLeft
else: else:
# for host printing we only start counting the print time at gcode line 100, so we need to calculate stuff return None
# a bit different here
if self.getPrintPos() < 200:
return None
printTime = (time.time() - self._printStartTime) / 60
printTimeTotal = printTime * (len(self._gcodeList) - 100) / (self.getPrintPos() - 100)
printTimeLeft = printTimeTotal - printTime
return printTimeLeft
def getSdProgress(self):
return (self._sdFilePos, self._sdFileSize)
def getTemp(self): def getTemp(self):
return self._temp return self._temp
@ -579,6 +578,7 @@ class MachineCom(object):
tempRequestTimeout = timeout tempRequestTimeout = timeout
sdStatusRequestTimeout = timeout sdStatusRequestTimeout = timeout
startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"]) startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"])
heatingUp = False
while True: while True:
line = self._readline() line = self._readline()
if line == None: if line == None:
@ -614,7 +614,7 @@ class MachineCom(object):
##~~ SD file list ##~~ SD file list
# if we are currently receiving an sd file list, each line is just a filename, so just read it and abort processing # if we are currently receiving an sd file list, each line is just a filename, so just read it and abort processing
if self._sdFileList and not 'End file list' in line: if self._sdFileList and not 'End file list' in line:
self._sdFiles.append(line) self._sdFiles.append(line.strip().lower())
continue continue
##~~ Temperature processing ##~~ Temperature processing
@ -630,16 +630,23 @@ class MachineCom(object):
pass pass
#If we are waiting for an M109 or M190 then measure the time we lost during heatup, so we can remove that time from our printing time estimate. #If we are waiting for an M109 or M190 then measure the time we lost during heatup, so we can remove that time from our printing time estimate.
if not 'ok' in line and self._heatupWaitStartTime != 0: if not 'ok' in line:
t = time.time() heatingUp = True
self._heatupWaitTimeLost = t - self._heatupWaitStartTime if self._heatupWaitStartTime != 0:
self._heatupWaitStartTime = t t = time.time()
self._heatupWaitTimeLost = t - self._heatupWaitStartTime
self._heatupWaitStartTime = t
##~~ SD Card handling ##~~ SD Card handling
elif 'SD init fail' in line: elif 'SD init fail' in line:
self._sdAvailable = False self._sdAvailable = False
self._sdFiles = [] self._sdFiles = []
self._callback.mcSdStateChange(self._sdAvailable) self._callback.mcSdStateChange(self._sdAvailable)
elif 'Not SD printing' in line:
if self.isSdFileSelected() and self.isPrinting():
# something went wrong, printer is reporting that we actually are not printing right now...
self._sdFilePos = 0
self._changeState(self.STATE_OPERATIONAL)
elif 'SD card ok' in line: elif 'SD card ok' in line:
self._sdAvailable = True self._sdAvailable = True
self.refreshSdFiles() self.refreshSdFiles()
@ -653,28 +660,32 @@ class MachineCom(object):
elif 'SD printing byte' in line: elif 'SD printing byte' in line:
# answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d" # answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d"
match = re.search("([0-9]*)/([0-9]*)", line) match = re.search("([0-9]*)/([0-9]*)", line)
self._sdFilePos = int(match.group(1)) self._currentFile.setFilepos(int(match.group(1)))
self._sdFileSize = int(match.group(2))
self._callback.mcProgress() self._callback.mcProgress()
elif 'File opened' in line: elif 'File opened' in line:
# answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d" # answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d"
match = re.search("File opened:\s*(.*?)\s+Size:\s*([0-9]*)", line) match = re.search("File opened:\s*(.*?)\s+Size:\s*([0-9]*)", line)
self._sdFile = match.group(1) self._currentFile = PrintingSdFileInformation(match.group(1), int(match.group(2)))
self._sdFileSize = int(match.group(2))
elif 'File selected' in line: elif 'File selected' in line:
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected" # final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
self._callback.mcSdSelected(self._sdFile, self._sdFileSize) self._callback.mcFileSelected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True)
elif 'Writing to file' in line:
# anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s"
self._printSection = "CUSTOM"
self._changeState(self.STATE_PRINTING)
elif 'Done printing file' in line: elif 'Done printing file' in line:
# printer is reporting file finished printing # printer is reporting file finished printing
self._sdPrinting = False
self._sdFilePos = 0 self._sdFilePos = 0
self._changeState(self.STATE_OPERATIONAL) self._changeState(self.STATE_OPERATIONAL)
self._callback.mcSdPrintingDone() self._callback.mcPrintjobDone()
##~~ Message handling ##~~ Message handling
elif line.strip() != '' and line.strip() != 'ok' and not line.startswith("wait") and not line.startswith('Resend:') and line != 'echo:Unknown command:""\n' and self.isOperational(): elif line.strip() != '' and line.strip() != 'ok' and not line.startswith("wait") and not line.startswith('Resend:') and line != 'echo:Unknown command:""\n' and self.isOperational():
self._callback.mcMessage(line) self._callback.mcMessage(line)
if "ok" in line and heatingUp:
heatingUp = False
### Baudrate detection ### Baudrate detection
if self._state == self.STATE_DETECT_BAUDRATE: if self._state == self.STATE_DETECT_BAUDRATE:
if line == '' or time.time() > timeout: if line == '' or time.time() > timeout:
@ -712,6 +723,8 @@ class MachineCom(object):
self._sendCommand("M999") self._sendCommand("M999")
self._serial.timeout = 2 self._serial.timeout = 2
self._changeState(self.STATE_OPERATIONAL) self._changeState(self.STATE_OPERATIONAL)
if self._sdAvailable:
self.refreshSdFiles()
eventManager().fire("Connected", "%s at %s baud" % (self._port, self._baudrate)) eventManager().fire("Connected", "%s at %s baud" % (self._port, self._baudrate))
else: else:
self._testingBaudrate = False self._testingBaudrate = False
@ -724,6 +737,8 @@ class MachineCom(object):
startSeen = True startSeen = True
elif "ok" in line and startSeen: elif "ok" in line and startSeen:
self._changeState(self.STATE_OPERATIONAL) self._changeState(self.STATE_OPERATIONAL)
if self._sdAvailable:
self.refreshSdFiles()
eventManager().fire("Connected", "%s at %s baud" % (self._port, self._baudrate)) eventManager().fire("Connected", "%s at %s baud" % (self._port, self._baudrate))
elif time.time() > timeout: elif time.time() > timeout:
self.close() self.close()
@ -749,12 +764,12 @@ class MachineCom(object):
self._log("Communication timeout during printing, forcing a line") self._log("Communication timeout during printing, forcing a line")
line = 'ok' line = 'ok'
if self._sdPrinting: if self.isSdPrinting():
if time.time() > tempRequestTimeout: if time.time() > tempRequestTimeout and not heatingUp:
self._sendCommand("M105") self._sendCommand("M105")
tempRequestTimeout = time.time() + 5 tempRequestTimeout = time.time() + 5
if time.time() > sdStatusRequestTimeout: if time.time() > sdStatusRequestTimeout and not heatingUp:
self._sendCommand("M27") self._sendCommand("M27")
sdStatusRequestTimeout = time.time() + 1 sdStatusRequestTimeout = time.time() + 1
@ -762,7 +777,7 @@ class MachineCom(object):
timeout = time.time() + 5 timeout = time.time() + 5
else: else:
# Even when printing request the temperature every 5 seconds. # Even when printing request the temperature every 5 seconds.
if time.time() > tempRequestTimeout: if time.time() > tempRequestTimeout and not self.isStreaming():
self._commandQueue.put("M105") self._commandQueue.put("M105")
tempRequestTimeout = time.time() + 5 tempRequestTimeout = time.time() + 5
@ -770,7 +785,7 @@ class MachineCom(object):
timeout = time.time() + 5 timeout = time.time() + 5
if self._resendDelta is not None: if self._resendDelta is not None:
self._resendNextCommand() self._resendNextCommand()
elif not self._commandQueue.empty(): elif not self._commandQueue.empty() and not self.isStreaming():
self._sendCommand(self._commandQueue.get()) self._sendCommand(self._commandQueue.get())
else: else:
self._sendNext() self._sendNext()
@ -864,23 +879,23 @@ class MachineCom(object):
return return
for gcode in gcodeToEvent.keys(): for gcode in gcodeToEvent.keys():
if gcode in cmd: if matchesGcode(cmd, gcode):
eventManager().fire(gcodeToEvent[gcode]) eventManager().fire(gcodeToEvent[gcode])
if 'M109' in cmd or 'M190' in cmd: if matchesGcode(cmd, "M109") or matchesGcode(cmd, "M190"):
self._heatupWaitStartTime = time.time() self._heatupWaitStartTime = time.time()
if 'M104' in cmd or 'M109' in cmd: if matchesGcode(cmd, "M104") or matchesGcode(cmd, "M109"):
try: try:
self._targetTemp = float(re.search('S([0-9]+)', cmd).group(1)) self._targetTemp = float(re.search('S([0-9]+)', cmd).group(1))
except: except:
pass pass
if 'M140' in cmd or 'M190' in cmd: if matchesGcode(cmd, "M140") or matchesGcode(cmd, "M190"):
try: try:
self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1)) self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1))
except: except:
pass pass
if "M110" in cmd: if matchesGcode(cmd, "M110"):
newLineNumber = None newLineNumber = None
if " N" in cmd: if " N" in cmd:
try: try:
@ -917,7 +932,7 @@ class MachineCom(object):
if self._alwaysSendChecksum: if self._alwaysSendChecksum:
lineNumber = self._currentLine lineNumber = self._currentLine
else: else:
lineNumber = self._gcodePos lineNumber = self._currentFile.getLineCount()
self._addToLastLines(cmd) self._addToLastLines(cmd)
self._currentLine += 1 self._currentLine += 1
self._doSendWithChecksum(cmd, lineNumber) self._doSendWithChecksum(cmd, lineNumber)
@ -927,8 +942,8 @@ class MachineCom(object):
def _doSendWithChecksum(self, cmd, lineNumber): def _doSendWithChecksum(self, cmd, lineNumber):
self._logger.debug("Sending cmd '%s' with lineNumber %r" % (cmd, lineNumber)) self._logger.debug("Sending cmd '%s' with lineNumber %r" % (cmd, lineNumber))
checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (lineNumber, cmd))) checksum = reduce(lambda x,y:x^y, map(ord, "N%d %s" % (lineNumber, cmd)))
commandToSend = "N%d%s*%d" % (lineNumber, cmd, checksum) commandToSend = "N%d %s*%d" % (lineNumber, cmd, checksum)
self._doSendWithoutChecksum(commandToSend) self._doSendWithoutChecksum(commandToSend)
def _doSendWithoutChecksum(self, cmd): def _doSendWithoutChecksum(self, cmd):
@ -950,131 +965,141 @@ class MachineCom(object):
def _sendNext(self): def _sendNext(self):
with self._sendNextLock: with self._sendNextLock:
if self._gcodePos >= len(self._gcodeList): line = self._currentFile.getNext()
if line is None:
if self.isStreaming():
self._sendCommand("M29")
self._currentFile = None
self._callback.mcFileTransferDone()
else:
self._callback.mcPrintjobDone()
self._changeState(self.STATE_OPERATIONAL) self._changeState(self.STATE_OPERATIONAL)
eventManager().fire('PrintDone') eventManager().fire('PrintDone')
return return
if self._gcodePos == 100:
self._printStartTime100 = time.time()
line = self._gcodeList[self._gcodePos]
if type(line) is tuple: if type(line) is tuple:
self._printSection = line[1] self._printSection = line[1]
line = line[0] line = line[0]
try:
if line == 'M0' or line == 'M1': if not self.isStreaming():
self.setPause(True) try:
line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause. if matchesGcode(line, "M0") or matchesGcode(line, "M1"):
if self._printSection in self._feedRateModifier: self.setPause(True)
line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line) line = "M105" # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
if ('G0' in line or 'G1' in line) and 'Z' in line: if (matchesGcode(line, "G0") or matchesGcode(line, "G1")) and 'Z' in line:
z = float(re.search('Z([0-9\.]*)', line).group(1)) z = float(re.search('Z([0-9\.]*)', line).group(1))
if self._currentZ != z: if self._currentZ != z:
self._currentZ = z self._currentZ = z
self._callback.mcZChange(z) self._callback.mcZChange(z)
except: except:
self._log("Unexpected error: %s" % (getExceptionString())) self._log("Unexpected error: %s" % (getExceptionString()))
self._sendCommand(line, True) self._sendCommand(line, True)
self._gcodePos += 1
self._callback.mcProgress() self._callback.mcProgress()
def sendCommand(self, cmd): def sendCommand(self, cmd):
cmd = cmd.encode('ascii', 'replace') cmd = cmd.encode('ascii', 'replace')
if self.isPrinting(): if self.isPrinting() and not self.isSdFileSelected():
self._commandQueue.put(cmd) self._commandQueue.put(cmd)
elif self.isOperational(): elif self.isOperational():
self._sendCommand(cmd) self._sendCommand(cmd)
def printGCode(self, gcodeList): def startPrint(self):
if not self.isOperational() or self.isPrinting():
return
if self._sdPrinting:
self._sdPrinting = False
self._gcodeList = gcodeList
self._gcodePos = 0
self._printSection = 'CUSTOM'
self._changeState(self.STATE_PRINTING)
self._printStartTime = time.time()
self._sendNext()
eventManager().fire("PrintStarted")
def printSdFile(self):
if not self.isOperational() or self.isPrinting(): if not self.isOperational() or self.isPrinting():
return return
if self.isPaused(): if self._currentFile is None:
self.sendCommand("M26 S0") # reset position in file to byte 0 raise ValueError("No file selected for printing")
self.sendCommand("M24")
self._printSection = 'CUSTOM' self._printSection = "CUSTOM"
self._sdPrinting = True
self._changeState(self.STATE_PRINTING) self._changeState(self.STATE_PRINTING)
self._printStartTime = time.time()
eventManager().fire("PrintStarted") eventManager().fire("PrintStarted")
try:
self._currentFile.start()
if self.isSdFileSelected():
if self.isPaused():
self.sendCommand("M26 S0")
self._currentFile.setFilepos(0)
self.sendCommand("M24")
else:
self._sendNext()
except:
self._changeState(self.STATE_ERROR)
self._errorValue = getExceptionString()
def startFileTransfer(self, filename, remoteFilename):
if not self.isOperational() or self.isBusy():
return
self._currentFile = StreamingGcodeFileInformation(filename)
self._currentFile.start()
self.sendCommand("M28 %s" % remoteFilename)
self._callback.mcFileTransferStarted(remoteFilename, self._currentFile.getFilesize())
def selectFile(self, filename, sd):
if self.isBusy():
return
if sd:
if not self.isOperational():
# printer is not connected, can't use SD
return
self.sendCommand("M23 %s" % filename)
else:
self._currentFile = PrintingGcodeFileInformation(filename)
self._callback.mcFileSelected(filename, self._currentFile.getFilesize(), False)
def cancelPrint(self): def cancelPrint(self):
if self.isOperational(): if not self.isOperational() or self.isStreaming():
self._changeState(self.STATE_OPERATIONAL) return
if self._sdPrinting:
self._sdPrinting = False self._changeState(self.STATE_OPERATIONAL)
if self.isSdFileSelected():
self.sendCommand("M25") # pause print self.sendCommand("M25") # pause print
self.sendCommand("M26 S0") # reset position in file to byte 0 self.sendCommand("M26 S0") # reset position in file to byte 0
eventManager().fire("PrintCancelled") eventManager().fire("PrintCancelled")
def setPause(self, pause): def setPause(self, pause):
if self.isStreaming():
return
if not pause and self.isPaused(): if not pause and self.isPaused():
self._changeState(self.STATE_PRINTING) self._changeState(self.STATE_PRINTING)
if self._sdPrinting: if self.isSdFileSelected():
self.sendCommand("M24") self.sendCommand("M24")
else: else:
for i in xrange(0, 6): self._sendNext()
self._sendNext()
if pause and self.isPrinting(): if pause and self.isPrinting():
self._changeState(self.STATE_PAUSED) self._changeState(self.STATE_PAUSED)
if self._sdPrinting: if self.isSdFileSelected():
self.sendCommand("M25") # pause print self.sendCommand("M25") # pause print
eventManager().fire("Paused") eventManager().fire("Paused")
def setFeedrateModifier(self, type, value): ##~~ SD card handling
self._feedRateModifier[type] = value
def getFeedrateModifiers(self):
result = {}
result.update(self._feedRateModifier)
return result
##~~ SD card
def getSdFiles(self): def getSdFiles(self):
return self._sdFiles return self._sdFiles
def startSdFileTransfer(self, filename): def startSdFileTransfer(self, filename):
if not self.isOperational() or self.isPrinting() or self.isPaused(): if not self.isOperational() or self.isBusy():
return return
self._changeState(self.STATE_RECEIVING_FILE) self._changeState(self.STATE_RECEIVING_FILE)
self.sendCommand("M28 %s" % filename.lower()) self.sendCommand("M28 %s" % filename.lower())
def endSdFileTransfer(self, filename): def endSdFileTransfer(self, filename):
if not self.isOperational() or self.isPrinting() or self.isPaused(): if not self.isOperational() or self.isBusy():
return return
self.sendCommand("M29 %s" % filename.lower()) self.sendCommand("M29 %s" % filename.lower())
self._changeState(self.STATE_OPERATIONAL) self._changeState(self.STATE_OPERATIONAL)
self.refreshSdFiles() self.refreshSdFiles()
def selectSdFile(self, filename):
if not self.isOperational() or self.isPrinting() or self.isPaused():
return
self._sdFile = None
self._sdFilePos = 0
self.sendCommand("M23 %s" % filename.lower())
def deleteSdFile(self, filename): def deleteSdFile(self, filename):
if not self.isOperational() or ((self.isPrinting() or self.isPaused()) and self._sdFile == filename.lower()): if not self.isOperational() or (self.isBusy() and self._sdFile == filename.lower()):
# do not delete a file from sd we are currently printing from # do not delete a file from sd we are currently printing from
return return
@ -1082,7 +1107,7 @@ class MachineCom(object):
self.refreshSdFiles() self.refreshSdFiles()
def refreshSdFiles(self): def refreshSdFiles(self):
if not self.isOperational() or self.isPrinting() or self.isPaused(): if not self.isOperational() or self.isBusy():
return return
self.sendCommand("M20") self.sendCommand("M20")
@ -1092,7 +1117,7 @@ class MachineCom(object):
self.sendCommand("M21") self.sendCommand("M21")
def releaseSdCard(self): def releaseSdCard(self):
if not self.isOperational() or ((self.isPrinting() or self.isPaused()) and self._sdPrinting): if not self.isOperational() or (self.isBusy() and self.isSdFileSelected()):
# do not release the sd card if we are currently printing from it # do not release the sd card if we are currently printing from it
return return
@ -1106,3 +1131,145 @@ class MachineCom(object):
def getExceptionString(): def getExceptionString():
locationInfo = traceback.extract_tb(sys.exc_info()[2])[0] locationInfo = traceback.extract_tb(sys.exc_info()[2])[0]
return "%s: '%s' @ %s:%s:%d" % (str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]), os.path.basename(locationInfo[0]), locationInfo[2], locationInfo[1]) return "%s: '%s' @ %s:%s:%d" % (str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]), os.path.basename(locationInfo[0]), locationInfo[2], locationInfo[1])
class PrintingFileInformation(object):
"""
Encapsulates information regarding the current file being printed: file name, current position, total size and
time the print started.
Allows to reset the current file position to 0 and to calculate the current progress as a floating point
value between 0 and 1.
"""
def __init__(self, filename):
self._filename = filename
self._filepos = 0
self._filesize = None
self._startTime = None
def getStartTime(self):
return self._startTime
def getFilename(self):
return self._filename
def getFilesize(self):
return self._filesize
def getFilepos(self):
return self._filepos
def getProgress(self):
"""
The current progress of the file, calculated as relation between file position and absolute size. Returns -1
if file size is None or < 1.
"""
if self._filesize is None or not self._filesize > 0:
return -1
return float(self._filepos) / float(self._filesize)
def reset(self):
"""
Resets the current file position to 0.
"""
self._filepos = 0
def start(self):
"""
Marks the print job as started and remembers the start time.
"""
self._startTime = time.time()
class PrintingSdFileInformation(PrintingFileInformation):
"""
Encapsulates information regarding an ongoing print from SD.
"""
def __init__(self, filename, filesize):
PrintingFileInformation.__init__(self, filename)
self._filesize = filesize
def setFilepos(self, filepos):
"""
Sets the current file position.
"""
self._filepos = filepos
class PrintingGcodeFileInformation(PrintingFileInformation):
"""
Encapsulates information regarding an ongoing direct print. Takes care of the needed file handle and ensures
that the file is closed in case of an error.
"""
def __init__(self, filename):
PrintingFileInformation.__init__(self, filename)
self._filehandle = None
self._lineCount = 0
self._firstLine = None
self._prevLineType = None
if not os.path.exists(self._filename) or not os.path.isfile(self._filename):
raise IOError("File %s does not exist" % self._filename)
self._filesize = os.stat(self._filename).st_size
def start(self):
"""
Opens the file for reading and determines the file size. Start time won't be recorded until 100 lines in
"""
self._filehandle = open(self._filename, "r")
self._lineCount = 0
self._prevLineType = "CUSTOM"
def getNext(self):
"""
Retrieves the next line for printing.
"""
if self._filehandle is None:
raise ValueError("File %s is not open for reading" % self._filename)
if self._lineCount == 0:
self._lineCount += 1
return "M110 N0"
try:
processedLine = None
while processedLine is None:
if self._filehandle is None:
# file got closed just now
return None
line = self._filehandle.readline()
if not line:
self._filehandle.close()
self._filehandle = None
processedLine = self._processLine(line)
self._lineCount += 1
self._filepos = self._filehandle.tell()
if self._lineCount >= 100 and self._startTime is None:
self._startTime = time.time()
return processedLine
except Exception as (e):
if self._filehandle is not None:
self._filehandle.close()
raise e
def getLineCount(self):
return self._lineCount
def _processLine(self, line):
lineType = self._prevLineType
if line.startswith(";TYPE:"):
lineType = line[6:].strip()
if ";" in line:
line = line[0:line.find(";")]
line = line.strip()
if len(line) > 0:
if self._prevLineType != lineType:
return line, lineType
else:
return line
else:
return None
class StreamingGcodeFileInformation(PrintingGcodeFileInformation):
pass

7
requirements-bbb.txt Normal file
View File

@ -0,0 +1,7 @@
flask==0.9
werkzeug==0.8.3
tornado>=2.4.1
tornadio2>=0.0.4
PyYAML>=3.10
Flask-Login>=0.1.3
Flask-Principal>=0.3.5