diff --git a/octoprint/printer.py b/octoprint/printer.py index db41c8d..c0bcaf2 100644 --- a/octoprint/printer.py +++ b/octoprint/printer.py @@ -57,18 +57,17 @@ class Printer(): self._printTime = None self._printTimeLeft = None - # gcode handling - self._gcodeList = None - self._filename = None - self._gcodeLoader = None + self._printAfterSelect = False # sd handling self._sdPrinting = False + self._sdStreaming = False + + # TODO Still needed? self._sdFile = None self._sdStreamer = None - # feedrate - self._feedrateModifierMapping = {"outerWall": "WALL-OUTER", "innerWall": "WALL_INNER", "fill": "FILL", "support": "SUPPORT"} + self._selectedFile = None # timelapse self._timelapse = None @@ -89,10 +88,8 @@ class Printer(): ) self._stateMonitor.reset( state={"state": None, "stateString": self.getStateString(), "flags": self._getStateFlags()}, - jobData={"filename": None, "lines": None, "estimatedPrintTime": None, "filament": None}, - gcodeData={"filename": None, "progress": None}, - sdUploadData={"filename": None, "progress": None}, - progress={"progress": None, "printTime": None, "printTimeLeft": None}, + jobData={"filename": None, "filesize": None, "estimatedPrintTime": None, "filament": None}, + progress={"progress": None, "filepos": None, "printTime": None, "printTimeLeft": None}, currentZ=None ) @@ -163,52 +160,25 @@ class Printer(): for command in commands: self._comm.sendCommand(command) - def setFeedrateModifier(self, structure, percentage): - if (not self._feedrateModifierMapping.has_key(structure)) or percentage < 0: + def selectFile(self, filename, sd, printAfterSelect=False): + if self._comm is not None and (self._comm.isBusy() or self._comm.isStreaming()): 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): """ Starts the currently loaded print job. 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 - if self._gcodeList is None and self._sdFile is None: - return - if self._comm.isPrinting(): + if self._selectedFile is None: return self._setCurrentZ(-1) - if self._sdFile is not None: - # we are working in sd mode - self._sdPrinting = True - self._comm.printSdFile() - else: - # we are working in local mode - self._comm.printGCode(self._gcodeList) + self._comm.startPrint() def togglePausePrint(self): """ @@ -216,6 +186,7 @@ class Printer(): """ if self._comm is None: return + self._comm.setPause(not self._comm.isPaused()) def cancelPrint(self, disableMotorsAndHeater=True): @@ -225,22 +196,18 @@ class Printer(): if self._comm is None: return - if self._sdPrinting: - self._sdPrinting = False self._comm.cancelPrint() if disableMotorsAndHeater: 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._setProgressData(None, None, None, None) # mark print as failure - if self._filename is not None: - self._gcodeManager.printFailed(self._filename) - - #~~ state monitoring + if self._selectedFile is not None: + self._gcodeManager.printFailed(self._selectedFile["filename"]) def setTimelapse(self, timelapse): if self._timelapse is not None and self.isPrinting(): @@ -251,6 +218,8 @@ class Printer(): def getTimelapse(self): return self._timelapse + #~~ state monitoring + def _setCurrentZ(self, currentZ): self._currentZ = currentZ @@ -273,7 +242,7 @@ class Printer(): self._messages = self._messages[-300:] self._stateMonitor.addMessage(message) - def _setProgressData(self, progress, currentLine, printTime, printTimeLeft): + def _setProgressData(self, progress, filepos, printTime, printTimeLeft): self._progress = progress self._printTime = printTime self._printTimeLeft = printTimeLeft @@ -286,7 +255,11 @@ class Printer(): if (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): currentTimeUtc = int(time.time() * 1000) @@ -310,19 +283,25 @@ class Printer(): self._stateMonitor.addTemperature({"currentTime": currentTimeUtc, "temp": self._temp, "bedTemp": self._bedTemp, "targetTemp": self._targetTemp, "targetBedTemp": self._targetBedTemp}) - def _setJobData(self, filename, gcodeList): - self._filename = filename - self._gcodeList = gcodeList - - lines = None - if self._gcodeList: - lines = len(self._gcodeList) + def _setJobData(self, filename, filesize, sd): + if filename is not None: + self._selectedFile = { + "filename": filename, + "filesize": filesize, + "sd": sd + } + else: + self._selectedFile = None formattedFilename = None + formattedFilesize = None estimatedPrintTime = None filament = None - if self._filename: - formattedFilename = os.path.basename(self._filename) + if filename: + formattedFilename = os.path.basename(filename) + + if filesize: + formattedFilesize = util.getFormattedSize(filesize) fileData = self._gcodeManager.getFileData(filename) if fileData is not None and "gcodeAnalysis" in fileData.keys(): @@ -331,7 +310,7 @@ class Printer(): if "filament" in fileData["gcodeAnalysis"].keys(): 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): try: @@ -358,7 +337,6 @@ class Printer(): "printing": self.isPrinting(), "closedOrError": self.isClosedOrError(), "error": self.isError(), - "loading": self.isLoading(), "paused": self.isPaused(), "ready": self.isReady(), "sdReady": sdReady @@ -386,14 +364,15 @@ class Printer(): if oldState == self._comm.STATE_PRINTING and state != self._comm.STATE_PAUSED: self._timelapse.onPrintjobStopped() 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 if self._comm is not None and oldState == self._comm.STATE_PRINTING: - if state == self._comm.STATE_OPERATIONAL: - self._gcodeManager.printSucceeded(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._filename) + if self._selectedFile is not None: + if state == self._comm.STATE_OPERATIONAL: + self._gcodeManager.printSucceeded(self._selectedFile["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 elif self._comm is not None and state == self._comm.STATE_PRINTING: self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing @@ -410,25 +389,10 @@ class Printer(): def mcProgress(self): """ 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: - 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()) + self._setProgressData(self._comm.getPrintProgress(), self._comm.getPrintFilepos(), self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate()) def mcZChange(self, newZ): """ @@ -446,33 +410,49 @@ class Printer(): def mcSdFiles(self, files): self._sendTriggerUpdateCallbacks("gcodeFiles") - def mcSdSelected(self, filename, filesize): - self._sdFile = filename - - self._setJobData(filename, None) + def mcFileSelected(self, filename, filesize, sd): + self._setJobData(filename, filesize, sd) self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) - if self._sdPrintAfterSelect: + if self._printAfterSelect: self.startPrint() - def mcSdPrintingDone(self): - self._sdPrinting = False - self._setProgressData(1.0, None, self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate()) + def mcPrintjobDone(self): + self._setProgressData(1.0, self._selectedFile["filesize"], self._comm.getPrintTime(), 0) 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): if self._comm is None: return return self._comm.getSdFiles() - def addSdFile(self, filename, file): - if not self._comm: + def addSdFile(self, filename, path): + if not self._comm or self._comm.isBusy(): return - - self._sdStreamer = SdFileStreamer(self._comm, filename, file, self._onSdFileStreamProgress, self._onSdFileStreamFinish) - self._sdStreamer.start() + self._comm.startFileTransfer(path, filename[:8].lower() + ".gco") def deleteSdFile(self, filename): if not self._comm: @@ -482,13 +462,6 @@ class Printer(): self._sdFile = None self._comm.deleteSdFile(filename) - def selectSdFile(self, filename, printAfterSelect): - if not self._comm: - return - - self._sdPrintAfterSelect = printAfterSelect - self._comm.selectSdFile(filename) - def initSdCard(self): if not self._comm: return @@ -504,56 +477,8 @@ class Printer(): return 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()}) - - def _onGcodeLoadedToPrint(self, filename, gcodeList): - self._onGcodeLoaded(filename, gcodeList) - self.startPrint() - #~~ 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): """ Returns a human readable string corresponding to the current communication state. @@ -579,55 +504,7 @@ class Printer(): return self._comm is not None and self._comm.isError() def isReady(self): - return self._gcodeLoader is None and self._sdStreamer is None and ((self._gcodeList and len(self._gcodeList) > 0) or self._sdFile) - - def isLoading(self): - return self._gcodeLoader is not None or self._sdStreamer is not None - -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 - thread while constantly notifying interested listeners about the current progress. - The progress is returned as a float value between 0 and 1 which is to be interpreted as the percentage of completion. - """ - - def __init__(self, filename, progressCallback, loadedCallback): - threading.Thread.__init__(self) - - self._progressCallback = progressCallback - self._loadedCallback = loadedCallback - - self._filename = filename - self._gcodeList = None - - def run(self): - #Send an initial M110 to reset the line counter to zero. - prevLineType = lineType = "CUSTOM" - gcodeList = ["M110 N0"] - filesize = os.stat(self._filename).st_size - with open(self._filename, "r") as file: - for line in file: - if line.startswith(";TYPE:"): - lineType = line[6:].strip() - if ";" in line: - line = line[0:line.find(";")] - line = line.strip() - if len(line) > 0: - if prevLineType != lineType: - gcodeList.append((line, lineType, )) - else: - gcodeList.append(line) - prevLineType = lineType - self._onLoadingProgress(float(file.tell()) / float(filesize)) - - self._gcodeList = gcodeList - self._loadedCallback(self._filename, self._gcodeList) - - def _onLoadingProgress(self, progress): - self._progressCallback(self._filename, progress, "loading") - - def _onParsingProgress(self, progress): - self._progressCallback(self._filename, progress, "parsing") + return self.isOperational() and not self._comm.isStreaming() class SdFileStreamer(threading.Thread): def __init__(self, comm, filename, file, progressCallback, finishCallback): @@ -644,7 +521,7 @@ class SdFileStreamer(threading.Thread): return name = self._filename[:self._filename.rfind(".")] - sdFilename = name[:8] + ".GCO" + sdFilename = name[:8].lower() + ".gco" try: size = os.stat(self._file).st_size with open(self._file, "r") as f: @@ -683,11 +560,9 @@ class StateMonitor(object): self._worker.daemon = True 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.setJobData(jobData) - self.setGcodeData(gcodeData) - self.setSdUploadData(sdUploadData) self.setProgress(progress) self.setCurrentZ(currentZ) @@ -715,14 +590,6 @@ class StateMonitor(object): self._jobData = jobData 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): self._progress = progress self._changeEvent.set() @@ -746,8 +613,6 @@ class StateMonitor(object): return { "state": self._state, "job": self._jobData, - "gcode": self._gcodeData, - "sdUpload": self._sdUploadData, "currentZ": self._currentZ, "progress": self._progress } diff --git a/octoprint/server.py b/octoprint/server.py index f7ee412..f9c5323 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -232,23 +232,6 @@ def jog(): 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"]) def getCustomControls(): customControls = settings().get(["controls"]) @@ -312,13 +295,14 @@ def loadGcodeFile(): if "print" in request.values.keys() and request.values["print"] in valid_boolean_trues: printAfterLoading = True + sd = False + filename = None if "target" in request.values.keys() and request.values["target"] == "sd": filename = request.values["filename"] - printer.selectSdFile(filename, printAfterLoading) + sd = True else: filename = gcodeManager.getAbsolutePath(request.values["filename"]) - if filename is not None: - printer.loadGcode(filename, printAfterLoading) + printer.selectFile(filename, sd, printAfterLoading) return jsonify(SUCCESS) @app.route(BASEURL + "gcodefiles/delete", methods=["POST"]) diff --git a/octoprint/static/css/octoprint.less b/octoprint/static/css/octoprint.less index b61e3bb..878a049 100644 --- a/octoprint/static/css/octoprint.less +++ b/octoprint/static/css/octoprint.less @@ -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; } \ No newline at end of file diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js index 01a01d8..31a5982 100644 --- a/octoprint/static/js/ui.js +++ b/octoprint/static/js/ui.js @@ -216,21 +216,25 @@ function PrinterStateViewModel(loginStateViewModel) { self.isSdReady = ko.observable(undefined); self.filename = ko.observable(undefined); - self.filament = ko.observable(undefined); - self.estimatedPrintTime = ko.observable(undefined); + self.progress = ko.observable(undefined); + self.filesize = ko.observable(undefined); + self.filepos = ko.observable(undefined); self.printTime = ko.observable(undefined); self.printTimeLeft = ko.observable(undefined); - self.progress = ko.observable(undefined); - self.currentLine = ko.observable(undefined); - self.totalLines = ko.observable(undefined); + self.sd = ko.observable(undefined); + + self.filament = ko.observable(undefined); + self.estimatedPrintTime = ko.observable(undefined); + self.currentHeight = ko.observable(undefined); - self.lineString = ko.computed(function() { - if (!self.totalLines()) + self.byteString = ko.computed(function() { + if (!self.filesize()) return "-"; - var currentLine = self.currentLine() ? self.currentLine() : "-"; - return currentLine + " / " + self.totalLines(); + var filepos = self.filepos() ? self.filepos() : "-"; + return filepos + " / " + self.filesize(); }); + self.progressString = ko.computed(function() { if (!self.progress()) return 0; @@ -254,8 +258,6 @@ function PrinterStateViewModel(loginStateViewModel) { self._fromData = function(data) { self._processStateData(data.state) self._processJobData(data.job); - self._processGcodeData(data.gcode); - self._processSdUploadData(data.sdUpload); self._processProgressData(data.progress); self._processZData(data.currentZ); } @@ -268,33 +270,15 @@ function PrinterStateViewModel(loginStateViewModel) { self.isPrinting(data.flags.printing); self.isError(data.flags.error); self.isReady(data.flags.ready); - self.isLoading(data.flags.loading); self.isSdReady(data.flags.sdReady); } self._processJobData = function(data) { self.filename(data.filename); - self.totalLines(data.lines); + self.filesize(data.filesize); self.estimatedPrintTime(data.estimatedPrintTime); self.filament(data.filament); - } - - 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.sd(data.sd); } self._processProgressData = function(data) { @@ -303,7 +287,7 @@ function PrinterStateViewModel(loginStateViewModel) { } else { self.progress(undefined); } - self.currentLine(data.currentLine); + self.filepos(data.filepos); self.printTime(data.printTime); self.printTimeLeft(data.printTimeLeft); } diff --git a/octoprint/templates/index.jinja2 b/octoprint/templates/index.jinja2 index 7b3c28c..f10be39 100644 --- a/octoprint/templates/index.jinja2 +++ b/octoprint/templates/index.jinja2 @@ -108,13 +108,13 @@
Machine State:
- File:
+ File:  (SD)
Filament:
Estimated Print Time:
- Line:
Height:
Print Time:
Print Time Left:
+ Printed:
@@ -153,7 +153,7 @@ {% if enableSdSupport %}
- +