Allow definition of temperature offsets for extruder and bed
Session based temperature offsets that are only applied to temperature definitions in GCODE files being printed, in order to quickly experiment with temperature settings. Closes #97master
parent
b2587579ba
commit
8ad20a0168
|
@ -171,9 +171,19 @@ class Printer():
|
||||||
"""
|
"""
|
||||||
Sends multiple gcode commands (provided as a list) to the printer.
|
Sends multiple gcode commands (provided as a list) to the printer.
|
||||||
"""
|
"""
|
||||||
|
if self._comm is None:
|
||||||
|
return
|
||||||
|
|
||||||
for command in commands:
|
for command in commands:
|
||||||
self._comm.sendCommand(command)
|
self._comm.sendCommand(command)
|
||||||
|
|
||||||
|
def setTemperatureOffset(self, extruder, bed):
|
||||||
|
if self._comm is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._comm.setTemperatureOffset(extruder, bed)
|
||||||
|
self._stateMonitor.setTempOffsets(extruder, bed)
|
||||||
|
|
||||||
def selectFile(self, filename, sd, printAfterSelect=False):
|
def selectFile(self, filename, sd, printAfterSelect=False):
|
||||||
if self._comm is None or (self._comm.isBusy() or self._comm.isStreaming()):
|
if self._comm is None or (self._comm.isBusy() or self._comm.isStreaming()):
|
||||||
return
|
return
|
||||||
|
@ -503,14 +513,22 @@ class Printer():
|
||||||
return currentData["job"]
|
return currentData["job"]
|
||||||
|
|
||||||
def getCurrentTemperatures(self):
|
def getCurrentTemperatures(self):
|
||||||
|
if self._comm is not None:
|
||||||
|
(tempOffset, bedTempOffset) = self._comm.getOffsets()
|
||||||
|
else:
|
||||||
|
tempOffset = 0
|
||||||
|
bedTempOffset = 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"extruder": {
|
"extruder": {
|
||||||
"current": self._temp,
|
"current": self._temp,
|
||||||
"target": self._targetTemp
|
"target": self._targetTemp,
|
||||||
|
"offset": tempOffset
|
||||||
},
|
},
|
||||||
"bed": {
|
"bed": {
|
||||||
"current": self._bedTemp,
|
"current": self._bedTemp,
|
||||||
"target": self._targetBedTemp
|
"target": self._targetBedTemp,
|
||||||
|
"offset": bedTempOffset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,6 +645,9 @@ class StateMonitor(object):
|
||||||
self._currentZ = None
|
self._currentZ = None
|
||||||
self._progress = None
|
self._progress = None
|
||||||
|
|
||||||
|
self._tempOffset = 0
|
||||||
|
self._bedTempOffset = 0
|
||||||
|
|
||||||
self._changeEvent = threading.Event()
|
self._changeEvent = threading.Event()
|
||||||
|
|
||||||
self._lastUpdate = time.time()
|
self._lastUpdate = time.time()
|
||||||
|
@ -668,6 +689,13 @@ class StateMonitor(object):
|
||||||
self._progress = progress
|
self._progress = progress
|
||||||
self._changeEvent.set()
|
self._changeEvent.set()
|
||||||
|
|
||||||
|
def setTempOffsets(self, tempOffset, bedTempOffset):
|
||||||
|
if tempOffset is not None:
|
||||||
|
self._tempOffset = tempOffset
|
||||||
|
if bedTempOffset is not None:
|
||||||
|
self._bedTempOffset = bedTempOffset
|
||||||
|
self._changeEvent.set()
|
||||||
|
|
||||||
def _work(self):
|
def _work(self):
|
||||||
while True:
|
while True:
|
||||||
self._changeEvent.wait()
|
self._changeEvent.wait()
|
||||||
|
@ -688,6 +716,7 @@ class StateMonitor(object):
|
||||||
"state": self._state,
|
"state": self._state,
|
||||||
"job": self._jobData,
|
"job": self._jobData,
|
||||||
"currentZ": self._currentZ,
|
"currentZ": self._currentZ,
|
||||||
"progress": self._progress
|
"progress": self._progress,
|
||||||
|
"offsets": (self._tempOffset, self._bedTempOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -277,6 +277,24 @@ def setTargetTemperature():
|
||||||
bedTemp = request.values["bedTemp"]
|
bedTemp = request.values["bedTemp"]
|
||||||
printer.command("M140 S" + bedTemp)
|
printer.command("M140 S" + bedTemp)
|
||||||
|
|
||||||
|
if "tempOffset" in request.values.keys():
|
||||||
|
# set target temperature offset
|
||||||
|
try:
|
||||||
|
tempOffset = float(request.values["tempOffset"])
|
||||||
|
if tempOffset >= -50 and tempOffset <= 50:
|
||||||
|
printer.setTemperatureOffset(tempOffset, None)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if "bedTempOffset" in request.values.keys():
|
||||||
|
# set target bed temperature offset
|
||||||
|
try:
|
||||||
|
bedTempOffset = float(request.values["bedTempOffset"])
|
||||||
|
if bedTempOffset >= -50 and bedTempOffset <= 50:
|
||||||
|
printer.setTemperatureOffset(None, bedTempOffset)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
return jsonify(SUCCESS)
|
return jsonify(SUCCESS)
|
||||||
|
|
||||||
@app.route(BASEURL + "control/jog", methods=["POST"])
|
@app.route(BASEURL + "control/jog", methods=["POST"])
|
||||||
|
|
|
@ -11,6 +11,11 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
||||||
self.newTemp = ko.observable(undefined);
|
self.newTemp = ko.observable(undefined);
|
||||||
self.newBedTemp = ko.observable(undefined);
|
self.newBedTemp = ko.observable(undefined);
|
||||||
|
|
||||||
|
self.newTempOffset = ko.observable(undefined);
|
||||||
|
self.tempOffset = ko.observable(0);
|
||||||
|
self.newBedTempOffset = ko.observable(undefined);
|
||||||
|
self.bedTempOffset = ko.observable(0);
|
||||||
|
|
||||||
self.isErrorOrClosed = ko.observable(undefined);
|
self.isErrorOrClosed = ko.observable(undefined);
|
||||||
self.isOperational = ko.observable(undefined);
|
self.isOperational = ko.observable(undefined);
|
||||||
self.isPrinting = ko.observable(undefined);
|
self.isPrinting = ko.observable(undefined);
|
||||||
|
@ -78,11 +83,13 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
||||||
self.fromCurrentData = function(data) {
|
self.fromCurrentData = function(data) {
|
||||||
self._processStateData(data.state);
|
self._processStateData(data.state);
|
||||||
self._processTemperatureUpdateData(data.temperatures);
|
self._processTemperatureUpdateData(data.temperatures);
|
||||||
|
self._processOffsetData(data.offsets);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fromHistoryData = function(data) {
|
self.fromHistoryData = function(data) {
|
||||||
self._processStateData(data.state);
|
self._processStateData(data.state);
|
||||||
self._processTemperatureHistoryData(data.temperatureHistory);
|
self._processTemperatureHistoryData(data.temperatureHistory);
|
||||||
|
self._processOffsetData(data.offsets);
|
||||||
}
|
}
|
||||||
|
|
||||||
self._processStateData = function(data) {
|
self._processStateData = function(data) {
|
||||||
|
@ -138,6 +145,11 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
||||||
self.updatePlot();
|
self.updatePlot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self._processOffsetData = function(data) {
|
||||||
|
self.tempOffset(data[0]);
|
||||||
|
self.bedTempOffset(data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
self.updatePlot = function() {
|
self.updatePlot = function() {
|
||||||
var graph = $("#temperature-graph");
|
var graph = $("#temperature-graph");
|
||||||
if (graph.length) {
|
if (graph.length) {
|
||||||
|
@ -166,6 +178,10 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
||||||
self._updateTemperature(0, "temp", function(){self.targetTemp(0); self.newTemp("");});
|
self._updateTemperature(0, "temp", function(){self.targetTemp(0); self.newTemp("");});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.setTempOffset = function() {
|
||||||
|
self._updateTemperature(self.newTempOffset(), "tempOffset", function() {self.tempOffset(self.newTempOffset()); self.newTempOffset("");});
|
||||||
|
}
|
||||||
|
|
||||||
self.setBedTempFromProfile = function(profile) {
|
self.setBedTempFromProfile = function(profile) {
|
||||||
self._updateTemperature(profile.bed, "bedTemp");
|
self._updateTemperature(profile.bed, "bedTemp");
|
||||||
}
|
}
|
||||||
|
@ -178,6 +194,10 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
||||||
self._updateTemperature(0, "bedTemp", function() {self.bedTargetTemp(0); self.newBedTemp("");});
|
self._updateTemperature(0, "bedTemp", function() {self.bedTargetTemp(0); self.newBedTemp("");});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.setBedTempOffset = function() {
|
||||||
|
self._updateTemperature(self.newBedTempOffset(), "bedTempOffset", function() {self.bedTempOffset(self.newBedTempOffset()); self.newBedTempOffset("");});
|
||||||
|
}
|
||||||
|
|
||||||
self._updateTemperature = function(temp, type, callback) {
|
self._updateTemperature = function(temp, type, callback) {
|
||||||
var data = {};
|
var data = {};
|
||||||
data[type] = temp;
|
data[type] = temp;
|
||||||
|
@ -185,10 +205,9 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: AJAX_BASEURL + "control/temperature",
|
url: AJAX_BASEURL + "control/temperature",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
dataType: "json",
|
|
||||||
data: data,
|
data: data,
|
||||||
success: function() { if (callback !== undefined) callback(); }
|
success: function() { if (callback !== undefined) callback(); }
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.handleEnter = function(event, type) {
|
self.handleEnter = function(event, type) {
|
||||||
|
|
|
@ -256,12 +256,12 @@
|
||||||
<div class="form-horizontal span6">
|
<div class="form-horizontal span6">
|
||||||
<h1>Temperature</h1>
|
<h1>Temperature</h1>
|
||||||
|
|
||||||
<label>Current: <strong data-bind="html: tempString"></strong></label>
|
<label title="Current extruder temperature">Current: <strong data-bind="html: tempString"></strong></label>
|
||||||
|
|
||||||
<label>Target: <strong data-bind="html: targetTempString"></strong></label>
|
<label title="Target extruder temperature">Target: <strong data-bind="html: targetTempString"></strong></label>
|
||||||
|
|
||||||
<div style="display: none;" data-bind="visible: loginState.isUser">
|
<div style="display: none;" data-bind="visible: loginState.isUser">
|
||||||
<label for="temp_newTemp">New Target</label>
|
<label for="temp_newTemp" title="Sets the new target temperature for the extruder">New Target</label>
|
||||||
<div class="input-append">
|
<div class="input-append">
|
||||||
<input type="text" class="input-mini text-right" data-bind="value: newTemp, valueUpdate: 'afterkeydown', attr: {placeholder: targetTemp}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'temp');} }" class="tempInput">
|
<input type="text" class="input-mini text-right" data-bind="value: newTemp, valueUpdate: 'afterkeydown', attr: {placeholder: targetTemp}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'temp');} }" class="tempInput">
|
||||||
<span class="add-on">°C</span>
|
<span class="add-on">°C</span>
|
||||||
|
@ -284,16 +284,24 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display: none;" data-bind="visible: loginState.isUser">
|
||||||
|
<label title="Sets a temperature offset to apply to temperatures set via streamed GCODE, may be positive or negative, will not persist across restarts of OctoPrint">Offset</label>
|
||||||
|
<div class="input-append">
|
||||||
|
<input type="number" min="-50" max="50" class="input-mini text-right" data-bind="value: newTempOffset, valueUpdate: 'afterkeydown', attr: {placeholder: tempOffset}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'tempOffset');} }" class="tempInput">
|
||||||
|
<span class="add-on">°C</span>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn" data-bind="click: setTempOffset, enable: newTempOffset() && isOperational() && loginState.isUser()">Set</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-horizontal span6">
|
<div class="form-horizontal span6">
|
||||||
<h1>Bed Temperature</h1>
|
<h1>Bed Temperature</h1>
|
||||||
|
|
||||||
<label>Current: <strong data-bind="html: bedTempString"></strong></label>
|
<label title="Current bed temperature">Current: <strong data-bind="html: bedTempString"></strong></label>
|
||||||
|
|
||||||
<label>Target: <strong data-bind="html: bedTargetTempString"></strong></label>
|
<label title="Target bed temperature">Target: <strong data-bind="html: bedTargetTempString"></strong></label>
|
||||||
|
|
||||||
<div style="display: none;" data-bind="visible: loginState.isUser">
|
<div style="display: none;" data-bind="visible: loginState.isUser">
|
||||||
<label for="temp_newBedTemp">New Target</label>
|
<label for="temp_newBedTemp" title="Sets the new target temperature for the bed">New Target</label>
|
||||||
<div class="input-append">
|
<div class="input-append">
|
||||||
<input type="text" class="input-mini text-right" data-bind="value: newBedTemp, valueUpdate: 'afterkeydown', attr: {placeholder: bedTargetTemp}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(event, 'bedTemp');} }" class="tempInput">
|
<input type="text" class="input-mini text-right" data-bind="value: newBedTemp, valueUpdate: 'afterkeydown', attr: {placeholder: bedTargetTemp}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(event, 'bedTemp');} }" class="tempInput">
|
||||||
<span class="add-on">°C</span>
|
<span class="add-on">°C</span>
|
||||||
|
@ -316,6 +324,14 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display: none;" data-bind="visible: loginState.isUser">
|
||||||
|
<label title="Sets a temperature offset to apply to bed temperatures set via streamed GCODE, may be positive or negative, will not persist across restarts of OctoPrint">Offset</label>
|
||||||
|
<div class="input-append">
|
||||||
|
<input type="number" min="-50" max="50" class="input-mini text-right" data-bind="value: newBedTempOffset, valueUpdate: 'afterkeydown', attr: {placeholder: bedTempOffset}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'bedTempOffset');} }" class="tempInput">
|
||||||
|
<span class="add-on">°C</span>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn" data-bind="click: setBedTempOffset, enable: newBedTempOffset() && isOperational() && loginState.isUser()">Set</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -120,6 +120,8 @@ class MachineCom(object):
|
||||||
self._bedTemp = 0
|
self._bedTemp = 0
|
||||||
self._targetTemp = 0
|
self._targetTemp = 0
|
||||||
self._bedTargetTemp = 0
|
self._bedTargetTemp = 0
|
||||||
|
self._tempOffset = 0
|
||||||
|
self._bedTempOffset = 0
|
||||||
self._commandQueue = queue.Queue()
|
self._commandQueue = queue.Queue()
|
||||||
self._currentZ = None
|
self._currentZ = None
|
||||||
self._heatupWaitStartTime = 0
|
self._heatupWaitStartTime = 0
|
||||||
|
@ -285,6 +287,9 @@ class MachineCom(object):
|
||||||
def getBedTemp(self):
|
def getBedTemp(self):
|
||||||
return self._bedTemp
|
return self._bedTemp
|
||||||
|
|
||||||
|
def getOffsets(self):
|
||||||
|
return (self._tempOffset, self._bedTempOffset)
|
||||||
|
|
||||||
##~~ external interface
|
##~~ external interface
|
||||||
|
|
||||||
def close(self, isError = False):
|
def close(self, isError = False):
|
||||||
|
@ -304,6 +309,13 @@ class MachineCom(object):
|
||||||
eventManager().fire("PrintFailed")
|
eventManager().fire("PrintFailed")
|
||||||
eventManager().fire("Disconnected")
|
eventManager().fire("Disconnected")
|
||||||
|
|
||||||
|
def setTemperatureOffset(self, extruder=None, bed=None):
|
||||||
|
if extruder is not None:
|
||||||
|
self._tempOffset = extruder
|
||||||
|
|
||||||
|
if bed is not None:
|
||||||
|
self._bedTempOffset = bed
|
||||||
|
|
||||||
def sendCommand(self, cmd):
|
def sendCommand(self, cmd):
|
||||||
cmd = cmd.encode('ascii', 'replace')
|
cmd = cmd.encode('ascii', 'replace')
|
||||||
if self.isPrinting() and not self.isSdFileSelected():
|
if self.isPrinting() and not self.isSdFileSelected():
|
||||||
|
@ -357,7 +369,7 @@ class MachineCom(object):
|
||||||
return
|
return
|
||||||
self.sendCommand("M23 %s" % filename)
|
self.sendCommand("M23 %s" % filename)
|
||||||
else:
|
else:
|
||||||
self._currentFile = PrintingGcodeFileInformation(filename)
|
self._currentFile = PrintingGcodeFileInformation(filename, self.getOffsets)
|
||||||
eventManager().fire("FileSelected", filename)
|
eventManager().fire("FileSelected", filename)
|
||||||
self._callback.mcFileSelected(filename, self._currentFile.getFilesize(), False)
|
self._callback.mcFileSelected(filename, self._currentFile.getFilesize(), False)
|
||||||
|
|
||||||
|
@ -794,10 +806,6 @@ class MachineCom(object):
|
||||||
eventManager().fire("PrintDone", self._currentFile.getFilename())
|
eventManager().fire("PrintDone", self._currentFile.getFilename())
|
||||||
return
|
return
|
||||||
|
|
||||||
if type(line) is tuple:
|
|
||||||
self._printSection = line[1]
|
|
||||||
line = line[0]
|
|
||||||
|
|
||||||
self._sendCommand(line, True)
|
self._sendCommand(line, True)
|
||||||
self._callback.mcProgress()
|
self._callback.mcProgress()
|
||||||
|
|
||||||
|
@ -1065,12 +1073,14 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
|
||||||
that the file is closed in case of an error.
|
that the file is closed in case of an error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filename):
|
def __init__(self, filename, offsetCallback):
|
||||||
PrintingFileInformation.__init__(self, filename)
|
PrintingFileInformation.__init__(self, filename)
|
||||||
self._filehandle = None
|
self._filehandle = None
|
||||||
self._lineCount = None
|
self._lineCount = None
|
||||||
self._firstLine = None
|
self._firstLine = None
|
||||||
self._prevLineType = None
|
|
||||||
|
self._offsetCallback = offsetCallback
|
||||||
|
self._tempCommandPattern = re.compile("^\s*M(104|109|140|190)\s+S([0-9\.]+)")
|
||||||
|
|
||||||
if not os.path.exists(self._filename) or not os.path.isfile(self._filename):
|
if not os.path.exists(self._filename) or not os.path.isfile(self._filename):
|
||||||
raise IOError("File %s does not exist" % self._filename)
|
raise IOError("File %s does not exist" % self._filename)
|
||||||
|
@ -1082,7 +1092,6 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
|
||||||
"""
|
"""
|
||||||
self._filehandle = open(self._filename, "r")
|
self._filehandle = open(self._filename, "r")
|
||||||
self._lineCount = None
|
self._lineCount = None
|
||||||
self._prevLineType = "CUSTOM"
|
|
||||||
self._startTime = None
|
self._startTime = None
|
||||||
|
|
||||||
def getNext(self):
|
def getNext(self):
|
||||||
|
@ -1121,16 +1130,27 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def _processLine(self, line):
|
def _processLine(self, line):
|
||||||
lineType = self._prevLineType
|
|
||||||
if line.startswith(";TYPE:"):
|
|
||||||
lineType = line[6:].strip()
|
|
||||||
if ";" in line:
|
if ";" in line:
|
||||||
line = line[0:line.find(";")]
|
line = line[0:line.find(";")]
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if len(line) > 0:
|
if len(line) > 0:
|
||||||
if self._prevLineType != lineType:
|
(tempOffset, bedTempOffset) = self._offsetCallback()
|
||||||
return line, lineType
|
if tempOffset != 0 or bedTempOffset != 0:
|
||||||
|
tempMatch = self._tempCommandPattern.match(line)
|
||||||
|
if tempMatch is not None:
|
||||||
|
if tempMatch.group(1) == "104" or tempMatch.group(1) == "109":
|
||||||
|
offset = tempOffset
|
||||||
|
elif tempMatch.group(1) == "140" or tempMatch.group(1) == "190":
|
||||||
|
offset = bedTempOffset
|
||||||
else:
|
else:
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
temp = float(tempMatch.group(2))
|
||||||
|
newTemp = temp + offset
|
||||||
|
line = line.replace("S" + tempMatch.group(2), "S%f" % newTemp)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
return line
|
return line
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
Loading…
Reference in New Issue