Changed to GCODE streaming instead of loading it all into memory -- no more loading times. Also better file handling.

master
Gina Häußge 2013-05-31 22:41:53 +02:00
parent 8d53f313fe
commit 56cb1c294c
6 changed files with 397 additions and 401 deletions

View File

@ -57,18 +57,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
@ -89,10 +88,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
) )
@ -163,52 +160,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(-1)
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):
""" """
@ -216,6 +186,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):
@ -225,22 +196,18 @@ 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"])
#~~ 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():
@ -251,6 +218,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
@ -273,7 +242,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
@ -286,7 +255,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)
@ -310,19 +283,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():
@ -331,7 +310,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:
@ -358,7 +337,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
@ -386,14 +364,15 @@ class Printer():
if oldState == self._comm.STATE_PRINTING and state != self._comm.STATE_PAUSED: if oldState == self._comm.STATE_PRINTING and state != self._comm.STATE_PAUSED:
self._timelapse.onPrintjobStopped() self._timelapse.onPrintjobStopped()
elif state == self._comm.STATE_PRINTING and oldState != self._comm.STATE_PAUSED: elif state == self._comm.STATE_PRINTING and oldState != self._comm.STATE_PAUSED:
self._timelapse.onPrintjobStarted(self._filename) self._timelapse.onPrintjobStarted(self._selectedFile["filename"])
# forward relevant state changes to gcode manager # forward relevant state changes to gcode manager
if self._comm is not None and oldState == self._comm.STATE_PRINTING: if self._comm is not None and oldState == self._comm.STATE_PRINTING:
if state == self._comm.STATE_OPERATIONAL: if self._selectedFile is not None:
self._gcodeManager.printSucceeded(self._filename) if state == self._comm.STATE_OPERATIONAL:
elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR: self._gcodeManager.printSucceeded(self._selectedFile["filename"])
self._gcodeManager.printFailed(self._filename) elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR:
self._gcodeManager.printFailed(self._selectedFile["filename"])
self._gcodeManager.resumeAnalysis() # printing done, put those cpu cycles to good use self._gcodeManager.resumeAnalysis() # printing done, put those cpu cycles to good use
elif self._comm is not None and state == self._comm.STATE_PRINTING: elif self._comm is not None and state == self._comm.STATE_PRINTING:
self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing
@ -410,25 +389,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):
""" """
@ -446,33 +410,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:
@ -482,13 +462,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
@ -504,56 +477,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()})
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.
@ -579,55 +504,7 @@ 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):
return self._gcodeLoader is not None or self._sdStreamer is not None
class GcodeLoader(threading.Thread):
"""
The GcodeLoader takes care of loading a gcode-File from disk and parsing it into a gcode object in a separate
thread while constantly notifying interested listeners about the current progress.
The progress is returned as a float value between 0 and 1 which is to be interpreted as the percentage of completion.
"""
def __init__(self, filename, progressCallback, loadedCallback):
threading.Thread.__init__(self)
self._progressCallback = progressCallback
self._loadedCallback = loadedCallback
self._filename = filename
self._gcodeList = None
def run(self):
#Send an initial M110 to reset the line counter to zero.
prevLineType = lineType = "CUSTOM"
gcodeList = ["M110 N0"]
filesize = os.stat(self._filename).st_size
with open(self._filename, "r") as file:
for line in file:
if line.startswith(";TYPE:"):
lineType = line[6:].strip()
if ";" in line:
line = line[0:line.find(";")]
line = line.strip()
if len(line) > 0:
if prevLineType != lineType:
gcodeList.append((line, lineType, ))
else:
gcodeList.append(line)
prevLineType = lineType
self._onLoadingProgress(float(file.tell()) / float(filesize))
self._gcodeList = gcodeList
self._loadedCallback(self._filename, self._gcodeList)
def _onLoadingProgress(self, progress):
self._progressCallback(self._filename, progress, "loading")
def _onParsingProgress(self, progress):
self._progressCallback(self._filename, progress, "parsing")
class SdFileStreamer(threading.Thread): class SdFileStreamer(threading.Thread):
def __init__(self, comm, filename, file, progressCallback, finishCallback): def __init__(self, comm, filename, file, progressCallback, finishCallback):
@ -644,7 +521,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:
@ -683,11 +560,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)
@ -715,14 +590,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()
@ -746,8 +613,6 @@ class StateMonitor(object):
return { return {
"state": self._state, "state": self._state,
"job": self._jobData, "job": self._jobData,
"gcode": self._gcodeData,
"sdUpload": self._sdUploadData,
"currentZ": self._currentZ, "currentZ": self._currentZ,
"progress": self._progress "progress": self._progress
} }

