Merge branch 'devel' into events
Conflicts: octoprint/printer.py octoprint/server.py octoprint/util/comm.pymaster
commit
ed9e93f379
13
README.md
13
README.md
|
@ -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
|
||||||
|
@ -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).
|
|
||||||
|
|
|
@ -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,24 +201,20 @@ 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():
|
||||||
self._timelapse.onPrintjobStopped()
|
self._timelapse.onPrintjobStopped()
|
||||||
|
@ -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 self._selectedFile is not None:
|
||||||
if state == self._comm.STATE_OPERATIONAL:
|
if state == self._comm.STATE_OPERATIONAL:
|
||||||
self._gcodeManager.printSucceeded(self._filename)
|
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:
|
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)
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -526,3 +526,9 @@ 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;
|
||||||
|
}
|
|
@ -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();
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 |
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,14 +1081,17 @@ 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)) {
|
||||||
|
var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.progress * 100);
|
||||||
if(cmdIndex){
|
if(cmdIndex){
|
||||||
GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
|
GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
|
||||||
GCODE.ui.updateLayerInfo(cmdIndex.layer);
|
GCODE.ui.updateLayerInfo(cmdIndex.layer);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.errorCount = 0
|
self.errorCount = 0
|
||||||
} else if (data.job.filename) {
|
} else if (data.job.filename) {
|
||||||
self.loadFile(data.job.filename);
|
self.loadFile(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();
|
||||||
|
|
|
@ -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> <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>
|
||||||
|
|
|
@ -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
|
||||||
|
return printTimeTotal - printTime
|
||||||
else:
|
else:
|
||||||
printTimeTotal = printTime * self._sdFileSize
|
|
||||||
printTimeLeft = printTimeTotal - printTime
|
|
||||||
return printTimeLeft
|
|
||||||
else:
|
|
||||||
# for host printing we only start counting the print time at gcode line 100, so we need to calculate stuff
|
|
||||||
# a bit different here
|
|
||||||
if self.getPrintPos() < 200:
|
|
||||||
return None
|
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,7 +630,9 @@ 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:
|
||||||
|
heatingUp = True
|
||||||
|
if self._heatupWaitStartTime != 0:
|
||||||
t = time.time()
|
t = time.time()
|
||||||
self._heatupWaitTimeLost = t - self._heatupWaitStartTime
|
self._heatupWaitTimeLost = t - self._heatupWaitStartTime
|
||||||
self._heatupWaitStartTime = t
|
self._heatupWaitStartTime = t
|
||||||
|
@ -640,6 +642,11 @@ class MachineCom(object):
|
||||||
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,24 +965,28 @@ 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]
|
||||||
|
|
||||||
|
if not self.isStreaming():
|
||||||
try:
|
try:
|
||||||
if line == 'M0' or line == 'M1':
|
if matchesGcode(line, "M0") or matchesGcode(line, "M1"):
|
||||||
self.setPause(True)
|
self.setPause(True)
|
||||||
line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
|
line = "M105" # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
|
||||||
if self._printSection in self._feedRateModifier:
|
if (matchesGcode(line, "G0") or matchesGcode(line, "G1")) and 'Z' in line:
|
||||||
line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line)
|
|
||||||
if ('G0' in line or 'G1' in line) 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
|
||||||
|
@ -975,106 +994,112 @@ class MachineCom(object):
|
||||||
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():
|
if not self.isOperational() or self.isPrinting():
|
||||||
return
|
return
|
||||||
if self._sdPrinting:
|
|
||||||
self._sdPrinting = False
|
if self._currentFile is None:
|
||||||
self._gcodeList = gcodeList
|
raise ValueError("No file selected for printing")
|
||||||
self._gcodePos = 0
|
|
||||||
self._printSection = 'CUSTOM'
|
self._printSection = "CUSTOM"
|
||||||
self._changeState(self.STATE_PRINTING)
|
self._changeState(self.STATE_PRINTING)
|
||||||
self._printStartTime = time.time()
|
|
||||||
self._sendNext()
|
|
||||||
eventManager().fire("PrintStarted")
|
eventManager().fire("PrintStarted")
|
||||||
|
|
||||||
def printSdFile(self):
|
try:
|
||||||
if not self.isOperational() or self.isPrinting():
|
self._currentFile.start()
|
||||||
return
|
if self.isSdFileSelected():
|
||||||
|
|
||||||
if self.isPaused():
|
if self.isPaused():
|
||||||
self.sendCommand("M26 S0") # reset position in file to byte 0
|
self.sendCommand("M26 S0")
|
||||||
|
self._currentFile.setFilepos(0)
|
||||||
self.sendCommand("M24")
|
self.sendCommand("M24")
|
||||||
|
else:
|
||||||
|
self._sendNext()
|
||||||
|
except:
|
||||||
|
self._changeState(self.STATE_ERROR)
|
||||||
|
self._errorValue = getExceptionString()
|
||||||
|
|
||||||
self._printSection = 'CUSTOM'
|
def startFileTransfer(self, filename, remoteFilename):
|
||||||
self._sdPrinting = True
|
if not self.isOperational() or self.isBusy():
|
||||||
self._changeState(self.STATE_PRINTING)
|
return
|
||||||
self._printStartTime = time.time()
|
|
||||||
eventManager().fire("PrintStarted")
|
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():
|
||||||
|
return
|
||||||
|
|
||||||
self._changeState(self.STATE_OPERATIONAL)
|
self._changeState(self.STATE_OPERATIONAL)
|
||||||
if self._sdPrinting:
|
|
||||||
self._sdPrinting = False
|
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
|
|
@ -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
|
Loading…
Reference in New Issue