Added controls for changing feed and flow rate factors to Controls tab
Closes #362
This commit is contained in:
parent
e476e4154e
commit
4abcf1054b
10 changed files with 195 additions and 34 deletions
|
|
@ -32,6 +32,7 @@
|
|||
- Home: Home X and Y axes
|
||||
- End: Home Z axes
|
||||
- 1, 2, 3, 4: change step size used (0.1, 1, 10, 100mm)
|
||||
* Controls for adjusting feed and flow rate factor added to Controls ([#362](https://github.com/foosel/OctoPrint/issues/362))
|
||||
|
||||
### Improvements
|
||||
|
||||
|
|
|
|||
|
|
@ -199,8 +199,13 @@ Issue a print head command
|
|||
|
||||
* ``axes``: A list of axes which to home, valid values are one or more of ``x``, ``y``, ``z``.
|
||||
|
||||
All of these commands may only be sent if the printer is currently operational and not printing. Otherwise a
|
||||
:http:statuscode:`409` is returned.
|
||||
feedrate
|
||||
Changes the feedrate factor to apply to the movement's of the axes.
|
||||
|
||||
* ``factor``: The new factor, percentage as integer or float (percentage divided by 100) between 50 and 200%.
|
||||
|
||||
All of these commands except ``feedrate`` may only be sent if the printer is currently operational and not printing.
|
||||
Otherwise a :http:statuscode:`409` is returned.
|
||||
|
||||
Upon success, a status code of :http:statuscode:`204` and an empty body is returned.
|
||||
|
||||
|
|
@ -246,13 +251,34 @@ Issue a print head command
|
|||
|
||||
HTTP/1.1 204 No Content
|
||||
|
||||
**Example feed rate request**
|
||||
|
||||
Set the feed rate factor to 105%.
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/printer/printhead HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "feedrate",
|
||||
"factor": 105
|
||||
}
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
|
||||
:json string command: The command to issue, either ``jog`` or ``home``.
|
||||
:json number x: ``jog`` command: The amount to travel on the X axis in mm.
|
||||
:json number y: ``jog`` command: The amount to travel on the Y axis in mm.
|
||||
:json number z: ``jog`` command: The amount to travel on the Z axis in mm.
|
||||
:json array axes: ``home`` command: The axes which to home, valid values are one or more of ``x``, ``y`` and ``z``.
|
||||
:json number factor: ``feedrate`` command: The factor to apply to the feed rate, percentage between 50 and 200% as integer or float.
|
||||
:statuscode 204: No error
|
||||
:statuscode 400: Invalid axis specified, invalid value for travel amount for a jog command or otherwise invalid
|
||||
:statuscode 400: Invalid axis specified, invalid value for travel amount for a jog command or factor for feed rate or otherwise invalid
|
||||
request.
|
||||
:statuscode 409: If the printer is not operational or currently printing.
|
||||
|
||||
|
|
@ -288,6 +314,10 @@ Issue a tool command
|
|||
|
||||
* ``amount``: The amount of filament to extrude in mm. May be negative to retract.
|
||||
|
||||
flowrate
|
||||
Changes the flow rate factor to apply to extrusion of the tool.
|
||||
|
||||
* ``factor``: The new factor, percentage as integer or float (percentage divided by 100) between 75 and 125%.
|
||||
|
||||
All of these commands may only be sent if the printer is currently operational and -- in case of ``select`` and
|
||||
``extrude`` -- not printing. Otherwise a :http:statuscode:`409` is returned.
|
||||
|
|
@ -400,14 +430,35 @@ Issue a tool command
|
|||
|
||||
HTTP/1.1 204 No Content
|
||||
|
||||
**Example flow rate request**
|
||||
|
||||
Set the flow rate factor to 95%.
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/printer/tool HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "flowrate",
|
||||
"factor": 95
|
||||
}
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
|
||||
:json string command: The command to issue, either ``target``, ``offset``, ``select`` or ``extrude``.
|
||||
:json object targets: ``target`` command: The target temperatures to set. Valid properties have to match the format ``tool{n}``.
|
||||
:json object offsets: ``offset`` command: The offset temperature to set. Valid properties have to match the format ``tool{n}``.
|
||||
:json object tool: ``select`` command: The tool to select, value has to match the format ``tool{n}``.
|
||||
:json object amount: ``extrude`` command: The amount of filament to extrude from the currently selected tool.
|
||||
:json number factor: ``flowrate`` command: The factor to apply to the flow rate, percentage between 75 and 125% as integer or float.
|
||||
:statuscode 204: No error
|
||||
:statuscode 400: If ``targets`` or ``offsets`` contains a property or ``tool`` contains a value not matching the format
|
||||
``tool{n}``, the target/offset temperature or extrusion amount is not a valid number or outside of
|
||||
``tool{n}``, the target/offset temperature, extrusion amount or flow rate factor is not a valid number or outside of
|
||||
the supported range, or if the request is otherwise invalid.
|
||||
:statuscode 409: If the printer is not operational or -- in case of ``select`` or ``extrude`` -- currently printing.
|
||||
|
||||
|
|
|
|||
|
|
@ -303,6 +303,26 @@ class Printer():
|
|||
self._comm.setTemperatureOffset(tool, bed)
|
||||
self._stateMonitor.setTempOffsets(validatedOffsets)
|
||||
|
||||
def _convertRateValue(self, factor, min=0, max=200):
|
||||
if not isinstance(factor, (int, float, long)):
|
||||
raise ValueError("factor is not a number")
|
||||
|
||||
if isinstance(factor, float):
|
||||
factor = int(factor * 100.0)
|
||||
|
||||
if factor < min or factor > max:
|
||||
raise ValueError("factor must be a value between %f and %f" % (min, max))
|
||||
|
||||
return factor
|
||||
|
||||
def feedRate(self, factor):
|
||||
factor = self._convertRateValue(factor, min=50, max=200)
|
||||
self.command("M220 S%d" % factor)
|
||||
|
||||
def flowRate(self, factor):
|
||||
factor = self._convertRateValue(factor, min=75, max=125)
|
||||
self.command("M221 S%d" % factor)
|
||||
|
||||
def selectFile(self, filename, sd, printAfterSelect=False):
|
||||
if self._comm is None or (self._comm.isBusy() or self._comm.isStreaming()):
|
||||
self._logger.info("Cannot load file: printer not connected or currently busy")
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ def printerToolCommand():
|
|||
"select": ["tool"],
|
||||
"target": ["targets"],
|
||||
"offset": ["offsets"],
|
||||
"extrude": ["amount"]
|
||||
"extrude": ["amount"],
|
||||
"flowrate": ["factor"]
|
||||
}
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
|
|
@ -125,6 +126,15 @@ def printerToolCommand():
|
|||
return make_response("Not a number for extrusion amount: %r" % amount, 400)
|
||||
printer.extrude(amount)
|
||||
|
||||
elif command == "flowrate":
|
||||
factor = data["factor"]
|
||||
if not isinstance(factor, (int, long, float)):
|
||||
return make_response("Not a number for flow rate: %r" % factor, 400)
|
||||
try:
|
||||
printer.flowRate(factor)
|
||||
except ValueError as e:
|
||||
return make_response("Invalid value for flow rate: %s" % e.message, 400)
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
|
|
@ -219,7 +229,8 @@ def printerPrintheadCommand():
|
|||
|
||||
valid_commands = {
|
||||
"jog": [],
|
||||
"home": ["axes"]
|
||||
"home": ["axes"],
|
||||
"feedrate": ["factor"]
|
||||
}
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
|
|
@ -253,6 +264,15 @@ def printerPrintheadCommand():
|
|||
# execute the home command
|
||||
printer.home(validated_values)
|
||||
|
||||
elif command == "feedrate":
|
||||
factor = data["factor"]
|
||||
if not isinstance(factor, (int, long, float)):
|
||||
return make_response("Not a number for feed rate: %r" % factor, 400)
|
||||
try:
|
||||
printer.feedRate(factor)
|
||||
except ValueError as e:
|
||||
return make_response("Invalid value for feed rate: %s" % e.message, 400)
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -24,6 +24,9 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
|
||||
self.tools = ko.observableArray([]);
|
||||
|
||||
self.feedRate = ko.observable(100);
|
||||
self.flowRate = ko.observable(100);
|
||||
|
||||
self.feedbackControlLookup = {};
|
||||
|
||||
self.controlsFromServer = [];
|
||||
|
|
@ -181,27 +184,20 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
};
|
||||
data[axis] = distance * multiplier;
|
||||
|
||||
$.ajax({
|
||||
url: API_BASEURL + "printer/printhead",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(data)
|
||||
});
|
||||
self.sendPrintHeadCommand(data);
|
||||
};
|
||||
|
||||
self.sendHomeCommand = function(axis) {
|
||||
var data = {
|
||||
self.sendPrintHeadCommand({
|
||||
"command": "home",
|
||||
"axes": axis
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: API_BASEURL + "printer/printhead",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(data)
|
||||
self.sendFeedRateCommand = function() {
|
||||
self.sendPrintHeadCommand({
|
||||
"command": "feedrate",
|
||||
"factor": self.feedRate()
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -213,17 +209,35 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
self._sendECommand(-1);
|
||||
};
|
||||
|
||||
self.sendFlowRateCommand = function() {
|
||||
self.sendToolCommand({
|
||||
"command": "flowrate",
|
||||
"factor": self.flowRate()
|
||||
});
|
||||
};
|
||||
|
||||
self._sendECommand = function(dir) {
|
||||
var length = self.extrusionAmount();
|
||||
if (!length) length = self.settings.printer_defaultExtrusionLength();
|
||||
|
||||
var data = {
|
||||
self.sendToolCommand({
|
||||
command: "extrude",
|
||||
amount: length * dir
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
self.sendSelectToolCommand = function(data) {
|
||||
if (!data || !data.key()) return;
|
||||
|
||||
self.sendToolCommand({
|
||||
command: "select",
|
||||
tool: data.key()
|
||||
});
|
||||
};
|
||||
|
||||
self.sendPrintHeadCommand = function(data) {
|
||||
$.ajax({
|
||||
url: API_BASEURL + "printer/tool",
|
||||
url: API_BASEURL + "printer/printhead",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
@ -231,20 +245,13 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
});
|
||||
};
|
||||
|
||||
self.sendSelectToolCommand = function(data) {
|
||||
if (!data || !data.key()) return;
|
||||
|
||||
var payload = {
|
||||
command: "select",
|
||||
tool: data.key()
|
||||
};
|
||||
|
||||
self.sendToolCommand = function(data) {
|
||||
$.ajax({
|
||||
url: API_BASEURL + "printer/tool",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(payload)
|
||||
data: JSON.stringify(data)
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
44
src/octoprint/static/js/lib/bootstrap-slider-knockout-binding.js
vendored
Normal file
44
src/octoprint/static/js/lib/bootstrap-slider-knockout-binding.js
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* Based on bootstrap-slider-knockout-binding, written by Cosmin Stefan-Dobrin, https://github.com/cosminstefanxp,
|
||||
* licensed under MIT License
|
||||
*
|
||||
* Github: https://github.com/cosminstefanxp/bootstrap-slider-knockout-binding
|
||||
*/
|
||||
ko.bindingHandlers.slider = {
|
||||
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
|
||||
var params = valueAccessor();
|
||||
|
||||
// Check whether the value observable is either placed directly or in the paramaters object.
|
||||
if (!(ko.isObservable(params) || params['value']))
|
||||
throw "You need to define an observable value for the sliderValue. Either pass the observable directly or as the 'value' field in the parameters.";
|
||||
|
||||
// Identify the value and initialize the slider
|
||||
var valueObservable;
|
||||
if (ko.isObservable(params)) {
|
||||
valueObservable = params;
|
||||
$(element).slider({value: ko.utils.unwrapObservable(params)});
|
||||
} else {
|
||||
valueObservable = params['value'];
|
||||
// Replace the 'value' field in the options object with the actual value
|
||||
params['value'] = ko.utils.unwrapObservable(valueObservable);
|
||||
$(element).slider(params);
|
||||
}
|
||||
|
||||
// Make sure we update the observable when changing the slider value
|
||||
$(element).on('slide', function (ev) {
|
||||
valueObservable(ev.value);
|
||||
});
|
||||
|
||||
},
|
||||
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
|
||||
var modelValue = valueAccessor();
|
||||
var valueObservable;
|
||||
if (ko.isObservable(modelValue)) {
|
||||
valueObservable = modelValue;
|
||||
} else {
|
||||
valueObservable = modelValue['value'];
|
||||
}
|
||||
|
||||
$(element).slider('setValue', parseFloat(ko.isObservable(valueObservable) ? valueObservable() : valueObservable));
|
||||
}
|
||||
};
|
||||
|
|
@ -534,6 +534,16 @@ ul.dropdown-menu li a {
|
|||
height: 30px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.slider-handle {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-left: -7px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
/** GCODE viewer */
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/detectmobilebrowser.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/md5.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/pnotify.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap-slider-knockout-binding.js') }}"></script>
|
||||
|
||||
<!-- Include OctoPrint files -->
|
||||
<!-- TODO: merge/minimize in the future -->
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feed rate -->
|
||||
<input type="number" style="width: 153px" data-bind="slider: {min: 50, max: 150, step: 1, value: feedRate, tooltip: 'hide'}">
|
||||
<button class="btn btn-block" style="width: 169px" data-bind="enable: isOperational() && loginState.isUser(), click: function() { $root.sendFeedRateCommand() }">{{ _('Feed rate:') }}<span data-bind="text: feedRate() + '%'"></span></button>
|
||||
</div>
|
||||
<!-- Extrusion control panel -->
|
||||
<div class="jog-panel" style="display: none;" data-bind="visible: loginState.isUser">
|
||||
|
|
@ -82,6 +85,10 @@
|
|||
</div>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendExtrudeCommand() }">{{ _('Extrude') }}</button>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendRetractCommand() }">{{ _('Retract') }}</button>
|
||||
|
||||
<!-- Flow rate -->
|
||||
<input style="width: 100px" type="number" data-bind="slider: {min: 75, max: 125, step: 1, value: flowRate, tooltip: 'hide'}">
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && loginState.isUser(), click: function() { $root.sendFlowRateCommand() }">{{ _('Flow rate:') }}<span data-bind="text: flowRate() + '%'"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- General control panel -->
|
||||
|
|
|
|||
Loading…
Reference in a new issue