Merge branch 'sdcard' into events
Conflicts: octoprint/printer.py octoprint/server.py octoprint/util/comm.pymaster
commit
d571aa588a
|
@ -66,6 +66,11 @@ class Printer():
|
||||||
self._filename = None
|
self._filename = None
|
||||||
self._gcodeLoader = None
|
self._gcodeLoader = None
|
||||||
|
|
||||||
|
# sd handling
|
||||||
|
self._sdPrinting = False
|
||||||
|
self._sdFile = None
|
||||||
|
self._sdStreamer = None
|
||||||
|
|
||||||
# feedrate
|
# feedrate
|
||||||
self._feedrateModifierMapping = {"outerWall": "WALL-OUTER", "innerWall": "WALL_INNER", "fill": "FILL", "support": "SUPPORT"}
|
self._feedrateModifierMapping = {"outerWall": "WALL-OUTER", "innerWall": "WALL_INNER", "fill": "FILL", "support": "SUPPORT"}
|
||||||
|
|
||||||
|
@ -90,6 +95,7 @@ class Printer():
|
||||||
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, "lines": None, "estimatedPrintTime": None, "filament": None},
|
||||||
gcodeData={"filename": None, "progress": None},
|
gcodeData={"filename": None, "progress": None},
|
||||||
|
sdUploadData={"filename": None, "progress": None},
|
||||||
progress={"progress": None, "printTime": None, "printTimeLeft": None},
|
progress={"progress": None, "printTime": None, "printTimeLeft": None},
|
||||||
currentZ=None
|
currentZ=None
|
||||||
)
|
)
|
||||||
|
@ -124,6 +130,11 @@ class Printer():
|
||||||
try: callback.sendCurrentData(copy.deepcopy(data))
|
try: callback.sendCurrentData(copy.deepcopy(data))
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
|
def _sendTriggerUpdateCallbacks(self, type):
|
||||||
|
for callback in self._callbacks:
|
||||||
|
try: callback.sendUpdateTrigger(type)
|
||||||
|
except: pass
|
||||||
|
|
||||||
#~~ printer commands
|
#~~ printer commands
|
||||||
|
|
||||||
def connect(self, port=None, baudrate=None):
|
def connect(self, port=None, baudrate=None):
|
||||||
|
@ -171,6 +182,7 @@ class Printer():
|
||||||
if (self._comm is not None and self._comm.isPrinting()) or (self._gcodeLoader is not None):
|
if (self._comm is not None and self._comm.isPrinting()) or (self._gcodeLoader is not None):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._sdFile = None
|
||||||
self._setJobData(None, None)
|
self._setJobData(None, None)
|
||||||
|
|
||||||
onGcodeLoadedCallback = self._onGcodeLoaded
|
onGcodeLoadedCallback = self._onGcodeLoaded
|
||||||
|
@ -189,14 +201,19 @@ class Printer():
|
||||||
"""
|
"""
|
||||||
if self._comm is None or not self._comm.isOperational():
|
if self._comm is None or not self._comm.isOperational():
|
||||||
return
|
return
|
||||||
if self._gcodeList is None:
|
if self._gcodeList is None and self._sdFile is None:
|
||||||
return
|
return
|
||||||
if self._comm.isPrinting():
|
if self._comm.isPrinting():
|
||||||
return
|
return
|
||||||
|
|
||||||
self._setCurrentZ(-1)
|
self._setCurrentZ(-1)
|
||||||
|
if self._sdFile is not None:
|
||||||
self._comm.printGCode(self._gcodeList)
|
# 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):
|
||||||
"""
|
"""
|
||||||
|
@ -212,16 +229,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 line, height, print time
|
||||||
self._setCurrentZ(None)
|
self._setCurrentZ(None)
|
||||||
self._setProgressData(None, None, None)
|
self._setProgressData(None, None, None, None)
|
||||||
|
|
||||||
# mark print as failure
|
# mark print as failure
|
||||||
if self._filename:
|
if self._filename is not None:
|
||||||
self._gcodeManager.printFailed(self._filename)
|
self._gcodeManager.printFailed(self._filename)
|
||||||
eventManager().fire("PrintFailed", self._filename)
|
eventManager().fire("PrintFailed", self._filename)
|
||||||
|
|
||||||
|
@ -258,7 +279,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, printTime, printTimeLeft):
|
def _setProgressData(self, progress, currentLine, printTime, printTimeLeft):
|
||||||
self._progress = progress
|
self._progress = progress
|
||||||
self._printTime = printTime
|
self._printTime = printTime
|
||||||
self._printTimeLeft = printTimeLeft
|
self._printTimeLeft = printTimeLeft
|
||||||
|
@ -271,7 +292,7 @@ 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, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft})
|
self._stateMonitor.setProgress({"progress": self._progress, "currentLine": currentLine, "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)
|
||||||
|
@ -333,6 +354,11 @@ class Printer():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _getStateFlags(self):
|
def _getStateFlags(self):
|
||||||
|
if not settings().getBoolean(["feature", "sdSupport"]) or self._comm is None:
|
||||||
|
sdReady = False
|
||||||
|
else:
|
||||||
|
sdReady = self._comm.isSdReady()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"operational": self.isOperational(),
|
"operational": self.isOperational(),
|
||||||
"printing": self.isPrinting(),
|
"printing": self.isPrinting(),
|
||||||
|
@ -340,7 +366,8 @@ class Printer():
|
||||||
"error": self.isError(),
|
"error": self.isError(),
|
||||||
"loading": self.isLoading(),
|
"loading": self.isLoading(),
|
||||||
"paused": self.isPaused(),
|
"paused": self.isPaused(),
|
||||||
"ready": self.isReady()
|
"ready": self.isReady(),
|
||||||
|
"sdReady": sdReady
|
||||||
}
|
}
|
||||||
|
|
||||||
def getCurrentData(self):
|
def getCurrentData(self):
|
||||||
|
@ -376,13 +403,12 @@ class Printer():
|
||||||
self._gcodeManager.printSucceeded(self._filename)
|
self._gcodeManager.printSucceeded(self._filename)
|
||||||
elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR:
|
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._filename)
|
||||||
self._gcodeManager.resumeAnalysis() # do not analyse gcode while printing
|
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() # printing done, put those cpu cycles to good use
|
self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing
|
||||||
|
|
||||||
self._setState(state)
|
self._setState(state)
|
||||||
|
|
||||||
|
|
||||||
def mcMessage(self, message):
|
def mcMessage(self, message):
|
||||||
"""
|
"""
|
||||||
Callback method for the comm object, called upon message exchanges via serial.
|
Callback method for the comm object, called upon message exchanges via serial.
|
||||||
|
@ -390,18 +416,28 @@ class Printer():
|
||||||
"""
|
"""
|
||||||
self._addMessage(message)
|
self._addMessage(message)
|
||||||
|
|
||||||
def mcProgress(self, lineNr):
|
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 line.
|
||||||
"""
|
"""
|
||||||
oldProgress = self._progress
|
oldProgress = self._progress
|
||||||
|
|
||||||
if self._timelapse is not None:
|
if self._sdPrinting:
|
||||||
try: self._timelapse.onPrintjobProgress(oldProgress, self._progress, int(round(self._progress * 100 / len(self._gcodeList))))
|
newLine = None
|
||||||
except: pass
|
(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(self._comm.getPrintPos(), self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
|
self._setProgressData(newProgress, newLine, self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
|
||||||
|
|
||||||
def mcZChange(self, newZ):
|
def mcZChange(self, newZ):
|
||||||
"""
|
"""
|
||||||
|
@ -418,6 +454,83 @@ class Printer():
|
||||||
|
|
||||||
self._setCurrentZ(newZ)
|
self._setCurrentZ(newZ)
|
||||||
|
|
||||||
|
def mcSdStateChange(self, sdReady):
|
||||||
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
|
def mcSdFiles(self, files):
|
||||||
|
self._sendTriggerUpdateCallbacks("gcodeFiles")
|
||||||
|
|
||||||
|
def mcSdSelected(self, filename, filesize):
|
||||||
|
self._sdFile = filename
|
||||||
|
|
||||||
|
self._setJobData(filename, None)
|
||||||
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
|
if self._sdPrintAfterSelect:
|
||||||
|
self.startPrint()
|
||||||
|
|
||||||
|
def mcSdPrintingDone(self):
|
||||||
|
self._sdPrinting = False
|
||||||
|
self._setProgressData(1.0, None, self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
|
||||||
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
|
#~~ sd file handling
|
||||||
|
|
||||||
|
def getSdFiles(self):
|
||||||
|
if self._comm is None:
|
||||||
|
return
|
||||||
|
return self._comm.getSdFiles()
|
||||||
|
|
||||||
|
def addSdFile(self, filename, file):
|
||||||
|
if not self._comm:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._sdStreamer = SdFileStreamer(self._comm, filename, file, self._onSdFileStreamProgress, self._onSdFileStreamFinish)
|
||||||
|
self._sdStreamer.start()
|
||||||
|
|
||||||
|
def deleteSdFile(self, filename):
|
||||||
|
if not self._comm:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._sdFile == filename:
|
||||||
|
self._sdFile = None
|
||||||
|
self._comm.deleteSdFile(filename)
|
||||||
|
|
||||||
|
def selectSdFile(self, filename, printAfterSelect):
|
||||||
|
if not self._comm:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._sdPrintAfterSelect = printAfterSelect
|
||||||
|
self._comm.selectSdFile(filename)
|
||||||
|
|
||||||
|
def initSdCard(self):
|
||||||
|
if not self._comm:
|
||||||
|
return
|
||||||
|
self._comm.initSdCard()
|
||||||
|
|
||||||
|
def releaseSdCard(self):
|
||||||
|
if not self._comm:
|
||||||
|
return
|
||||||
|
self._comm.releaseSdCard()
|
||||||
|
|
||||||
|
def refreshSdFiles(self):
|
||||||
|
if not self._comm:
|
||||||
|
return
|
||||||
|
self._comm.refreshSdFiles()
|
||||||
|
|
||||||
|
#~~ callbacks triggered by sdFileStreamer
|
||||||
|
|
||||||
|
def _onSdFileStreamProgress(self, filename, progress):
|
||||||
|
self._stateMonitor.setSdUploadData({"filename": filename, "progress": progress})
|
||||||
|
|
||||||
|
def _onSdFileStreamFinish(self, filename):
|
||||||
|
self._setCurrentZ(None)
|
||||||
|
self._setProgressData(None, None, None, None)
|
||||||
|
self._sdStreamer = None
|
||||||
|
|
||||||
|
self._stateMonitor.setSdUploadData({"filename": None, "progress": None})
|
||||||
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
||||||
|
|
||||||
#~~ callbacks triggered by gcodeLoader
|
#~~ callbacks triggered by gcodeLoader
|
||||||
|
|
||||||
def _onGcodeLoadingProgress(self, filename, progress, mode):
|
def _onGcodeLoadingProgress(self, filename, progress, mode):
|
||||||
|
@ -430,7 +543,7 @@ class Printer():
|
||||||
def _onGcodeLoaded(self, filename, gcodeList):
|
def _onGcodeLoaded(self, filename, gcodeList):
|
||||||
self._setJobData(filename, gcodeList)
|
self._setJobData(filename, gcodeList)
|
||||||
self._setCurrentZ(None)
|
self._setCurrentZ(None)
|
||||||
self._setProgressData(None, None, None)
|
self._setProgressData(None, None, None, None)
|
||||||
self._gcodeLoader = None
|
self._gcodeLoader = None
|
||||||
self._stateMonitor.setGcodeData({"filename": None, "progress": None})
|
self._stateMonitor.setGcodeData({"filename": None, "progress": 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()})
|
||||||
|
@ -481,10 +594,10 @@ 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._gcodeList and len(self._gcodeList) > 0
|
return self._gcodeLoader is None and self._sdStreamer is None and ((self._gcodeList and len(self._gcodeList) > 0) or self._sdFile)
|
||||||
|
|
||||||
def isLoading(self):
|
def isLoading(self):
|
||||||
return self._gcodeLoader is not None
|
return self._gcodeLoader is not None or self._sdStreamer is not None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -507,7 +620,7 @@ class GcodeLoader(threading.Thread):
|
||||||
def run(self):
|
def run(self):
|
||||||
#Send an initial M110 to reset the line counter to zero.
|
#Send an initial M110 to reset the line counter to zero.
|
||||||
prevLineType = lineType = "CUSTOM"
|
prevLineType = lineType = "CUSTOM"
|
||||||
gcodeList = ["M110"]
|
gcodeList = ["M110 N0"]
|
||||||
filesize = os.stat(self._filename).st_size
|
filesize = os.stat(self._filename).st_size
|
||||||
with open(self._filename, "r") as file:
|
with open(self._filename, "r") as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
|
@ -533,6 +646,38 @@ class GcodeLoader(threading.Thread):
|
||||||
def _onParsingProgress(self, progress):
|
def _onParsingProgress(self, progress):
|
||||||
self._progressCallback(self._filename, progress, "parsing")
|
self._progressCallback(self._filename, progress, "parsing")
|
||||||
|
|
||||||
|
class SdFileStreamer(threading.Thread):
|
||||||
|
def __init__(self, comm, filename, file, progressCallback, finishCallback):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
self._comm = comm
|
||||||
|
self._filename = filename
|
||||||
|
self._file = file
|
||||||
|
self._progressCallback = progressCallback
|
||||||
|
self._finishCallback = finishCallback
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self._comm.isBusy():
|
||||||
|
return
|
||||||
|
|
||||||
|
name = self._filename[:self._filename.rfind(".")]
|
||||||
|
sdFilename = name[:8] + ".GCO"
|
||||||
|
try:
|
||||||
|
size = os.stat(self._file).st_size
|
||||||
|
with open(self._file, "r") as f:
|
||||||
|
self._comm.startSdFileTransfer(sdFilename)
|
||||||
|
for line in f:
|
||||||
|
if ";" in line:
|
||||||
|
line = line[0:line.find(";")]
|
||||||
|
line = line.strip()
|
||||||
|
if len(line) > 0:
|
||||||
|
self._comm.sendCommand(line)
|
||||||
|
time.sleep(0.001) # do not send too fast
|
||||||
|
self._progressCallback(sdFilename, float(f.tell()) / float(size))
|
||||||
|
finally:
|
||||||
|
self._comm.endSdFileTransfer(sdFilename)
|
||||||
|
self._finishCallback(sdFilename)
|
||||||
|
|
||||||
class StateMonitor(object):
|
class StateMonitor(object):
|
||||||
def __init__(self, ratelimit, updateCallback, addTemperatureCallback, addLogCallback, addMessageCallback):
|
def __init__(self, ratelimit, updateCallback, addTemperatureCallback, addLogCallback, addMessageCallback):
|
||||||
self._ratelimit = ratelimit
|
self._ratelimit = ratelimit
|
||||||
|
@ -544,6 +689,7 @@ class StateMonitor(object):
|
||||||
self._state = None
|
self._state = None
|
||||||
self._jobData = None
|
self._jobData = None
|
||||||
self._gcodeData = None
|
self._gcodeData = None
|
||||||
|
self._sdUploadData = None
|
||||||
self._currentZ = None
|
self._currentZ = None
|
||||||
self._peakZ = -1
|
self._peakZ = -1
|
||||||
self._progress = None
|
self._progress = None
|
||||||
|
@ -555,10 +701,11 @@ 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, progress=None, currentZ=None):
|
def reset(self, state=None, jobData=None, gcodeData=None, sdUploadData=None, progress=None, currentZ=None):
|
||||||
self.setState(state)
|
self.setState(state)
|
||||||
self.setJobData(jobData)
|
self.setJobData(jobData)
|
||||||
self.setGcodeData(gcodeData)
|
self.setGcodeData(gcodeData)
|
||||||
|
self.setSdUploadData(sdUploadData)
|
||||||
self.setProgress(progress)
|
self.setProgress(progress)
|
||||||
self.setCurrentZ(currentZ)
|
self.setCurrentZ(currentZ)
|
||||||
|
|
||||||
|
@ -590,6 +737,10 @@ class StateMonitor(object):
|
||||||
self._gcodeData = gcodeData
|
self._gcodeData = gcodeData
|
||||||
self._changeEvent.set()
|
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()
|
||||||
|
@ -614,6 +765,7 @@ class StateMonitor(object):
|
||||||
"state": self._state,
|
"state": self._state,
|
||||||
"job": self._jobData,
|
"job": self._jobData,
|
||||||
"gcode": self._gcodeData,
|
"gcode": self._gcodeData,
|
||||||
|
"sdUpload": self._sdUploadData,
|
||||||
"currentZ": self._currentZ,
|
"currentZ": self._currentZ,
|
||||||
"progress": self._progress
|
"progress": self._progress
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,8 @@ def index():
|
||||||
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
|
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
|
||||||
enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]),
|
enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]),
|
||||||
enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0,
|
enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0,
|
||||||
enableAccessControl=userManager is not None
|
enableAccessControl=userManager is not None,
|
||||||
|
enableSdSupport=settings().get(["feature", "sdSupport"])
|
||||||
)
|
)
|
||||||
|
|
||||||
#~~ Printer control
|
#~~ Printer control
|
||||||
|
@ -261,11 +262,40 @@ def getCustomControls():
|
||||||
customControls = settings().get(["controls"])
|
customControls = settings().get(["controls"])
|
||||||
return jsonify(controls=customControls)
|
return jsonify(controls=customControls)
|
||||||
|
|
||||||
|
@app.route(BASEURL + "control/sd", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def sdCommand():
|
||||||
|
if not settings().getBoolean(["feature", "sdSupport"]) or not printer.isOperational() or printer.isPrinting():
|
||||||
|
return jsonify(SUCCESS)
|
||||||
|
|
||||||
|
if "command" in request.values.keys():
|
||||||
|
command = request.values["command"]
|
||||||
|
if command == "init":
|
||||||
|
printer.initSdCard()
|
||||||
|
elif command == "refresh":
|
||||||
|
printer.refreshSdFiles()
|
||||||
|
elif command == "release":
|
||||||
|
printer.releaseSdCard()
|
||||||
|
|
||||||
|
return jsonify(SUCCESS)
|
||||||
|
|
||||||
#~~ GCODE file handling
|
#~~ GCODE file handling
|
||||||
|
|
||||||
@app.route(BASEURL + "gcodefiles", methods=["GET"])
|
@app.route(BASEURL + "gcodefiles", methods=["GET"])
|
||||||
def readGcodeFiles():
|
def readGcodeFiles():
|
||||||
return jsonify(files=gcodeManager.getAllFileData())
|
files = gcodeManager.getAllFileData()
|
||||||
|
|
||||||
|
sdFileList = printer.getSdFiles()
|
||||||
|
if sdFileList is not None:
|
||||||
|
for sdFile in sdFileList:
|
||||||
|
files.append({
|
||||||
|
"name": sdFile,
|
||||||
|
"size": "n/a",
|
||||||
|
"bytes": 0,
|
||||||
|
"date": "n/a",
|
||||||
|
"origin": "sd"
|
||||||
|
})
|
||||||
|
return jsonify(files=files)
|
||||||
|
|
||||||
@app.route(BASEURL + "gcodefiles/<path:filename>", methods=["GET"])
|
@app.route(BASEURL + "gcodefiles/<path:filename>", methods=["GET"])
|
||||||
def readGcodeFile(filename):
|
def readGcodeFile(filename):
|
||||||
|
@ -278,6 +308,8 @@ def uploadGcodeFile():
|
||||||
if "gcode_file" in request.files.keys():
|
if "gcode_file" in request.files.keys():
|
||||||
file = request.files["gcode_file"]
|
file = request.files["gcode_file"]
|
||||||
filename = gcodeManager.addFile(file)
|
filename = gcodeManager.addFile(file)
|
||||||
|
if filename and "target" in request.values.keys() and request.values["target"] == "sd":
|
||||||
|
printer.addSdFile(filename, gcodeManager.getAbsolutePath(filename))
|
||||||
|
|
||||||
global eventManager
|
global eventManager
|
||||||
eventManager.fire("Upload", filename)
|
eventManager.fire("Upload", filename)
|
||||||
|
@ -290,12 +322,17 @@ def loadGcodeFile():
|
||||||
printAfterLoading = False
|
printAfterLoading = False
|
||||||
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
|
||||||
filename = gcodeManager.getAbsolutePath(request.values["filename"])
|
|
||||||
if filename is not None:
|
|
||||||
printer.loadGcode(filename, printAfterLoading)
|
|
||||||
|
|
||||||
global eventManager
|
if "target" in request.values.keys() and request.values["target"] == "sd":
|
||||||
eventManager.fire("LoadStart", filename)
|
filename = request.values["filename"]
|
||||||
|
printer.selectSdFile(filename, printAfterLoading)
|
||||||
|
else:
|
||||||
|
filename = gcodeManager.getAbsolutePath(request.values["filename"])
|
||||||
|
if filename is not None:
|
||||||
|
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"])
|
||||||
|
@ -303,9 +340,17 @@ def loadGcodeFile():
|
||||||
def deleteGcodeFile():
|
def deleteGcodeFile():
|
||||||
if "filename" in request.values.keys():
|
if "filename" in request.values.keys():
|
||||||
filename = request.values["filename"]
|
filename = request.values["filename"]
|
||||||
gcodeManager.removeFile(filename)
|
if "target" in request.values.keys() and request.values["target"] == "sd":
|
||||||
|
printer.deleteSdFile(filename)
|
||||||
|
else:
|
||||||
|
gcodeManager.removeFile(filename)
|
||||||
return readGcodeFiles()
|
return readGcodeFiles()
|
||||||
|
|
||||||
|
@app.route(BASEURL + "gcodefiles/refresh", methods=["POST"])
|
||||||
|
def refreshFiles():
|
||||||
|
printer.updateSdFiles()
|
||||||
|
return jsonify(SUCCESS)
|
||||||
|
|
||||||
#~~ timelapse handling
|
#~~ timelapse handling
|
||||||
|
|
||||||
@app.route(BASEURL + "timelapse", methods=["GET"])
|
@app.route(BASEURL + "timelapse", methods=["GET"])
|
||||||
|
@ -394,7 +439,10 @@ def getSettings():
|
||||||
},
|
},
|
||||||
"feature": {
|
"feature": {
|
||||||
"gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]),
|
"gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]),
|
||||||
"waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"])
|
"waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]),
|
||||||
|
"alwaysSendChecksum": s.getBoolean(["feature", "alwaysSendChecksum"]),
|
||||||
|
"resetLineNumbersWithPrefixedN": s.getBoolean(["feature", "resetLineNumbersWithPrefixedN"]),
|
||||||
|
"sdSupport": s.getBoolean(["feature", "sdSupport"])
|
||||||
},
|
},
|
||||||
"folder": {
|
"folder": {
|
||||||
"uploads": s.getBaseFolder("uploads"),
|
"uploads": s.getBaseFolder("uploads"),
|
||||||
|
@ -439,6 +487,9 @@ def setSettings():
|
||||||
if "feature" in data.keys():
|
if "feature" in data.keys():
|
||||||
if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"])
|
if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"])
|
||||||
if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"])
|
if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"])
|
||||||
|
if "alwaysSendChecksum" in data["feature"].keys(): s.setBoolean(["feature", "alwaysSendChecksum"], data["feature"]["alwaysSendChecksum"])
|
||||||
|
if "resetLineNumbersWithPrefixedN" in data["feature"].keys(): s.setBoolean(["feature", "resetLineNumbersWithPrefixedN"], data["feature"]["resetLineNumbersWithPrefixedN"])
|
||||||
|
if "sdSupport" in data["feature"].keys(): s.setBoolean(["feature", "sdSupport"], data["feature"]["sdSupport"])
|
||||||
|
|
||||||
if "folder" in data.keys():
|
if "folder" in data.keys():
|
||||||
if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"])
|
if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"])
|
||||||
|
|
|
@ -39,13 +39,18 @@ default_settings = {
|
||||||
},
|
},
|
||||||
"feature": {
|
"feature": {
|
||||||
"gCodeVisualizer": True,
|
"gCodeVisualizer": True,
|
||||||
"waitForStartOnConnect": False
|
"waitForStartOnConnect": False,
|
||||||
|
"waitForWaitOnConnect": False,
|
||||||
|
"alwaysSendChecksum": False,
|
||||||
|
"resetLineNumbersWithPrefixedN": False,
|
||||||
|
"sdSupport": False
|
||||||
},
|
},
|
||||||
"folder": {
|
"folder": {
|
||||||
"uploads": None,
|
"uploads": None,
|
||||||
"timelapse": None,
|
"timelapse": None,
|
||||||
"timelapse_tmp": None,
|
"timelapse_tmp": None,
|
||||||
"logs": None
|
"logs": None,
|
||||||
|
"virtualSd": None
|
||||||
},
|
},
|
||||||
"temperature": {
|
"temperature": {
|
||||||
"profiles":
|
"profiles":
|
||||||
|
|
|
@ -111,10 +111,10 @@ body {
|
||||||
|
|
||||||
.octoprint-container {
|
.octoprint-container {
|
||||||
.accordion-heading {
|
.accordion-heading {
|
||||||
.settings-trigger {
|
.accordion-heading-button {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
.dropdown-toggle {
|
a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 8px 15px;
|
padding: 8px 15px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -145,6 +145,9 @@ body {
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upload-buttons .btn {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/** Tables */
|
/** Tables */
|
||||||
|
|
||||||
|
@ -399,3 +402,127 @@ ul.dropdown-menu li a {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#drop_overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 10000;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&.in {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#drop_overlay_background {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #000000;
|
||||||
|
filter:alpha(opacity=50);
|
||||||
|
-moz-opacity:0.5;
|
||||||
|
-khtml-opacity: 0.5;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#drop_overlay_wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding-top: 60px;
|
||||||
|
|
||||||
|
@dropzone_width: 400px;
|
||||||
|
@dropzone_height: 400px;
|
||||||
|
@dropzone_distance: 50px;
|
||||||
|
@dropzone_border: 2px;
|
||||||
|
|
||||||
|
#drop, #drop_background {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -1 * @dropzone_width / 2;
|
||||||
|
margin-top: -1 * @dropzone_height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#drop_locally, #drop_locally_background {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -1 * @dropzone_width - @dropzone_distance / 2;
|
||||||
|
margin-top: -1 * @dropzone_height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#drop_sd, #drop_sd_background {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: @dropzone_distance / 2;
|
||||||
|
margin-top: -1 * @dropzone_height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone {
|
||||||
|
width: @dropzone_width + 2 * @dropzone_border;
|
||||||
|
height: @dropzone_height + 2 * @dropzone_border;
|
||||||
|
z-index: 10001;
|
||||||
|
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 30px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
display: table-cell;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: @dropzone_width;
|
||||||
|
height: @dropzone_height;
|
||||||
|
line-height: 40px;
|
||||||
|
|
||||||
|
filter:alpha(opacity=100);
|
||||||
|
-moz-opacity:1.0;
|
||||||
|
-khtml-opacity: 1.0;
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone_background {
|
||||||
|
width: @dropzone_width;
|
||||||
|
height: @dropzone_height;
|
||||||
|
border: 2px dashed #eeeeee;
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
-moz-border-radius: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
background-color: #000000;
|
||||||
|
filter:alpha(opacity=25);
|
||||||
|
-moz-opacity:0.25;
|
||||||
|
-khtml-opacity: 0.25;
|
||||||
|
opacity: 0.25;
|
||||||
|
|
||||||
|
&.hover {
|
||||||
|
background-color: #000000;
|
||||||
|
filter:alpha(opacity=50);
|
||||||
|
-moz-opacity:0.5;
|
||||||
|
-khtml-opacity: 0.5;
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fade {
|
||||||
|
-webkit-transition: all 0.3s ease-out;
|
||||||
|
-moz-transition: all 0.3s ease-out;
|
||||||
|
-ms-transition: all 0.3s ease-out;
|
||||||
|
-o-transition: all 0.3s ease-out;
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -213,12 +213,14 @@ function PrinterStateViewModel(loginStateViewModel) {
|
||||||
self.isError = ko.observable(undefined);
|
self.isError = ko.observable(undefined);
|
||||||
self.isReady = ko.observable(undefined);
|
self.isReady = ko.observable(undefined);
|
||||||
self.isLoading = ko.observable(undefined);
|
self.isLoading = ko.observable(undefined);
|
||||||
|
self.isSdReady = ko.observable(undefined);
|
||||||
|
|
||||||
self.filename = ko.observable(undefined);
|
self.filename = ko.observable(undefined);
|
||||||
self.filament = ko.observable(undefined);
|
self.filament = ko.observable(undefined);
|
||||||
self.estimatedPrintTime = ko.observable(undefined);
|
self.estimatedPrintTime = 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.currentLine = ko.observable(undefined);
|
self.currentLine = ko.observable(undefined);
|
||||||
self.totalLines = ko.observable(undefined);
|
self.totalLines = ko.observable(undefined);
|
||||||
self.currentHeight = ko.observable(undefined);
|
self.currentHeight = ko.observable(undefined);
|
||||||
|
@ -229,10 +231,10 @@ function PrinterStateViewModel(loginStateViewModel) {
|
||||||
var currentLine = self.currentLine() ? self.currentLine() : "-";
|
var currentLine = self.currentLine() ? self.currentLine() : "-";
|
||||||
return currentLine + " / " + self.totalLines();
|
return currentLine + " / " + self.totalLines();
|
||||||
});
|
});
|
||||||
self.progress = ko.computed(function() {
|
self.progressString = ko.computed(function() {
|
||||||
if (!self.currentLine() || !self.totalLines())
|
if (!self.progress())
|
||||||
return 0;
|
return 0;
|
||||||
return Math.round(self.currentLine() * 100 / self.totalLines());
|
return self.progress();
|
||||||
});
|
});
|
||||||
self.pauseString = ko.computed(function() {
|
self.pauseString = ko.computed(function() {
|
||||||
if (self.isPaused())
|
if (self.isPaused())
|
||||||
|
@ -253,6 +255,7 @@ function PrinterStateViewModel(loginStateViewModel) {
|
||||||
self._processStateData(data.state)
|
self._processStateData(data.state)
|
||||||
self._processJobData(data.job);
|
self._processJobData(data.job);
|
||||||
self._processGcodeData(data.gcode);
|
self._processGcodeData(data.gcode);
|
||||||
|
self._processSdUploadData(data.sdUpload);
|
||||||
self._processProgressData(data.progress);
|
self._processProgressData(data.progress);
|
||||||
self._processZData(data.currentZ);
|
self._processZData(data.currentZ);
|
||||||
}
|
}
|
||||||
|
@ -266,6 +269,7 @@ function PrinterStateViewModel(loginStateViewModel) {
|
||||||
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.isLoading(data.flags.loading);
|
||||||
|
self.isSdReady(data.flags.sdReady);
|
||||||
}
|
}
|
||||||
|
|
||||||
self._processJobData = function(data) {
|
self._processJobData = function(data) {
|
||||||
|
@ -286,8 +290,20 @@ function PrinterStateViewModel(loginStateViewModel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
self.currentLine(data.progress);
|
if (data.progress) {
|
||||||
|
self.progress(Math.round(data.progress * 100));
|
||||||
|
} else {
|
||||||
|
self.progress(undefined);
|
||||||
|
}
|
||||||
|
self.currentLine(data.currentLine);
|
||||||
self.printTime(data.printTime);
|
self.printTime(data.printTime);
|
||||||
self.printTimeLeft(data.printTimeLeft);
|
self.printTimeLeft(data.printTimeLeft);
|
||||||
}
|
}
|
||||||
|
@ -742,6 +758,7 @@ function GcodeFilesViewModel(loginStateViewModel) {
|
||||||
self.isError = ko.observable(undefined);
|
self.isError = ko.observable(undefined);
|
||||||
self.isReady = ko.observable(undefined);
|
self.isReady = ko.observable(undefined);
|
||||||
self.isLoading = ko.observable(undefined);
|
self.isLoading = ko.observable(undefined);
|
||||||
|
self.isSdReady = ko.observable(undefined);
|
||||||
|
|
||||||
// initialize list helper
|
// initialize list helper
|
||||||
self.listHelper = new ItemListHelper(
|
self.listHelper = new ItemListHelper(
|
||||||
|
@ -754,14 +771,14 @@ function GcodeFilesViewModel(loginStateViewModel) {
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
"upload": function(a, b) {
|
"upload": function(a, b) {
|
||||||
// sorts descending
|
// sorts descending
|
||||||
if (a["date"] > b["date"]) return -1;
|
if (b["date"] === undefined || a["date"] > b["date"]) return -1;
|
||||||
if (a["date"] < b["date"]) return 1;
|
if (a["date"] < b["date"]) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
"size": function(a, b) {
|
"size": function(a, b) {
|
||||||
// sorts descending
|
// sorts descending
|
||||||
if (a["bytes"] > b["bytes"]) return -1;
|
if (b["bytes"] === undefined || a["bytes"] > b["bytes"]) return -1;
|
||||||
if (a["bytes"] < b["bytes"]) return 1;
|
if (a["bytes"] < b["bytes"]) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -769,10 +786,17 @@ function GcodeFilesViewModel(loginStateViewModel) {
|
||||||
{
|
{
|
||||||
"printed": function(file) {
|
"printed": function(file) {
|
||||||
return !(file["prints"] && file["prints"]["success"] && file["prints"]["success"] > 0);
|
return !(file["prints"] && file["prints"]["success"] && file["prints"]["success"] > 0);
|
||||||
|
},
|
||||||
|
"sd": function(file) {
|
||||||
|
return file["origin"] && file["origin"] == "sd";
|
||||||
|
},
|
||||||
|
"local": function(file) {
|
||||||
|
return !(file["origin"] && file["origin"] == "sd");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name",
|
"name",
|
||||||
[],
|
[],
|
||||||
|
[["sd", "local"]],
|
||||||
CONFIG_GCODEFILESPERPAGE
|
CONFIG_GCODEFILESPERPAGE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -800,6 +824,7 @@ function GcodeFilesViewModel(loginStateViewModel) {
|
||||||
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.isLoading(data.flags.loading);
|
||||||
|
self.isSdReady(data.flags.sdReady);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.requestData = function() {
|
self.requestData = function() {
|
||||||
|
@ -823,24 +848,51 @@ function GcodeFilesViewModel(loginStateViewModel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.loadFile = function(filename, printAfterLoad) {
|
self.loadFile = function(filename, printAfterLoad) {
|
||||||
|
var file = self.listHelper.getItem(function(item) {return item.name == filename});
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: AJAX_BASEURL + "gcodefiles/load",
|
url: AJAX_BASEURL + "gcodefiles/load",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: {filename: filename, print: printAfterLoad}
|
data: {filename: filename, print: printAfterLoad, target: file.origin}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
self.removeFile = function(filename) {
|
self.removeFile = function(filename) {
|
||||||
|
var file = self.listHelper.getItem(function(item) {return item.name == filename});
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: AJAX_BASEURL + "gcodefiles/delete",
|
url: AJAX_BASEURL + "gcodefiles/delete",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: {filename: filename},
|
data: {filename: filename, target: file.origin},
|
||||||
success: self.fromResponse
|
success: self.fromResponse
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.initSdCard = function() {
|
||||||
|
self._sendSdCommand("init");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.releaseSdCard = function() {
|
||||||
|
self._sendSdCommand("release");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.refreshSdFiles = function() {
|
||||||
|
self._sendSdCommand("refresh");
|
||||||
|
}
|
||||||
|
|
||||||
|
self._sendSdCommand = function(command) {
|
||||||
|
$.ajax({
|
||||||
|
url: AJAX_BASEURL + "control/sd",
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
data: {command: command}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
self.getPopoverContent = function(data) {
|
self.getPopoverContent = function(data) {
|
||||||
var output = "<p><strong>Uploaded:</strong> " + data["date"] + "</p>";
|
var output = "<p><strong>Uploaded:</strong> " + data["date"] + "</p>";
|
||||||
if (data["gcodeAnalysis"]) {
|
if (data["gcodeAnalysis"]) {
|
||||||
|
@ -917,6 +969,7 @@ function TimelapseViewModel(loginStateViewModel) {
|
||||||
},
|
},
|
||||||
"name",
|
"name",
|
||||||
[],
|
[],
|
||||||
|
[],
|
||||||
CONFIG_TIMELAPSEFILESPERPAGE
|
CONFIG_TIMELAPSEFILESPERPAGE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -996,13 +1049,15 @@ function GcodeViewModel(loginStateViewModel) {
|
||||||
self.status = 'idle';
|
self.status = 'idle';
|
||||||
self.enabled = false;
|
self.enabled = false;
|
||||||
|
|
||||||
|
self.errorCount = 0;
|
||||||
|
|
||||||
self.initialize = function(){
|
self.initialize = function(){
|
||||||
self.enabled = true;
|
self.enabled = true;
|
||||||
GCODE.ui.initHandlers();
|
GCODE.ui.initHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.loadFile = function(filename){
|
self.loadFile = function(filename){
|
||||||
if (self.status == 'idle') {
|
if (self.status == 'idle' && self.errorCount < 3) {
|
||||||
self.status = 'request';
|
self.status = 'request';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: AJAX_BASEURL + "gcodefiles/" + filename,
|
url: AJAX_BASEURL + "gcodefiles/" + filename,
|
||||||
|
@ -1016,6 +1071,7 @@ function GcodeViewModel(loginStateViewModel) {
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
self.status = 'idle';
|
self.status = 'idle';
|
||||||
|
self.errorCount++;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1045,6 +1101,7 @@ function GcodeViewModel(loginStateViewModel) {
|
||||||
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
|
||||||
} else if (data.job.filename) {
|
} else if (data.job.filename) {
|
||||||
self.loadFile(data.job.filename);
|
self.loadFile(data.job.filename);
|
||||||
}
|
}
|
||||||
|
@ -1198,7 +1255,7 @@ function UsersViewModel(loginStateViewModel) {
|
||||||
if (user === undefined) return;
|
if (user === undefined) return;
|
||||||
|
|
||||||
if (user.name == loginStateViewModel.username()) {
|
if (user.name == loginStateViewModel.username()) {
|
||||||
// we do not allow to delete ourself
|
// we do not allow to delete ourselves
|
||||||
$.pnotify({title: "Not possible", text: "You may not delete your own account.", type: "error"});
|
$.pnotify({title: "Not possible", text: "You may not delete your own account.", type: "error"});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1267,6 +1324,9 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
||||||
|
|
||||||
self.feature_gcodeViewer = ko.observable(undefined);
|
self.feature_gcodeViewer = ko.observable(undefined);
|
||||||
self.feature_waitForStart = ko.observable(undefined);
|
self.feature_waitForStart = ko.observable(undefined);
|
||||||
|
self.feature_alwaysSendChecksum = ko.observable(undefined);
|
||||||
|
self.feature_resetLineNumbersWithPrefixedN = ko.observable(undefined);
|
||||||
|
self.feature_sdSupport = ko.observable(undefined);
|
||||||
|
|
||||||
self.folder_uploads = ko.observable(undefined);
|
self.folder_uploads = ko.observable(undefined);
|
||||||
self.folder_timelapse = ko.observable(undefined);
|
self.folder_timelapse = ko.observable(undefined);
|
||||||
|
@ -1311,6 +1371,9 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
||||||
|
|
||||||
self.feature_gcodeViewer(response.feature.gcodeViewer);
|
self.feature_gcodeViewer(response.feature.gcodeViewer);
|
||||||
self.feature_waitForStart(response.feature.waitForStart);
|
self.feature_waitForStart(response.feature.waitForStart);
|
||||||
|
self.feature_alwaysSendChecksum(response.feature.alwaysSendChecksum);
|
||||||
|
self.feature_resetLineNumbersWithPrefixedN(response.feature.resetLineNumbersWithPrefixedN);
|
||||||
|
self.feature_sdSupport(response.feature.sdSupport);
|
||||||
|
|
||||||
self.folder_uploads(response.folder.uploads);
|
self.folder_uploads(response.folder.uploads);
|
||||||
self.folder_timelapse(response.folder.timelapse);
|
self.folder_timelapse(response.folder.timelapse);
|
||||||
|
@ -1343,7 +1406,11 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
||||||
},
|
},
|
||||||
"feature": {
|
"feature": {
|
||||||
"gcodeViewer": self.feature_gcodeViewer(),
|
"gcodeViewer": self.feature_gcodeViewer(),
|
||||||
"waitForStart": self.feature_waitForStart()
|
"waitForStart": self.feature_waitForStart(),
|
||||||
|
"alwaysSendChecksum": self.feature_alwaysSendChecksum(),
|
||||||
|
"resetLineNumbersWithPrefixedN": self.feature_resetLineNumbersWithPrefixedN(),
|
||||||
|
"waitForStart": self.feature_waitForStart(),
|
||||||
|
"sdSupport": self.feature_sdSupport()
|
||||||
},
|
},
|
||||||
"folder": {
|
"folder": {
|
||||||
"uploads": self.folder_uploads(),
|
"uploads": self.folder_uploads(),
|
||||||
|
@ -1427,6 +1494,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
|
||||||
self.timelapseViewModel.requestData();
|
self.timelapseViewModel.requestData();
|
||||||
$("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime());
|
$("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime());
|
||||||
self.loginStateViewModel.requestData();
|
self.loginStateViewModel.requestData();
|
||||||
|
self.gcodeFilesViewModel.requestData();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self._socket.on("disconnect", function() {
|
self._socket.on("disconnect", function() {
|
||||||
|
@ -1475,7 +1543,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSorting, defaultFilters, filesPerPage) {
|
function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSorting, defaultFilters, exclusiveFilters, filesPerPage) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.listType = listType;
|
self.listType = listType;
|
||||||
|
@ -1483,6 +1551,7 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
|
||||||
self.supportedFilters = supportedFilters;
|
self.supportedFilters = supportedFilters;
|
||||||
self.defaultSorting = defaultSorting;
|
self.defaultSorting = defaultSorting;
|
||||||
self.defaultFilters = defaultFilters;
|
self.defaultFilters = defaultFilters;
|
||||||
|
self.exclusiveFilters = exclusiveFilters;
|
||||||
|
|
||||||
self.allItems = [];
|
self.allItems = [];
|
||||||
self.items = ko.observableArray([]);
|
self.items = ko.observableArray([]);
|
||||||
|
@ -1574,6 +1643,17 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.getItem = function(matcher) {
|
||||||
|
var itemList = self.items();
|
||||||
|
for (var i = 0; i < itemList.length; i++) {
|
||||||
|
if (matcher(itemList[i])) {
|
||||||
|
return itemList[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
//~~ sorting
|
//~~ sorting
|
||||||
|
|
||||||
self.changeSorting = function(sorting) {
|
self.changeSorting = function(sorting) {
|
||||||
|
@ -1604,6 +1684,16 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
|
||||||
if (!_.contains(_.keys(self.supportedFilters), filter))
|
if (!_.contains(_.keys(self.supportedFilters), filter))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
for (var i = 0; i < self.exclusiveFilters.length; i++) {
|
||||||
|
if (_.contains(self.exclusiveFilters[i], filter)) {
|
||||||
|
for (var j = 0; j < self.exclusiveFilters[i].length; j++) {
|
||||||
|
if (self.exclusiveFilters[i][j] == filter)
|
||||||
|
continue;
|
||||||
|
self.removeFilter(self.exclusiveFilters[i][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var filters = self.currentFilters();
|
var filters = self.currentFilters();
|
||||||
filters.push(filter);
|
filters.push(filter);
|
||||||
self.currentFilters(filters);
|
self.currentFilters(filters);
|
||||||
|
@ -1613,7 +1703,7 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
|
||||||
}
|
}
|
||||||
|
|
||||||
self.removeFilter = function(filter) {
|
self.removeFilter = function(filter) {
|
||||||
if (filter != "printed")
|
if (!_.contains(_.keys(self.supportedFilters), filter))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var filters = self.currentFilters();
|
var filters = self.currentFilters();
|
||||||
|
@ -1765,8 +1855,6 @@ $(function() {
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
|
|
||||||
//~~ Print job control (should move to PrinterStateViewModel)
|
|
||||||
|
|
||||||
//~~ Temperature control (should really move to knockout click binding)
|
//~~ Temperature control (should really move to knockout click binding)
|
||||||
|
|
||||||
$("#temp_newTemp_set").click(function() {
|
$("#temp_newTemp_set").click(function() {
|
||||||
|
@ -1828,23 +1916,103 @@ $(function() {
|
||||||
|
|
||||||
//~~ Gcode upload
|
//~~ Gcode upload
|
||||||
|
|
||||||
|
function gcode_upload_done(e, data) {
|
||||||
|
gcodeFilesViewModel.fromResponse(data.result);
|
||||||
|
$("#gcode_upload_progress .bar").css("width", "0%");
|
||||||
|
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
|
||||||
|
$("#gcode_upload_progress .bar").text("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function gcode_upload_progress(e, data) {
|
||||||
|
var progress = parseInt(data.loaded / data.total * 100, 10);
|
||||||
|
$("#gcode_upload_progress .bar").css("width", progress + "%");
|
||||||
|
$("#gcode_upload_progress .bar").text("Uploading ...");
|
||||||
|
if (progress >= 100) {
|
||||||
|
$("#gcode_upload_progress").addClass("progress-striped").addClass("active");
|
||||||
|
$("#gcode_upload_progress .bar").text("Saving ...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var localTarget;
|
||||||
|
if (CONFIG_SD_SUPPORT) {
|
||||||
|
localTarget = $("#drop_locally");
|
||||||
|
} else {
|
||||||
|
localTarget = $("#drop");
|
||||||
|
}
|
||||||
|
|
||||||
$("#gcode_upload").fileupload({
|
$("#gcode_upload").fileupload({
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
done: function (e, data) {
|
dropZone: localTarget,
|
||||||
gcodeFilesViewModel.fromResponse(data.result);
|
formData: {target: "local"},
|
||||||
$("#gcode_upload_progress .bar").css("width", "0%");
|
done: gcode_upload_done,
|
||||||
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
|
progressall: gcode_upload_progress
|
||||||
$("#gcode_upload_progress .bar").text("");
|
});
|
||||||
},
|
|
||||||
progressall: function (e, data) {
|
if (CONFIG_SD_SUPPORT) {
|
||||||
var progress = parseInt(data.loaded / data.total * 100, 10);
|
$("#gcode_upload_sd").fileupload({
|
||||||
$("#gcode_upload_progress .bar").css("width", progress + "%");
|
dataType: "json",
|
||||||
$("#gcode_upload_progress .bar").text("Uploading ...");
|
dropZone: $("#drop_sd"),
|
||||||
if (progress >= 100) {
|
formData: {target: "sd"},
|
||||||
$("#gcode_upload_progress").addClass("progress-striped").addClass("active");
|
done: gcode_upload_done,
|
||||||
$("#gcode_upload_progress .bar").text("Saving ...");
|
progressall: gcode_upload_progress
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).bind("dragover", function (e) {
|
||||||
|
var dropOverlay = $("#drop_overlay");
|
||||||
|
var dropZone = $("#drop");
|
||||||
|
var dropZoneLocal = $("#drop_locally");
|
||||||
|
var dropZoneSd = $("#drop_sd");
|
||||||
|
var dropZoneBackground = $("#drop_background");
|
||||||
|
var dropZoneLocalBackground = $("#drop_locally_background");
|
||||||
|
var dropZoneSdBackground = $("#drop_sd_background");
|
||||||
|
var timeout = window.dropZoneTimeout;
|
||||||
|
|
||||||
|
if (!timeout) {
|
||||||
|
dropOverlay.addClass('in');
|
||||||
|
} else {
|
||||||
|
clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var foundLocal = false;
|
||||||
|
var foundSd = false;
|
||||||
|
var found = false
|
||||||
|
var node = e.target;
|
||||||
|
do {
|
||||||
|
if (dropZoneLocal && node === dropZoneLocal[0]) {
|
||||||
|
foundLocal = true;
|
||||||
|
break;
|
||||||
|
} else if (dropZoneSd && node === dropZoneSd[0]) {
|
||||||
|
foundSd = true;
|
||||||
|
break;
|
||||||
|
} else if (dropZone && node === dropZone[0]) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = node.parentNode;
|
||||||
|
} while (node != null);
|
||||||
|
|
||||||
|
if (foundLocal) {
|
||||||
|
dropZoneLocalBackground.addClass("hover");
|
||||||
|
dropZoneSdBackground.removeClass("hover");
|
||||||
|
} else if (foundSd) {
|
||||||
|
dropZoneSdBackground.addClass("hover");
|
||||||
|
dropZoneLocalBackground.removeClass("hover");
|
||||||
|
} else if (found) {
|
||||||
|
dropZoneBackground.addClass("hover");
|
||||||
|
} else {
|
||||||
|
if (dropZoneLocalBackground) dropZoneLocalBackground.removeClass("hover");
|
||||||
|
if (dropZoneSdBackground) dropZoneSdBackground.removeClass("hover");
|
||||||
|
if (dropZoneBackground) dropZoneBackground.removeClass("hover");
|
||||||
|
}
|
||||||
|
|
||||||
|
window.dropZoneTimeout = setTimeout(function () {
|
||||||
|
window.dropZoneTimeout = null;
|
||||||
|
dropOverlay.removeClass("in");
|
||||||
|
if (dropZoneLocal) dropZoneLocalBackground.removeClass("hover");
|
||||||
|
if (dropZoneSd) dropZoneSdBackground.removeClass("hover");
|
||||||
|
if (dropZone) dropZoneBackground.removeClass("hover");
|
||||||
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
//~~ Offline overlay
|
//~~ Offline overlay
|
||||||
|
@ -1908,7 +2076,7 @@ $(function() {
|
||||||
} else {
|
} else {
|
||||||
$("#gcode_upload").fileupload("disable");
|
$("#gcode_upload").fileupload("disable");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
//~~ UI stuff
|
//~~ UI stuff
|
||||||
|
|
||||||
|
@ -1930,10 +2098,13 @@ $(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix input element click problem on login dialog
|
// Fix input element click problem on login dialog
|
||||||
$('.dropdown input, .dropdown label').click(function(e) {
|
$(".dropdown input, .dropdown label").click(function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).bind("drop dragover", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="drop_overlay">
|
||||||
|
<div id="drop_overlay_background"></div>
|
||||||
|
<div id="drop_overlay_wrapper">
|
||||||
|
{% if enableSdSupport %}
|
||||||
|
<div class="dropzone" id="drop_locally"><span class="centered"><i class="icon-upload-alt"></i><br>Upload locally</span></div>
|
||||||
|
<div class="dropzone_background" id="drop_locally_background"></div>
|
||||||
|
<div class="dropzone" id="drop_sd"><span class="centered"><i class="icon-upload-alt"></i><br>Upload to SD</span></div>
|
||||||
|
<div class="dropzone_background" id="drop_sd_background"></div>
|
||||||
|
{% else %}
|
||||||
|
<div class="dropzone" id="drop"><span class="centered"><i class="icon-upload-alt"></i><br>Upload</span></div>
|
||||||
|
<div class="dropzone_background" id="drop_background"></div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="confirmation_dialog" class="modal hide fade">
|
<div id="confirmation_dialog" class="modal hide fade">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
var CONFIG_USERSPERPAGE = 10;
|
var CONFIG_USERSPERPAGE = 10;
|
||||||
var CONFIG_WEBCAM_STREAM = "{{ webcamStream }}";
|
var CONFIG_WEBCAM_STREAM = "{{ webcamStream }}";
|
||||||
var CONFIG_ACCESS_CONTROL = {% if enableAccessControl -%} true; {% else %} false; {%- endif %}
|
var CONFIG_ACCESS_CONTROL = {% if enableAccessControl -%} true; {% else %} false; {%- endif %}
|
||||||
|
var CONFIG_SD_SUPPORT = {% if enableSdSupport -%} true; {% else %} false; {%- endif %}
|
||||||
|
|
||||||
var WEB_SOCKET_SWF_LOCATION = "{{ url_for('static', filename='js/socket.io/WebSocketMain.swf') }}";
|
var WEB_SOCKET_SWF_LOCATION = "{{ url_for('static', filename='js/socket.io/WebSocketMain.swf') }}";
|
||||||
var WEB_SOCKET_DEBUG = true;
|
var WEB_SOCKET_DEBUG = true;
|
||||||
|
@ -116,7 +117,7 @@
|
||||||
Print Time Left: <strong data-bind="text: printTimeLeft"></strong><br>
|
Print Time Left: <strong data-bind="text: printTimeLeft"></strong><br>
|
||||||
|
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="bar" id="job_progressBar" data-bind="style: { width: progress() + '%' }"></div>
|
<div class="bar" id="job_progressBar" data-bind="style: { width: progressString() + '%' }"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row-fluid print-control" style="display: none;" data-bind="visible: loginState.isUser">
|
<div class="row-fluid print-control" style="display: none;" data-bind="visible: loginState.isUser">
|
||||||
|
@ -131,7 +132,7 @@
|
||||||
<div class="accordion-heading">
|
<div class="accordion-heading">
|
||||||
<a class="accordion-toggle" data-toggle="collapse" href="#files"><i class="icon-list"></i> Files</a>
|
<a class="accordion-toggle" data-toggle="collapse" href="#files"><i class="icon-list"></i> Files</a>
|
||||||
|
|
||||||
<div class="settings-trigger btn-group">
|
<div class="settings-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-wrench"></span>
|
<span class="icon-wrench"></span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -139,10 +140,28 @@
|
||||||
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('name'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> Sort by name (ascending)</a></li>
|
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('name'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> Sort by name (ascending)</a></li>
|
||||||
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('upload'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'upload' ? 'visible' : 'hidden'}"></i> Sort by upload date (descending)</a></li>
|
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('upload'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'upload' ? 'visible' : 'hidden'}"></i> Sort by upload date (descending)</a></li>
|
||||||
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('size'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> Sort by file size (descending)</a></li>
|
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('size'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> Sort by file size (descending)</a></li>
|
||||||
|
{% if enableSdSupport %}
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('local'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'local') ? 'visible' : 'hidden'}"></i> Only show files stored locally</a></li>
|
||||||
|
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('sd'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'sd') ? 'visible' : 'hidden'}"></i> Only show files stored on SD</a></li>
|
||||||
|
{% endif %}
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('printed'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'printed') ? 'visible' : 'hidden'}"></i> Hide successfully printed files</a></li>
|
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('printed'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'printed') ? 'visible' : 'hidden'}"></i> Hide successfully printed files</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if enableSdSupport %}
|
||||||
|
<div class="sd-trigger accordion-heading-button btn-group">
|
||||||
|
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
|
<span class="icon-hdd"></span>
|
||||||
|
</a>
|
||||||
|
<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.refreshSdFiles(); }"><i class="icon-refresh"></i> Refresh SD files</a></li>
|
||||||
|
<li data-bind="visible: isSdReady()"><a href="#" data-bind="click: function() { $root.releaseSdCard(); }"><i class="icon-eject"></i> Release SD card</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="accordion-body collapse in overflow_visible" id="files">
|
<div class="accordion-body collapse in overflow_visible" id="files">
|
||||||
<div class="accordion-inner">
|
<div class="accordion-inner">
|
||||||
|
@ -176,11 +195,26 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: none;" data-bind="visible: loginState.isUser">
|
<div style="display: none;" data-bind="visible: loginState.isUser">
|
||||||
<span class="btn btn-primary btn-block fileinput-button" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
|
<div class="row-fluid upload-buttons">
|
||||||
<i class="icon-upload icon-white"></i>
|
{% if enableSdSupport %}
|
||||||
<span>Upload</span>
|
<button class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
|
||||||
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
|
<i class="icon-upload-alt icon-white"></i>
|
||||||
</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()">
|
||||||
|
</button>
|
||||||
|
<button 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>
|
||||||
|
<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()">
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<button 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>
|
||||||
|
<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()">
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div id="gcode_upload_progress" class="progress" style="width: 100%;">
|
<div id="gcode_upload_progress" class="progress" style="width: 100%;">
|
||||||
<div class="bar" style="width: 0%"></div>
|
<div class="bar" style="width: 0%"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -103,7 +103,28 @@
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
<input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> Wait for start on connect
|
<input type="checkbox" data-bind="checked: feature_sdSupport" id="settings-featureSdSupport"> Enable SD support
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> Wait for <code>start</code> on connect
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" data-bind="checked: feature_alwaysSendChecksum" id="settings-featureAlwaysSendChecksum"> Send a checksum with <strong>every</strong> command <span class="label">Repetier</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" data-bind="checked: feature_resetLineNumbersWithPrefixedN" id="settings-resetLineNumbersWithPrefixedN"> Send M110 commands with target line number as N-prefix <span class="label">Repetier</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -53,9 +53,6 @@ class Timelapse(object):
|
||||||
def onPrintjobStopped(self):
|
def onPrintjobStopped(self):
|
||||||
self.stopTimelapse()
|
self.stopTimelapse()
|
||||||
|
|
||||||
def onPrintjobProgress(self, oldPos, newPos, percentage):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onZChange(self, oldZ, newZ):
|
def onZChange(self, oldZ, newZ):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -73,16 +73,38 @@ gcodeToEvent = {
|
||||||
|
|
||||||
class VirtualPrinter():
|
class VirtualPrinter():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.readList = ['start\n', 'Marlin: Virtual Marlin!\n', '\x80\n']
|
self.readList = ['start\n', 'Marlin: Virtual Marlin!\n', '\x80\n', 'SD init fail\n'] # no sd card as default startup scenario
|
||||||
self.temp = 0.0
|
self.temp = 0.0
|
||||||
self.targetTemp = 0.0
|
self.targetTemp = 0.0
|
||||||
self.lastTempAt = time.time()
|
self.lastTempAt = time.time()
|
||||||
self.bedTemp = 1.0
|
self.bedTemp = 1.0
|
||||||
self.bedTargetTemp = 1.0
|
self.bedTargetTemp = 1.0
|
||||||
|
|
||||||
|
self._virtualSd = settings().getBaseFolder("virtualSd")
|
||||||
|
self._sdCardReady = False
|
||||||
|
self._sdPrinter = None
|
||||||
|
self._sdPrintingSemaphore = threading.Event()
|
||||||
|
self._selectedSdFile = None
|
||||||
|
self._selectedSdFileSize = None
|
||||||
|
self._selectedSdFilePos = None
|
||||||
|
self._writingToSd = False
|
||||||
|
self._newSdFilePos = None
|
||||||
|
|
||||||
|
self.currentLine = 0
|
||||||
|
|
||||||
|
waitThread = threading.Thread(target=self._sendWaitAfterTimeout)
|
||||||
|
waitThread.start()
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if self.readList is None:
|
if self.readList is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# shortcut for writing to SD
|
||||||
|
if self._writingToSd and not self._selectedSdFile is None and not "M29" in data:
|
||||||
|
with open(self._selectedSdFile, "a") as f:
|
||||||
|
f.write(data)
|
||||||
|
return
|
||||||
|
|
||||||
#print "Send: %s" % (data.rstrip())
|
#print "Send: %s" % (data.rstrip())
|
||||||
if 'M104' in data or 'M109' in data:
|
if 'M104' in data or 'M109' in data:
|
||||||
try:
|
try:
|
||||||
|
@ -94,11 +116,155 @@ class VirtualPrinter():
|
||||||
self.bedTargetTemp = float(re.search('S([0-9]+)', data).group(1))
|
self.bedTargetTemp = float(re.search('S([0-9]+)', data).group(1))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if 'M105' in data:
|
if 'M105' in data:
|
||||||
|
# send simulated temperature data
|
||||||
self.readList.append("ok T:%.2f /%.2f B:%.2f /%.2f @:64\n" % (self.temp, self.targetTemp, self.bedTemp, self.bedTargetTemp))
|
self.readList.append("ok T:%.2f /%.2f B:%.2f /%.2f @:64\n" % (self.temp, self.targetTemp, self.bedTemp, self.bedTargetTemp))
|
||||||
|
elif 'M20' in data:
|
||||||
|
if self._sdCardReady:
|
||||||
|
self._listSd()
|
||||||
|
elif 'M21' in data:
|
||||||
|
self._sdCardReady = True
|
||||||
|
self.readList.append("SD card ok")
|
||||||
|
elif 'M22' in data:
|
||||||
|
self._sdCardReady = False
|
||||||
|
elif 'M23' in data:
|
||||||
|
if self._sdCardReady:
|
||||||
|
filename = data.split(None, 1)[1].strip()
|
||||||
|
self._selectSdFile(filename)
|
||||||
|
elif 'M24' in data:
|
||||||
|
if self._sdCardReady:
|
||||||
|
self._startSdPrint()
|
||||||
|
elif 'M25' in data:
|
||||||
|
if self._sdCardReady:
|
||||||
|
self._pauseSdPrint()
|
||||||
|
elif 'M26' in data:
|
||||||
|
if self._sdCardReady:
|
||||||
|
pos = int(re.search("S([0-9]+)", data).group(1))
|
||||||
|
self._setSdPos(pos)
|
||||||
|
elif 'M27' in data:
|
||||||
|
if self._sdCardReady:
|
||||||
|
self._reportSdStatus()
|
||||||
|
elif 'M28' in data:
|
||||||
|
if self._sdCardReady:
|
||||||
|
filename = data.split(None, 1)[1].strip()
|
||||||
|
self._writeSdFile(filename)
|
||||||
|
elif 'M29' in data:
|
||||||
|
if self._sdCardReady:
|
||||||
|
self._finishSdFile()
|
||||||
|
elif 'M30' in data:
|
||||||
|
if self._sdCardReady:
|
||||||
|
filename = data.split(None, 1)[1].strip()
|
||||||
|
self._deleteSdFile(filename)
|
||||||
|
elif "M110" in data:
|
||||||
|
# reset current line
|
||||||
|
self.currentLine = int(re.search('N([0-9]+)', data).group(1))
|
||||||
|
self.readList.append("ok\n")
|
||||||
|
elif self.currentLine == 100:
|
||||||
|
# simulate a resend at line 100 of the last 5 lines
|
||||||
|
self.readList.append("Error: Line Number is not Last Line Number\n")
|
||||||
|
self.readList.append("rs %d\n" % (self.currentLine - 5))
|
||||||
elif len(data.strip()) > 0:
|
elif len(data.strip()) > 0:
|
||||||
self.readList.append("ok\n")
|
self.readList.append("ok\n")
|
||||||
|
|
||||||
|
if "*" in data:
|
||||||
|
self.currentLine += 1
|
||||||
|
|
||||||
|
def _listSd(self):
|
||||||
|
self.readList.append("Begin file list")
|
||||||
|
for osFile in os.listdir(self._virtualSd):
|
||||||
|
self.readList.append(osFile.upper())
|
||||||
|
self.readList.append("End file list")
|
||||||
|
self.readList.append("ok")
|
||||||
|
|
||||||
|
def _selectSdFile(self, filename):
|
||||||
|
file = os.path.join(self._virtualSd, filename).lower()
|
||||||
|
if not os.path.exists(file) or not os.path.isfile(file):
|
||||||
|
self.readList.append("open failed, File: %s." % filename)
|
||||||
|
else:
|
||||||
|
self._selectedSdFile = file
|
||||||
|
self._selectedSdFileSize = os.stat(file).st_size
|
||||||
|
self.readList.append("File opened: %s Size: %d" % (filename, self._selectedSdFileSize))
|
||||||
|
self.readList.append("File selected")
|
||||||
|
|
||||||
|
def _startSdPrint(self):
|
||||||
|
if self._selectedSdFile is not None:
|
||||||
|
if self._sdPrinter is None:
|
||||||
|
self._sdPrinter = threading.Thread(target=self._sdPrintingWorker)
|
||||||
|
self._sdPrinter.start()
|
||||||
|
self._sdPrintingSemaphore.set()
|
||||||
|
self.readList.append("ok")
|
||||||
|
|
||||||
|
def _pauseSdPrint(self):
|
||||||
|
self._sdPrintingSemaphore.clear()
|
||||||
|
self.readList.append("ok")
|
||||||
|
|
||||||
|
def _setSdPos(self, pos):
|
||||||
|
self._newSdFilePos = pos
|
||||||
|
|
||||||
|
def _reportSdStatus(self):
|
||||||
|
if self._sdPrinter is not None and self._sdPrintingSemaphore.is_set:
|
||||||
|
self.readList.append("SD printing byte %d/%d" % (self._selectedSdFilePos, self._selectedSdFileSize))
|
||||||
|
else:
|
||||||
|
self.readList.append("Not SD printing")
|
||||||
|
|
||||||
|
def _writeSdFile(self, filename):
|
||||||
|
file = os.path.join(self._virtualSd, filename).lower()
|
||||||
|
if os.path.exists(file):
|
||||||
|
if os.path.isfile(file):
|
||||||
|
os.remove(file)
|
||||||
|
else:
|
||||||
|
self.readList.append("error writing to file")
|
||||||
|
|
||||||
|
self._writingToSd = True
|
||||||
|
self._selectedSdFile = file
|
||||||
|
self.readList.append("ok")
|
||||||
|
|
||||||
|
def _finishSdFile(self):
|
||||||
|
self._writingToSd = False
|
||||||
|
self._selectedSdFile = None
|
||||||
|
self.readList.append("ok")
|
||||||
|
|
||||||
|
def _sdPrintingWorker(self):
|
||||||
|
self._selectedSdFilePos = 0
|
||||||
|
with open(self._selectedSdFile, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
# reset position if requested by client
|
||||||
|
if self._newSdFilePos is not None:
|
||||||
|
f.seek(self._newSdFilePos)
|
||||||
|
self._newSdFilePos = None
|
||||||
|
|
||||||
|
# read current file position
|
||||||
|
self._selectedSdFilePos = f.tell()
|
||||||
|
|
||||||
|
# if we are paused, wait for unpausing
|
||||||
|
self._sdPrintingSemaphore.wait()
|
||||||
|
|
||||||
|
# set target temps
|
||||||
|
if 'M104' in line or 'M109' in line:
|
||||||
|
try:
|
||||||
|
self.targetTemp = float(re.search('S([0-9]+)', line).group(1))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if 'M140' in line or 'M190' in line:
|
||||||
|
try:
|
||||||
|
self.bedTargetTemp = float(re.search('S([0-9]+)', line).group(1))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
self._sdPrintingSemaphore.clear()
|
||||||
|
self._selectedSdFilePos = 0
|
||||||
|
self._sdPrinter = None
|
||||||
|
self.readList.append("Done printing file")
|
||||||
|
|
||||||
|
def _deleteSdFile(self, filename):
|
||||||
|
file = os.path.join(self._virtualSd, filename)
|
||||||
|
if os.path.exists(file) and os.path.isfile(file):
|
||||||
|
os.remove(file)
|
||||||
|
self.readList.append("ok")
|
||||||
|
|
||||||
def readline(self):
|
def readline(self):
|
||||||
if self.readList is None:
|
if self.readList is None:
|
||||||
return ''
|
return ''
|
||||||
|
@ -121,12 +287,15 @@ class VirtualPrinter():
|
||||||
if self.readList is None:
|
if self.readList is None:
|
||||||
return ''
|
return ''
|
||||||
time.sleep(0.001)
|
time.sleep(0.001)
|
||||||
#print "Recv: %s" % (self.readList[0].rstrip())
|
|
||||||
return self.readList.pop(0)
|
return self.readList.pop(0)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.readList = None
|
self.readList = None
|
||||||
|
|
||||||
|
def _sendWaitAfterTimeout(self, timeout=5):
|
||||||
|
time.sleep(timeout)
|
||||||
|
self.readList.append("wait")
|
||||||
|
|
||||||
class MachineComPrintCallback(object):
|
class MachineComPrintCallback(object):
|
||||||
def mcLog(self, message):
|
def mcLog(self, message):
|
||||||
pass
|
pass
|
||||||
|
@ -140,12 +309,24 @@ class MachineComPrintCallback(object):
|
||||||
def mcMessage(self, message):
|
def mcMessage(self, message):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcProgress(self, lineNr):
|
def mcProgress(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mcZChange(self, newZ):
|
def mcZChange(self, newZ):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def mcSdStateChange(self, sdReady):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def mcSdFiles(self, files):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def mcSdSelected(self, filename, size):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def mcSdPrintingDone(self):
|
||||||
|
pass
|
||||||
|
|
||||||
class MachineCom(object):
|
class MachineCom(object):
|
||||||
STATE_NONE = 0
|
STATE_NONE = 0
|
||||||
STATE_OPEN_SERIAL = 1
|
STATE_OPEN_SERIAL = 1
|
||||||
|
@ -158,6 +339,7 @@ class MachineCom(object):
|
||||||
STATE_CLOSED = 8
|
STATE_CLOSED = 8
|
||||||
STATE_ERROR = 9
|
STATE_ERROR = 9
|
||||||
STATE_CLOSED_WITH_ERROR = 10
|
STATE_CLOSED_WITH_ERROR = 10
|
||||||
|
STATE_RECEIVING_FILE = 11
|
||||||
|
|
||||||
def __init__(self, port = None, baudrate = None, callbackObject = None):
|
def __init__(self, port = None, baudrate = None, callbackObject = None):
|
||||||
self._logger = logging.getLogger(__name__)
|
self._logger = logging.getLogger(__name__)
|
||||||
|
@ -193,15 +375,42 @@ class MachineCom(object):
|
||||||
self._currentZ = -1
|
self._currentZ = -1
|
||||||
self._heatupWaitStartTime = 0
|
self._heatupWaitStartTime = 0
|
||||||
self._heatupWaitTimeLost = 0.0
|
self._heatupWaitTimeLost = 0.0
|
||||||
self._printStartTime100 = None
|
self._printStartTime = None
|
||||||
|
|
||||||
|
self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"])
|
||||||
|
self._currentLine = 1
|
||||||
|
self._resendDelta = None
|
||||||
|
self._lastLines = []
|
||||||
|
|
||||||
|
self._sendNextLock = threading.Lock()
|
||||||
|
self._sendingLock = threading.Lock()
|
||||||
|
|
||||||
self.thread = threading.Thread(target=self._monitor)
|
self.thread = threading.Thread(target=self._monitor)
|
||||||
self.thread.daemon = True
|
self.thread.daemon = True
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
|
self._sdAvailable = False
|
||||||
|
self._sdPrinting = False
|
||||||
|
self._sdFileList = False
|
||||||
|
self._sdFile = None
|
||||||
|
self._sdFilePos = None
|
||||||
|
self._sdFileSize = None
|
||||||
|
self._sdFiles = []
|
||||||
|
|
||||||
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 settings().get(["feature", "sdSupport"]):
|
||||||
|
self._sdPrinting = False
|
||||||
|
self._sdFileList = False
|
||||||
|
self._sdFile = None
|
||||||
|
self._sdFilePos = None
|
||||||
|
self._sdFileSize = None
|
||||||
|
self._sdFiles = []
|
||||||
|
self._callback.mcSdFiles([])
|
||||||
|
|
||||||
oldState = self.getStateString()
|
oldState = self.getStateString()
|
||||||
self._state = newState
|
self._state = newState
|
||||||
self._log('Changing monitoring state from \'%s\' to \'%s\'' % (oldState, self.getStateString()))
|
self._log('Changing monitoring state from \'%s\' to \'%s\'' % (oldState, self.getStateString()))
|
||||||
|
@ -224,7 +433,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:
|
||||||
return "Printing"
|
if self._sdPrinting:
|
||||||
|
return "Printing from SD"
|
||||||
|
else:
|
||||||
|
return "Printing"
|
||||||
if self._state == self.STATE_PAUSED:
|
if self._state == self.STATE_PAUSED:
|
||||||
return "Paused"
|
return "Paused"
|
||||||
if self._state == self.STATE_CLOSED:
|
if self._state == self.STATE_CLOSED:
|
||||||
|
@ -233,6 +445,8 @@ class MachineCom(object):
|
||||||
return "Error: %s" % (self.getShortErrorString())
|
return "Error: %s" % (self.getShortErrorString())
|
||||||
if self._state == self.STATE_CLOSED_WITH_ERROR:
|
if self._state == self.STATE_CLOSED_WITH_ERROR:
|
||||||
return "Error: %s" % (self.getShortErrorString())
|
return "Error: %s" % (self.getShortErrorString())
|
||||||
|
if self._state == self.STATE_RECEIVING_FILE:
|
||||||
|
return "Sending file to SD"
|
||||||
return "?%d?" % (self._state)
|
return "?%d?" % (self._state)
|
||||||
|
|
||||||
def getShortErrorString(self):
|
def getShortErrorString(self):
|
||||||
|
@ -250,31 +464,61 @@ class MachineCom(object):
|
||||||
return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR
|
return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR
|
||||||
|
|
||||||
def isOperational(self):
|
def isOperational(self):
|
||||||
return self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PRINTING or self._state == self.STATE_PAUSED
|
return self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PRINTING or self._state == self.STATE_PAUSED or self._state == self.STATE_RECEIVING_FILE
|
||||||
|
|
||||||
def isPrinting(self):
|
def isPrinting(self):
|
||||||
return self._state == self.STATE_PRINTING
|
return self._state == self.STATE_PRINTING
|
||||||
|
|
||||||
|
def isSdPrinting(self):
|
||||||
|
return self._sdPrinting
|
||||||
|
|
||||||
def isPaused(self):
|
def isPaused(self):
|
||||||
return self._state == self.STATE_PAUSED
|
return self._state == self.STATE_PAUSED
|
||||||
|
|
||||||
|
def isBusy(self):
|
||||||
|
return self.isPrinting() or self._state == self.STATE_RECEIVING_FILE
|
||||||
|
|
||||||
|
def isSdReady(self):
|
||||||
|
return self._sdAvailable
|
||||||
|
|
||||||
def getPrintPos(self):
|
def getPrintPos(self):
|
||||||
return self._gcodePos
|
if self._sdPrinting:
|
||||||
|
return self._sdFilePos
|
||||||
|
else:
|
||||||
|
return self._gcodePos
|
||||||
|
|
||||||
def getPrintTime(self):
|
def getPrintTime(self):
|
||||||
if self._printStartTime100 == None:
|
if self._printStartTime == None:
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return time.time() - self._printStartTime100
|
return time.time() - self._printStartTime
|
||||||
|
|
||||||
def getPrintTimeRemainingEstimate(self):
|
def getPrintTimeRemainingEstimate(self):
|
||||||
if self._printStartTime100 == None or self.getPrintPos() < 200:
|
if self._printStartTime == None:
|
||||||
return None
|
return None
|
||||||
printTime = (time.time() - self._printStartTime100) / 60
|
|
||||||
printTimeTotal = printTime * (len(self._gcodeList) - 100) / (self.getPrintPos() - 100)
|
if self._sdPrinting:
|
||||||
printTimeLeft = printTimeTotal - printTime
|
printTime = (time.time() - self._printStartTime) / 60
|
||||||
return printTimeLeft
|
if self._sdFilePos > 0:
|
||||||
|
printTimeTotal = printTime * self._sdFileSize / self._sdFilePos
|
||||||
|
else:
|
||||||
|
printTimeTotal = printTime * self._sdFileSize
|
||||||
|
printTimeLeft = printTimeTotal - printTime
|
||||||
|
self._logger.info("printTime: %f, sdFileSize: %f, sdFilePos: %f, printTimeTotal: %f, printTimeLeft: %f" % (printTime, self._sdFileSize, self._sdFilePos, printTimeTotal, printTimeLeft))
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
@ -335,13 +579,15 @@ class MachineCom(object):
|
||||||
#Start monitoring the serial port.
|
#Start monitoring the serial port.
|
||||||
timeout = time.time() + 5
|
timeout = time.time() + 5
|
||||||
tempRequestTimeout = timeout
|
tempRequestTimeout = timeout
|
||||||
|
sdStatusRequestTimeout = timeout
|
||||||
startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"])
|
startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"])
|
||||||
while True:
|
while True:
|
||||||
line = self._readline()
|
line = self._readline()
|
||||||
if line == None:
|
if line == None:
|
||||||
break
|
break
|
||||||
|
|
||||||
#No matter the state, if we see an error, goto the error state and store the error for reference.
|
##~~ Error handling
|
||||||
|
# No matter the state, if we see an error, goto the error state and store the error for reference.
|
||||||
if line.startswith('Error:'):
|
if line.startswith('Error:'):
|
||||||
#Oh YEAH, consistency.
|
#Oh YEAH, consistency.
|
||||||
# Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n"
|
# Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n"
|
||||||
|
@ -350,7 +596,13 @@ class MachineCom(object):
|
||||||
if re.match('Error:[0-9]\n', line):
|
if re.match('Error:[0-9]\n', line):
|
||||||
line = line.rstrip() + self._readline()
|
line = line.rstrip() + self._readline()
|
||||||
#Skip the communication errors, as those get corrected.
|
#Skip the communication errors, as those get corrected.
|
||||||
if 'checksum mismatch' in line or 'Line Number is not Last Line Number' in line or 'No Line Number with checksum' in line or 'No Checksum with line number' in line:
|
if 'checksum mismatch' in line \
|
||||||
|
or 'Wrong checksum' in line \
|
||||||
|
or 'Line Number is not Last Line Number' in line \
|
||||||
|
or 'expected line' in line \
|
||||||
|
or 'No Line Number with checksum' in line \
|
||||||
|
or 'No Checksum with line number' in line \
|
||||||
|
or 'Missing checksum' in line:
|
||||||
pass
|
pass
|
||||||
elif not self.isError():
|
elif not self.isError():
|
||||||
if self.isPrinting():
|
if self.isPrinting():
|
||||||
|
@ -358,20 +610,74 @@ class MachineCom(object):
|
||||||
|
|
||||||
self._errorValue = line[6:]
|
self._errorValue = line[6:]
|
||||||
self._changeState(self.STATE_ERROR)
|
self._changeState(self.STATE_ERROR)
|
||||||
|
|
||||||
eventManager().fire("Error", self.getErrorString())
|
eventManager().fire("Error", self.getErrorString())
|
||||||
|
|
||||||
|
##~~ 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 self._sdFileList and not 'End file list' in line:
|
||||||
|
self._sdFiles.append(line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
##~~ Temperature processing
|
||||||
if ' T:' in line or line.startswith('T:'):
|
if ' T:' in line or line.startswith('T:'):
|
||||||
self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0))
|
try:
|
||||||
if ' B:' in line:
|
self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0))
|
||||||
self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0))
|
if ' B:' in line:
|
||||||
self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp)
|
self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0))
|
||||||
|
|
||||||
|
self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp)
|
||||||
|
except ValueError:
|
||||||
|
# catch conversion issues, we'll rather just not get the temperature update instead of killing the connection
|
||||||
|
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 and 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
|
||||||
elif line.strip() != '' and line.strip() != 'ok' and not line.startswith('Resend:') and line != 'echo:Unknown command:""\n' and self.isOperational():
|
|
||||||
|
##~~ SD Card handling
|
||||||
|
elif 'SD init fail' in line:
|
||||||
|
self._sdAvailable = False
|
||||||
|
self._sdFiles = []
|
||||||
|
self._callback.mcSdStateChange(self._sdAvailable)
|
||||||
|
elif 'SD card ok' in line:
|
||||||
|
self._sdAvailable = True
|
||||||
|
self.refreshSdFiles()
|
||||||
|
self._callback.mcSdStateChange(self._sdAvailable)
|
||||||
|
elif 'Begin file list' in line:
|
||||||
|
self._sdFiles = []
|
||||||
|
self._sdFileList = True
|
||||||
|
elif 'End file list' in line:
|
||||||
|
self._sdFileList = False
|
||||||
|
self._callback.mcSdFiles(self._sdFiles)
|
||||||
|
elif 'SD printing byte' in line:
|
||||||
|
# answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d"
|
||||||
|
match = re.search("([0-9]*)/([0-9]*)", line)
|
||||||
|
self._sdFilePos = int(match.group(1))
|
||||||
|
self._sdFileSize = int(match.group(2))
|
||||||
|
self._callback.mcProgress()
|
||||||
|
elif 'File opened' in line:
|
||||||
|
# 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)
|
||||||
|
self._sdFile = match.group(1)
|
||||||
|
self._sdFileSize = int(match.group(2))
|
||||||
|
elif 'File selected' in line:
|
||||||
|
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
|
||||||
|
self._callback.mcSdSelected(self._sdFile, self._sdFileSize)
|
||||||
|
elif 'Done printing file' in line:
|
||||||
|
# printer is reporting file finished printing
|
||||||
|
self._sdPrinting = False
|
||||||
|
self._sdFilePos = 0
|
||||||
|
self._changeState(self.STATE_OPERATIONAL)
|
||||||
|
self._callback.mcSdPrintingDone()
|
||||||
|
|
||||||
|
##~~ 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():
|
||||||
self._callback.mcMessage(line)
|
self._callback.mcMessage(line)
|
||||||
|
|
||||||
|
### 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:
|
||||||
if len(self._baudrateDetectList) < 1:
|
if len(self._baudrateDetectList) < 1:
|
||||||
|
@ -411,43 +717,86 @@ class MachineCom(object):
|
||||||
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
|
||||||
|
|
||||||
|
### Connection attempt
|
||||||
elif self._state == self.STATE_CONNECTING:
|
elif self._state == self.STATE_CONNECTING:
|
||||||
if line == '' and startSeen:
|
if (line == "" or "wait" in line) and startSeen:
|
||||||
self._sendCommand("M105")
|
self._sendCommand("M105")
|
||||||
elif 'start' in line:
|
elif "start" in line:
|
||||||
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)
|
||||||
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()
|
||||||
|
|
||||||
|
### Operational
|
||||||
elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED:
|
elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED:
|
||||||
#Request the temperature on comm timeout (every 5 seconds) when we are not printing.
|
#Request the temperature on comm timeout (every 5 seconds) when we are not printing.
|
||||||
if line == '':
|
if line == "" or "wait" in line:
|
||||||
self._sendCommand("M105")
|
if self._resendDelta is not None:
|
||||||
tempRequestTimeout = time.time() + 5
|
self._resendNextCommand()
|
||||||
elif self._state == self.STATE_PRINTING:
|
elif not self._commandQueue.empty():
|
||||||
if line == '' and time.time() > timeout:
|
|
||||||
self._log("Communication timeout during printing, forcing a line")
|
|
||||||
line = 'ok'
|
|
||||||
#Even when printing request the temperture every 5 seconds.
|
|
||||||
if time.time() > tempRequestTimeout:
|
|
||||||
self._commandQueue.put("M105")
|
|
||||||
tempRequestTimeout = time.time() + 5
|
|
||||||
if 'ok' in line:
|
|
||||||
timeout = time.time() + 5
|
|
||||||
if not self._commandQueue.empty():
|
|
||||||
self._sendCommand(self._commandQueue.get())
|
self._sendCommand(self._commandQueue.get())
|
||||||
else:
|
else:
|
||||||
self._sendNext()
|
self._sendCommand("M105")
|
||||||
|
tempRequestTimeout = time.time() + 5
|
||||||
|
# resend -> start resend procedure from requested line
|
||||||
elif "resend" in line.lower() or "rs" in line:
|
elif "resend" in line.lower() or "rs" in line:
|
||||||
try:
|
self._handleResendRequest(line)
|
||||||
self._gcodePos = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])
|
|
||||||
except:
|
### Printing
|
||||||
if "rs" in line:
|
elif self._state == self.STATE_PRINTING:
|
||||||
self._gcodePos = int(line.split()[1])
|
if line == "" and time.time() > timeout:
|
||||||
|
self._log("Communication timeout during printing, forcing a line")
|
||||||
|
line = 'ok'
|
||||||
|
|
||||||
|
if self._sdPrinting:
|
||||||
|
if time.time() > tempRequestTimeout:
|
||||||
|
self._sendCommand("M105")
|
||||||
|
tempRequestTimeout = time.time() + 5
|
||||||
|
|
||||||
|
if time.time() > sdStatusRequestTimeout:
|
||||||
|
self._sendCommand("M27")
|
||||||
|
sdStatusRequestTimeout = time.time() + 1
|
||||||
|
|
||||||
|
if 'ok' or 'SD printing byte' in line:
|
||||||
|
timeout = time.time() + 5
|
||||||
|
else:
|
||||||
|
# Even when printing request the temperature every 5 seconds.
|
||||||
|
if time.time() > tempRequestTimeout:
|
||||||
|
self._commandQueue.put("M105")
|
||||||
|
tempRequestTimeout = time.time() + 5
|
||||||
|
|
||||||
|
if 'ok' in line:
|
||||||
|
timeout = time.time() + 5
|
||||||
|
if self._resendDelta is not None:
|
||||||
|
self._resendNextCommand()
|
||||||
|
elif not self._commandQueue.empty():
|
||||||
|
self._sendCommand(self._commandQueue.get())
|
||||||
|
else:
|
||||||
|
self._sendNext()
|
||||||
|
elif "resend" in line.lower() or "rs" in line:
|
||||||
|
self._handleResendRequest(line)
|
||||||
self._log("Connection closed, closing down monitor")
|
self._log("Connection closed, closing down monitor")
|
||||||
|
|
||||||
|
def _handleResendRequest(self, line):
|
||||||
|
lineToResend = None
|
||||||
|
try:
|
||||||
|
lineToResend = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])
|
||||||
|
except:
|
||||||
|
if "rs" in line:
|
||||||
|
lineToResend = int(line.split()[1])
|
||||||
|
|
||||||
|
if lineToResend is not None:
|
||||||
|
self._resendDelta = self._currentLine - lineToResend
|
||||||
|
if self._resendDelta > len(self._lastLines):
|
||||||
|
self._errorValue = "Printer requested line %d but history is only available up to line %d" % (lineToResend, self._currentLine - len(self._lastLines))
|
||||||
|
self._changeState(self.STATE_ERROR)
|
||||||
|
self._logger.warn(self._errorValue)
|
||||||
|
else:
|
||||||
|
self._resendNextCommand()
|
||||||
|
|
||||||
def _log(self, message):
|
def _log(self, message):
|
||||||
self._callback.mcLog(message)
|
self._callback.mcLog(message)
|
||||||
self._serialLogger.debug(message)
|
self._serialLogger.debug(message)
|
||||||
|
@ -487,34 +836,105 @@ class MachineCom(object):
|
||||||
self._changeState(self.STATE_CLOSED)
|
self._changeState(self.STATE_CLOSED)
|
||||||
self._serial = None
|
self._serial = None
|
||||||
|
|
||||||
|
if settings().get(["feature", "sdSupport"]):
|
||||||
|
self._sdFileList = []
|
||||||
|
|
||||||
if printing:
|
if printing:
|
||||||
eventManager().fire("PrintFailed")
|
eventManager().fire("PrintFailed")
|
||||||
eventManager().fire("Disconnected")
|
eventManager().fire("Disconnected")
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def _sendCommand(self, cmd):
|
|
||||||
if self._serial is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
for gcode in gcodeToEvent.keys():
|
def _resendNextCommand(self):
|
||||||
if gcode in cmd:
|
# Make sure we are only handling one sending job at a time
|
||||||
eventManager().fire(gcodeToEvent[gcode])
|
with self._sendingLock:
|
||||||
|
self._logger.debug("Resending line %d, delta is %d, history log is %s items strong" % (self._currentLine - self._resendDelta, self._resendDelta, len(self._lastLines)))
|
||||||
|
cmd = self._lastLines[-self._resendDelta]
|
||||||
|
lineNumber = self._currentLine - self._resendDelta
|
||||||
|
|
||||||
if 'M109' in cmd or 'M190' in cmd:
|
self._doSendWithChecksum(cmd, lineNumber)
|
||||||
self._heatupWaitStartTime = time.time()
|
|
||||||
if 'M104' in cmd or 'M109' in cmd:
|
self._resendDelta -= 1
|
||||||
try:
|
if self._resendDelta <= 0:
|
||||||
self._targetTemp = float(re.search('S([0-9]+)', cmd).group(1))
|
self._resendDelta = None
|
||||||
except:
|
|
||||||
pass
|
def _sendCommand(self, cmd, sendChecksum=False):
|
||||||
if 'M140' in cmd or 'M190' in cmd:
|
# Make sure we are only handling one sending job at a time
|
||||||
try:
|
with self._sendingLock:
|
||||||
self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1))
|
if self._serial is None:
|
||||||
except:
|
return
|
||||||
pass
|
|
||||||
self._log('Send: %s' % (cmd))
|
for gcode in gcodeToEvent.keys():
|
||||||
|
if gcode in cmd:
|
||||||
|
eventManager().fire(gcodeToEvent[gcode])
|
||||||
|
|
||||||
|
if 'M109' in cmd or 'M190' in cmd:
|
||||||
|
self._heatupWaitStartTime = time.time()
|
||||||
|
if 'M104' in cmd or 'M109' in cmd:
|
||||||
|
try:
|
||||||
|
self._targetTemp = float(re.search('S([0-9]+)', cmd).group(1))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if 'M140' in cmd or 'M190' in cmd:
|
||||||
|
try:
|
||||||
|
self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if "M110" in cmd:
|
||||||
|
newLineNumber = None
|
||||||
|
if " N" in cmd:
|
||||||
|
try:
|
||||||
|
newLineNumber = int(re.search("N([0-9]+)", cmd).group(1))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
newLineNumber = 0
|
||||||
|
|
||||||
|
if settings().getBoolean(["feature", "resetLineNumbersWithPrefixedN"]) and newLineNumber is not None:
|
||||||
|
# let's rewrite the M110 command to fit repetier syntax
|
||||||
|
self._addToLastLines(cmd)
|
||||||
|
self._doSendWithChecksum("M110", newLineNumber)
|
||||||
|
else:
|
||||||
|
self._doSend(cmd, sendChecksum)
|
||||||
|
|
||||||
|
if newLineNumber is not None:
|
||||||
|
self._currentLine = newLineNumber + 1
|
||||||
|
|
||||||
|
# after a reset of the line number we have no way to determine what line exactly the printer now wants
|
||||||
|
self._lastLines = []
|
||||||
|
self._resendDelta = None
|
||||||
|
else:
|
||||||
|
self._doSend(cmd, sendChecksum)
|
||||||
|
|
||||||
|
def _addToLastLines(self, cmd):
|
||||||
|
self._lastLines.append(cmd)
|
||||||
|
if len(self._lastLines) > 50:
|
||||||
|
self._lastLines = self._lastLines[-50:] # only keep the last 50 lines in memory
|
||||||
|
self._logger.debug("Got %d lines of history in memory" % len(self._lastLines))
|
||||||
|
|
||||||
|
def _doSend(self, cmd, sendChecksum=False):
|
||||||
|
if sendChecksum or self._alwaysSendChecksum:
|
||||||
|
if self._alwaysSendChecksum:
|
||||||
|
lineNumber = self._currentLine
|
||||||
|
else:
|
||||||
|
lineNumber = self._gcodePos
|
||||||
|
self._addToLastLines(cmd)
|
||||||
|
self._currentLine += 1
|
||||||
|
self._doSendWithChecksum(cmd, lineNumber)
|
||||||
|
else:
|
||||||
|
self._doSendWithoutChecksum(cmd)
|
||||||
|
|
||||||
|
def _doSendWithChecksum(self, 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)))
|
||||||
|
commandToSend = "N%d%s*%d" % (lineNumber, cmd, checksum)
|
||||||
|
self._doSendWithoutChecksum(commandToSend)
|
||||||
|
|
||||||
|
def _doSendWithoutChecksum(self, cmd):
|
||||||
|
self._log("Send: %s" % cmd)
|
||||||
try:
|
try:
|
||||||
self._serial.write(cmd + '\n')
|
self._serial.write(cmd + '\n')
|
||||||
except serial.SerialTimeoutException:
|
except serial.SerialTimeoutException:
|
||||||
|
@ -529,36 +949,36 @@ class MachineCom(object):
|
||||||
self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
|
self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
|
||||||
self._errorValue = getExceptionString()
|
self._errorValue = getExceptionString()
|
||||||
self.close(True)
|
self.close(True)
|
||||||
|
|
||||||
def _sendNext(self):
|
|
||||||
if self._gcodePos >= len(self._gcodeList):
|
|
||||||
self._changeState(self.STATE_OPERATIONAL)
|
|
||||||
eventManager().fire('PrintDone')
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._gcodePos == 100:
|
def _sendNext(self):
|
||||||
self._printStartTime100 = time.time()
|
with self._sendNextLock:
|
||||||
line = self._gcodeList[self._gcodePos]
|
if self._gcodePos >= len(self._gcodeList):
|
||||||
if type(line) is tuple:
|
self._changeState(self.STATE_OPERATIONAL)
|
||||||
self._printSection = line[1]
|
eventManager().fire('PrintDone')
|
||||||
line = line[0]
|
return
|
||||||
try:
|
|
||||||
if line == 'M0' or line == 'M1':
|
if self._gcodePos == 100:
|
||||||
self.setPause(True)
|
self._printStartTime100 = time.time()
|
||||||
line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
|
line = self._gcodeList[self._gcodePos]
|
||||||
if self._printSection in self._feedRateModifier:
|
if type(line) is tuple:
|
||||||
line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line)
|
self._printSection = line[1]
|
||||||
if ('G0' in line or 'G1' in line) and 'Z' in line:
|
line = line[0]
|
||||||
z = float(re.search('Z([0-9\.]*)', line).group(1))
|
try:
|
||||||
if self._currentZ != z:
|
if line == 'M0' or line == 'M1':
|
||||||
self._currentZ = z
|
self.setPause(True)
|
||||||
self._callback.mcZChange(z)
|
line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
|
||||||
except:
|
if self._printSection in self._feedRateModifier:
|
||||||
self._log("Unexpected error: %s" % (getExceptionString()))
|
line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line)
|
||||||
checksum = reduce(lambda x,y:x^y, map(ord, "N%d %s " % (self._gcodePos, line)))
|
if ('G0' in line or 'G1' in line) and 'Z' in line:
|
||||||
self._sendCommand("N%d %s *%d" % (self._gcodePos, line, checksum)) # added spaces between line # and command and checksum, because some firmware needs it and it's more readable in the terminal window
|
z = float(re.search('Z([0-9\.]*)', line).group(1))
|
||||||
self._gcodePos += 1
|
if self._currentZ != z:
|
||||||
self._callback.mcProgress(self._gcodePos)
|
self._currentZ = z
|
||||||
|
self._callback.mcZChange(z)
|
||||||
|
except:
|
||||||
|
self._log("Unexpected error: %s" % (getExceptionString()))
|
||||||
|
self._sendCommand(line, True)
|
||||||
|
self._gcodePos += 1
|
||||||
|
self._callback.mcProgress()
|
||||||
|
|
||||||
def sendCommand(self, cmd):
|
def sendCommand(self, cmd):
|
||||||
cmd = cmd.encode('ascii', 'replace')
|
cmd = cmd.encode('ascii', 'replace')
|
||||||
|
@ -570,28 +990,53 @@ class MachineCom(object):
|
||||||
def printGCode(self, gcodeList):
|
def printGCode(self, gcodeList):
|
||||||
if not self.isOperational() or self.isPrinting():
|
if not self.isOperational() or self.isPrinting():
|
||||||
return
|
return
|
||||||
|
if self._sdPrinting:
|
||||||
|
self._sdPrinting = False
|
||||||
self._gcodeList = gcodeList
|
self._gcodeList = gcodeList
|
||||||
self._gcodePos = 0
|
self._gcodePos = 0
|
||||||
self._printStartTime100 = None
|
|
||||||
self._printSection = 'CUSTOM'
|
self._printSection = 'CUSTOM'
|
||||||
self._changeState(self.STATE_PRINTING)
|
self._changeState(self.STATE_PRINTING)
|
||||||
self._printStartTime = time.time()
|
self._printStartTime = time.time()
|
||||||
for i in xrange(0, 6):
|
self._sendNext()
|
||||||
self._sendNext()
|
|
||||||
eventManager().fire("PrintStarted")
|
eventManager().fire("PrintStarted")
|
||||||
|
|
||||||
|
def printSdFile(self):
|
||||||
|
if not self.isOperational() or self.isPrinting():
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.isPaused():
|
||||||
|
self.sendCommand("M26 S0") # reset position in file to byte 0
|
||||||
|
self.sendCommand("M24")
|
||||||
|
|
||||||
|
self._printSection = 'CUSTOM'
|
||||||
|
self._sdPrinting = True
|
||||||
|
self._changeState(self.STATE_PRINTING)
|
||||||
|
self._printStartTime = time.time()
|
||||||
|
eventManager().fire("PrintStarted")
|
||||||
|
|
||||||
def cancelPrint(self):
|
def cancelPrint(self):
|
||||||
if self.isOperational():
|
if self.isOperational():
|
||||||
self._changeState(self.STATE_OPERATIONAL)
|
self._changeState(self.STATE_OPERATIONAL)
|
||||||
|
if self._sdPrinting:
|
||||||
|
self._sdPrinting = False
|
||||||
|
self.sendCommand("M25") # pause print
|
||||||
|
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 not pause and self.isPaused():
|
if not pause and self.isPaused():
|
||||||
self._changeState(self.STATE_PRINTING)
|
self._changeState(self.STATE_PRINTING)
|
||||||
for i in xrange(0, 6):
|
if self._sdPrinting:
|
||||||
self._sendNext()
|
self.sendCommand("M24")
|
||||||
|
else:
|
||||||
|
for i in xrange(0, 6):
|
||||||
|
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:
|
||||||
|
self.sendCommand("M25") # pause print
|
||||||
|
|
||||||
eventManager().fire("Paused")
|
eventManager().fire("Paused")
|
||||||
|
|
||||||
def setFeedrateModifier(self, type, value):
|
def setFeedrateModifier(self, type, value):
|
||||||
|
@ -602,6 +1047,64 @@ class MachineCom(object):
|
||||||
result.update(self._feedRateModifier)
|
result.update(self._feedRateModifier)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
##~~ SD card
|
||||||
|
def getSdFiles(self):
|
||||||
|
return self._sdFiles
|
||||||
|
|
||||||
|
def startSdFileTransfer(self, filename):
|
||||||
|
if not self.isOperational() or self.isPrinting() or self.isPaused():
|
||||||
|
return
|
||||||
|
|
||||||
|
self._changeState(self.STATE_RECEIVING_FILE)
|
||||||
|
self.sendCommand("M28 %s" % filename.lower())
|
||||||
|
|
||||||
|
def endSdFileTransfer(self, filename):
|
||||||
|
if not self.isOperational() or self.isPrinting() or self.isPaused():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.sendCommand("M29 %s" % filename.lower())
|
||||||
|
self._changeState(self.STATE_OPERATIONAL)
|
||||||
|
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):
|
||||||
|
if not self.isOperational() or ((self.isPrinting() or self.isPaused()) and self._sdFile == filename.lower()):
|
||||||
|
# do not delete a file from sd we are currently printing from
|
||||||
|
return
|
||||||
|
|
||||||
|
self.sendCommand("M30 %s" % filename.lower())
|
||||||
|
self.refreshSdFiles()
|
||||||
|
|
||||||
|
def refreshSdFiles(self):
|
||||||
|
if not self.isOperational() or self.isPrinting() or self.isPaused():
|
||||||
|
return
|
||||||
|
self.sendCommand("M20")
|
||||||
|
|
||||||
|
def initSdCard(self):
|
||||||
|
if not self.isOperational():
|
||||||
|
return
|
||||||
|
self.sendCommand("M21")
|
||||||
|
|
||||||
|
def releaseSdCard(self):
|
||||||
|
if not self.isOperational() or ((self.isPrinting() or self.isPaused()) and self._sdPrinting):
|
||||||
|
# do not release the sd card if we are currently printing from it
|
||||||
|
return
|
||||||
|
|
||||||
|
self.sendCommand("M22")
|
||||||
|
self._sdAvailable = False
|
||||||
|
self._sdFiles = []
|
||||||
|
|
||||||
|
self._callback.mcSdStateChange(self._sdAvailable)
|
||||||
|
self._callback.mcSdFiles(self._sdFiles)
|
||||||
|
|
||||||
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])
|
||||||
|
|
Loading…
Reference in New Issue