View File

@ -232,23 +232,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"])
@ -312,13 +295,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)
return jsonify(SUCCESS) return jsonify(SUCCESS)
@app.route(BASEURL + "gcodefiles/delete", methods=["POST"]) @app.route(BASEURL + "gcodefiles/delete", methods=["POST"])

View File

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

View File

@ -216,21 +216,25 @@ 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.progressString = ko.computed(function() { self.progressString = ko.computed(function() {
if (!self.progress()) if (!self.progress())
return 0; return 0;
@ -254,8 +258,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 +270,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 +287,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);
} }

View File

@ -108,13 +108,13 @@
<div class="accordion-body collapse in" id="state"> <div class="accordion-body collapse in" id="state">
<div class="accordion-inner"> <div class="accordion-inner">
Machine State: <strong data-bind="text: stateString"></strong><br> Machine State: <strong data-bind="text: stateString"></strong><br>
File: <strong data-bind="text: filename"></strong><br> File: <strong data-bind="text: filename"></strong>&nbsp;<strong data-bind="visible: sd">(SD)</strong><br>
Filament: <strong data-bind="text: filament"></strong><br> Filament: <strong data-bind="text: filament"></strong><br>
Estimated Print Time: <strong data-bind="text: estimatedPrintTime"></strong><br> Estimated Print Time: <strong data-bind="text: estimatedPrintTime"></strong><br>
Line: <strong data-bind="text: lineString"></strong><br>
Height: <strong data-bind="text: currentHeight"></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>

View File

@ -87,6 +87,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())
@ -202,6 +203,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):
@ -299,16 +301,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):
@ -351,15 +356,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 = -1 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
@ -373,25 +374,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([])
@ -417,8 +414,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:
@ -454,53 +453,51 @@ class MachineCom(object):
return self._state == self.STATE_PRINTING return self._state == self.STATE_PRINTING
def isSdPrinting(self): def isSdPrinting(self):
return self._sdPrinting return self.isSdFileSelected() and self.isPrinting()
def isSdFileSelected(self):
return self._currentFile is not None and isinstance(self._currentFile, PrintingSdFileInformation)
def isStreaming(self):
return self._currentFile is not None and isinstance(self._currentFile, StreamingGcodeFileInformation)
def isPaused(self): def isPaused(self):
return self._state == self.STATE_PAUSED return self._state == self.STATE_PAUSED
def isBusy(self): def isBusy(self):
return self.isPrinting() or self._state == self.STATE_RECEIVING_FILE return self.isPrinting() or self.isPaused()
def isSdReady(self): def isSdReady(self):
return self._sdAvailable return self._sdAvailable
def getPrintPos(self): def getPrintProgress(self):
if self._sdPrinting: if self._currentFile is None:
return self._sdFilePos return None
else: return self._currentFile.getProgress()
return self._gcodePos
def getPrintFilepos(self):
if self._currentFile is None:
return None
return self._currentFile.getFilepos()
def getPrintTime(self): def getPrintTime(self):
if self._printStartTime == None: if self._currentFile is None or self._currentFile.getStartTime() is None:
return 0 return None
else: else:
return time.time() - self._printStartTime return time.time() - self._currentFile.getStartTime()
def getPrintTimeRemainingEstimate(self): def getPrintTimeRemainingEstimate(self):
if self._printStartTime == None: printTime = self.getPrintTime()
if printTime is None:
return None return None
if self._sdPrinting: printTime /= 60
printTime = (time.time() - self._printStartTime) / 60 progress = self._currentFile.getProgress()
if self._sdFilePos > 0: if progress:
printTimeTotal = printTime * self._sdFileSize / self._sdFilePos printTimeTotal = printTime / progress
else: return printTimeTotal - printTime
printTimeTotal = printTime * self._sdFileSize
printTimeLeft = printTimeTotal - printTime
return printTimeLeft
else: else:
# for host printing we only start counting the print time at gcode line 100, so we need to calculate stuff return None
# a bit different here
if self.getPrintPos() < 200:
return None
printTime = (time.time() - self._printStartTime) / 60
printTimeTotal = printTime * (len(self._gcodeList) - 100) / (self.getPrintPos() - 100)
printTimeLeft = printTimeTotal - printTime
return printTimeLeft
def getSdProgress(self):
return (self._sdFilePos, self._sdFileSize)
def getTemp(self): def getTemp(self):
return self._temp return self._temp
@ -593,7 +590,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.lower())
continue continue
##~~ Temperature processing ##~~ Temperature processing
@ -619,6 +616,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()
@ -632,23 +634,24 @@ 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():
@ -725,7 +728,7 @@ 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:
self._sendCommand("M105") self._sendCommand("M105")
tempRequestTimeout = time.time() + 5 tempRequestTimeout = time.time() + 5
@ -738,7 +741,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
@ -746,7 +749,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()
@ -883,7 +886,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)
@ -916,123 +919,135 @@ 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)
return return
if self._gcodePos == 100:
self._printStartTime100 = time.time()
line = self._gcodeList[self._gcodePos]
if type(line) is tuple: if type(line) is tuple:
self._printSection = line[1] self._printSection = line[1]
line = line[0] line = line[0]
try:
if line == 'M0' or line == 'M1': if not self.isStreaming():
self.setPause(True) try:
line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause. if line == 'M0' or line == 'M1':
if self._printSection in self._feedRateModifier: self.setPause(True)
line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line) line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
if ('G0' in line or 'G1' in line) and 'Z' in line: if ('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
self._callback.mcZChange(z) self._callback.mcZChange(z)
except: except:
self._log("Unexpected error: %s" % (getExceptionString())) self._log("Unexpected error: %s" % (getExceptionString()))
self._sendCommand(line, True) self._sendCommand(line, True)
self._gcodePos += 1
self._callback.mcProgress() self._callback.mcProgress()
def sendCommand(self, cmd): def sendCommand(self, cmd):
cmd = cmd.encode('ascii', 'replace') cmd = cmd.encode('ascii', 'replace')
if self.isPrinting(): if self.isPrinting() and not self.isSdFileSelected():
self._commandQueue.put(cmd) self._commandQueue.put(cmd)
elif self.isOperational(): elif self.isOperational():
self._sendCommand(cmd) self._sendCommand(cmd)
def printGCode(self, gcodeList): def startPrint(self):
if not self.isOperational() or self.isPrinting():
return
if self._sdPrinting:
self._sdPrinting = False
self._gcodeList = gcodeList
self._gcodePos = 0
self._printSection = 'CUSTOM'
self._changeState(self.STATE_PRINTING)
self._printStartTime = time.time()
self._sendNext()
def printSdFile(self):
if not self.isOperational() or self.isPrinting(): if not self.isOperational() or self.isPrinting():
return return
if self.isPaused(): if self._currentFile is None:
self.sendCommand("M26 S0") # reset position in file to byte 0 raise ValueError("No file selected for printing")
self.sendCommand("M24")
self._printSection = 'CUSTOM' self._printSection = "CUSTOM"
self._sdPrinting = True
self._changeState(self.STATE_PRINTING) self._changeState(self.STATE_PRINTING)
self._printStartTime = time.time()
try:
self._currentFile.start()
if self.isSdFileSelected():
if self.isPaused():
self.sendCommand("M26 S0")
self._currentFile.setFilepos(0)
self.sendCommand("M24")
else:
self._sendNext()
except:
self._changeState(self.STATE_ERROR)
self._errorValue = getExceptionString()
def startFileTransfer(self, filename, remoteFilename):
if not self.isOperational() or self.isBusy():
return
self._currentFile = StreamingGcodeFileInformation(filename)
self._currentFile.start()
self.sendCommand("M28 %s" % remoteFilename)
self._callback.mcFileTransferStarted(remoteFilename, self._currentFile.getFilesize())
def selectFile(self, filename, sd):
if self.isBusy():
return
if sd:
if not self.isOperational():
# printer is not connected, can't use SD
return
self.sendCommand("M23 %s" % filename)
else:
self._currentFile = PrintingGcodeFileInformation(filename)
self._callback.mcFileSelected(filename, self._currentFile.getFilesize(), False)
def cancelPrint(self): def cancelPrint(self):
if self.isOperational(): if not self.isOperational() or self.isStreaming():
self._changeState(self.STATE_OPERATIONAL) return
if self._sdPrinting:
self._sdPrinting = False self._changeState(self.STATE_OPERATIONAL)
if self.isSdFileSelected():
self.sendCommand("M25") # pause print self.sendCommand("M25") # pause print
self.sendCommand("M26 S0") # reset position in file to byte 0 self.sendCommand("M26 S0") # reset position in file to byte 0
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
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
@ -1040,7 +1055,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")
@ -1050,7 +1065,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
@ -1064,3 +1079,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()
self._lineCount += 1
if not line:
self._filehandle.close()
self._filehandle = None
processedLine = self._processLine(line)
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