diff --git a/README.md b/README.md index 7dd80ab..0f6ea7c 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,17 @@ OctoPrint OctoPrint provides a responsive web interface for controlling a 3D printer (RepRap, Ultimaker, ...). It currently allows -* uploading .gcode files to the server and managing them via the UI -* selecting a file for printing, getting the usual stats regarding filament length etc (stats can be disabled for - faster initial processing) +* 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 not available for SD files) * 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 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 * 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: 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, 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 - ./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`. @@ -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 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). diff --git a/octoprint/printer.py b/octoprint/printer.py index 85ccdc8..7e7eaea 100644 --- a/octoprint/printer.py +++ b/octoprint/printer.py @@ -61,18 +61,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 @@ -93,10 +92,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 ) @@ -168,52 +165,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._setCurrentZ(None) + self._comm.startPrint() def togglePausePrint(self): """ @@ -221,6 +191,7 @@ class Printer(): """ if self._comm is None: return + self._comm.setPause(not self._comm.isPaused()) def cancelPrint(self, disableMotorsAndHeater=True): @@ -230,23 +201,19 @@ 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) + if self._selectedFile is not None: + self._gcodeManager.printFailed(self._selectedFile["filename"]) eventManager().fire("PrintFailed", self._filename) - - #~~ state monitoring def setTimelapse(self, timelapse): if self._timelapse is not None and self.isPrinting(): @@ -257,6 +224,8 @@ class Printer(): def getTimelapse(self): return self._timelapse + #~~ state monitoring + def _setCurrentZ(self, currentZ): self._currentZ = currentZ @@ -279,7 +248,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 @@ -292,7 +261,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) @@ -316,19 +289,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(): @@ -337,7 +316,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: @@ -364,7 +343,6 @@ class Printer(): "printing": self.isPrinting(), "closedOrError": self.isClosedOrError(), "error": self.isError(), - "loading": self.isLoading(), "paused": self.isPaused(), "ready": self.isReady(), "sdReady": sdReady @@ -395,14 +373,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 @@ -419,25 +398,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): """ @@ -460,33 +424,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: @@ -496,13 +476,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 @@ -518,57 +491,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()}) - - eventManager().fire("LoadDone", filename) - - 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. @@ -578,6 +502,21 @@ class Printer(): else: 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): 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() 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): 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 @@ -661,7 +598,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: @@ -701,11 +638,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) @@ -733,14 +668,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() @@ -764,8 +691,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 ef21eb9..6444af3 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -240,23 +240,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"]) @@ -279,6 +262,17 @@ def sdCommand(): 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 @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: 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) - - global eventManager - eventManager.fire("LoadStart", filename) + printer.selectFile(filename, sd, printAfterLoading) return jsonify(SUCCESS) @app.route(BASEURL + "gcodefiles/delete", methods=["POST"]) diff --git a/octoprint/settings.py b/octoprint/settings.py index 1d6457a..1073251 100644 --- a/octoprint/settings.py +++ b/octoprint/settings.py @@ -43,7 +43,7 @@ default_settings = { "waitForWaitOnConnect": False, "alwaysSendChecksum": False, "resetLineNumbersWithPrefixedN": False, - "sdSupport": False + "sdSupport": True }, "folder": { "uploads": None, 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/gcodeviewer/js/Worker.js b/octoprint/static/gcodeviewer/js/Worker.js index 98b39b2..feb4d95 100644 --- a/octoprint/static/gcodeviewer/js/Worker.js +++ b/octoprint/static/gcodeviewer/js/Worker.js @@ -223,42 +223,31 @@ var assumeNonDC = false; for(var i=0;i0&&prevRetract < 0){ prevRetract = 0; retract = 1; } else { - // prevRetract = retract; retract = 0; } prev_extrude[argChar] = numSlice; @@ -317,24 +301,24 @@ prev_extrude["abs"] = Math.sqrt((prevX-x)*(prevX-x)+(prevY-y)*(prevY-y)); } 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}; if(typeof(x) !== 'undefined') prevX = x; if(typeof(y) !== 'undefined') prevY = y; - } else if(gcode[i].match(/^(?:M82)/i)){ + } else if(line.match(/^(?:M82)/i)){ extrudeRelative = false; - }else if(gcode[i].match(/^(?:G91)/i)){ + }else if(line.match(/^(?:G91)/i)){ extrudeRelative=true; - }else if(gcode[i].match(/^(?:G90)/i)){ + }else if(line.match(/^(?:G90)/i)){ extrudeRelative=false; - }else if(gcode[i].match(/^(?:M83)/i)){ + }else if(line.match(/^(?:M83)/i)){ extrudeRelative=true; - }else if(gcode[i].match(/^(?:M101)/i)){ + }else if(line.match(/^(?:M101)/i)){ dcExtrude=true; - }else if(gcode[i].match(/^(?:M103)/i)){ + }else if(line.match(/^(?:M103)/i)){ dcExtrude=false; - }else if(gcode[i].match(/^(?:G92)/i)){ - var args = gcode[i].split(/\s/); + }else if(line.match(/^(?:G92)/i)){ + var args = line.split(/\s/); for(j=0;j gcode.length*0.02 && sendMultiLayer.length != 0){ lastSend = i; @@ -429,13 +406,7 @@ sendLayerZ = undefined; } } -// sendMultiLayer[sendMultiLayer.length] = layer; -// sendMultiLayerZ[sendMultiLayerZ.length] = z; sendMultiLayerToParent(sendMultiLayer, sendMultiLayerZ, i/gcode.length*100); - -// if(gCodeOptions["sortLayers"])sortLayers(); -// if(gCodeOptions["purgeEmptyLayers"])purgeLayers(); - }; diff --git a/octoprint/static/gcodeviewer/js/gCodeReader.js b/octoprint/static/gcodeviewer/js/gCodeReader.js index 190bc57..f30fdc6 100644 --- a/octoprint/static/gcodeviewer/js/gCodeReader.js +++ b/octoprint/static/gcodeviewer/js/gCodeReader.js @@ -23,57 +23,73 @@ GCODE.gCodeReader = (function(){ purgeEmptyLayers: true, analyzeModel: false }; - var linesCmdIndex = {}; - var prepareGCode = function(){ + var percentageTree = undefined; + + var prepareGCode = function(totalSize){ if(!lines)return; gcode = []; - var i, tmp; + var i, tmp, byteCount; + + byteCount = 0; for(i=0;i 1 || tmp === -1) { - gcode.push(lines[i]); + gcode.push({line: lines[i], percentage: byteCount * 100 / totalSize}); } } lines = []; -// console.log("GCode prepared"); }; var sortLayers = function(){ var sortedZ = []; var tmpModel = []; -// var cnt = 0; -// console.log(z_heights); + for(var layer in z_heights){ sortedZ[z_heights[layer]] = layer; -// cnt++; } -// console.log("cnt is " + cnt); + sortedZ.sort(function(a,b){ return a-b; }); -// console.log(sortedZ); -// console.log(model.length); + for(var i=0;i 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; + } +} diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js index 01a01d8..e585a75 100644 --- a/octoprint/static/js/ui.js +++ b/octoprint/static/js/ui.js @@ -216,21 +216,29 @@ 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.heightString = ko.computed(function() { + if (!self.currentHeight()) + return "-"; + return self.currentHeight(); + }) self.progressString = ko.computed(function() { if (!self.progress()) return 0; @@ -254,8 +262,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 +274,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 +291,7 @@ function PrinterStateViewModel(loginStateViewModel) { } else { self.progress(undefined); } - self.currentLine(data.currentLine); + self.filepos(data.filepos); self.printTime(data.printTime); self.printTimeLeft(data.printTimeLeft); } @@ -1093,13 +1081,16 @@ function GcodeViewModel(loginStateViewModel) { } self._processData = function(data) { - if(!self.enabled)return; + if (!self.enabled) return; + if (!data.job.filename) return; - if(self.loadedFilename == data.job.filename) { - var cmdIndex = GCODE.gCodeReader.getLinesCmdIndex(data.progress.progress); - if(cmdIndex){ - GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd); - GCODE.ui.updateLayerInfo(cmdIndex.layer); + if(self.loadedFilename && self.loadedFilename == data.job.filename) { + if (data.state.flags && (data.state.flags.printing || data.state.flags.paused)) { + var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.progress * 100); + if(cmdIndex){ + GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd); + GCODE.ui.updateLayerInfo(cmdIndex.layer); + } } self.errorCount = 0 } else if (data.job.filename) { @@ -1745,43 +1736,48 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor //~~ local storage self._saveCurrentSortingToLocalStorage = function() { - self._initializeLocalStorage(); - - var currentSorting = self.currentSorting(); - if (currentSorting !== undefined) - localStorage[self.listType + "." + "currentSorting"] = currentSorting; - else - localStorage[self.listType + "." + "currentSorting"] = undefined; + if ( self._initializeLocalStorage() ) { + var currentSorting = self.currentSorting(); + if (currentSorting !== undefined) + localStorage[self.listType + "." + "currentSorting"] = currentSorting; + else + localStorage[self.listType + "." + "currentSorting"] = undefined; + } } self._loadCurrentSortingFromLocalStorage = function() { - self._initializeLocalStorage(); - - if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"])) - self.currentSorting(localStorage[self.listType + "." + "currentSorting"]); - else - self.currentSorting(defaultSorting); + if ( self._initializeLocalStorage() ) { + if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"])) + self.currentSorting(localStorage[self.listType + "." + "currentSorting"]); + else + self.currentSorting(defaultSorting); + } } self._saveCurrentFiltersToLocalStorage = function() { - self._initializeLocalStorage(); - - var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters()); - localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters); + if ( self._initializeLocalStorage() ) { + var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters()); + localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters); + } } self._loadCurrentFiltersFromLocalStorage = function() { - self._initializeLocalStorage(); - - self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"]))); + if ( self._initializeLocalStorage() ) { + self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"]))); + } } 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) - return; + return true; localStorage[self.listType + "." + "currentSorting"] = self.defaultSorting; localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(self.defaultFilters); + + return true; } self._loadCurrentFiltersFromLocalStorage(); diff --git a/octoprint/templates/index.jinja2 b/octoprint/templates/index.jinja2 index 7b3c28c..0684fbc 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:
+ Height:
Print Time:
Print Time Left:
+ Printed:
@@ -153,7 +153,7 @@ {% if enableSdSupport %}
- +