Merge branch 'improve/slicingFlexibility' into devel

Conflicts:
	src/octoprint/filemanager/__init__.py
	src/octoprint/server/api/files.py
	src/octoprint/static/js/app/viewmodels/slicing.js
This commit is contained in:
Gina Häußge 2016-07-04 10:24:43 +02:00
commit b07c352b48
8 changed files with 211 additions and 115 deletions

View file

@ -299,7 +299,7 @@ class FileManager(object):
"stl_location": source_location,
"gcode": dest_path,
"gcode_location": dest_location,
"progressAvailable": slicer.get_slicer_properties()["progress_report"] if slicer else False})
"progressAvailable": slicer.get_slicer_properties().get("progress_report", False) if slicer else False})
import tempfile
f = tempfile.NamedTemporaryFile(suffix=".gco", delete=False)

View file

@ -1587,6 +1587,13 @@ class SlicerPlugin(OctoPrintPlugin):
against slicers running on the same device will result in an error.
progress_report
``True`` if the slicer can report back slicing progress to OctoPrint ``False`` otherwise.
source_file_types
A list of file types this slicer supports as valid origin file types. These are file types as found in the
paths within the extension tree. Plugins may add additional file types through the :ref:`sec-plugins-hook-filemanager-extensiontree` hook.
The system will test source files contains in incoming slicing requests via :meth:`octoprint.filemanager.valid_file_type` against the
targeted slicer's ``source_file_types``.
destination_extension
The possible extensions of slicing result files.
Returns:
dict: A dict describing the slicer as outlined above.
@ -1595,7 +1602,9 @@ class SlicerPlugin(OctoPrintPlugin):
type=None,
name=None,
same_device=True,
progress_report=False
progress_report=False,
source_file_types=["model"],
destination_extensions=["gco", "gcode", "g"]
)
def get_slicer_default_profile(self):

View file

@ -199,7 +199,9 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
type="cura",
name="CuraEngine",
same_device=True,
progress_report=True
progress_report=True,
source_file_types=["stl"],
destination_extensions=["gco", "gcode", "g"]
)
def get_slicer_default_profile(self):

View file

@ -438,28 +438,31 @@ def gcodeFileCommand(filename, target):
except octoprint.slicing.UnknownSlicer as e:
return make_response("Slicer {slicer} is not available".format(slicer=e.slicer), 400)
if not octoprint.filemanager.valid_file_type(filename, type="stl"):
return make_response("Cannot slice {filename}, not an STL file".format(**locals()), 415)
if not any([octoprint.filemanager.valid_file_type(filename, type=source_file_type) for source_file_type in slicer_instance.get_slicer_properties().get("source_file_types", ["model"])]):
return make_response("Cannot slice {filename}, not a model file".format(**locals()), 415)
if slicer_instance.get_slicer_properties()["same_device"] and (printer.is_printing() or printer.is_paused()):
if slicer_instance.get_slicer_properties().get("same_device", True) and (printer.is_printing() or printer.is_paused()):
# slicer runs on same device as OctoPrint, slicing while printing is hence disabled
return make_response("Cannot slice on {slicer} while printing due to performance reasons".format(**locals()), 409)
if "gcode" in data and data["gcode"]:
gcode_name = data["gcode"]
if "destination" in data and data["destination"]:
destination = data["destination"]
del data["destination"]
elif "gcode" in data and data["gcode"]:
destination = data["gcode"]
del data["gcode"]
else:
import os
name, _ = os.path.splitext(filename)
gcode_name = name + ".gco"
destination = name + "." + slicer_instance.get_slicer_properties().get("destination_extensions", ["gco", "gcode", "g"])[0]
full_path = gcode_name
full_path = destination
if "path" in data and data["path"]:
full_path = fileManager.join_path(target, data["path"], gcode_name)
full_path = fileManager.join_path(target, data["path"], destination)
else:
path, _ = fileManager.split_path(target, filename)
if path:
full_path = fileManager.join_path(target, path, gcode_name)
full_path = fileManager.join_path(target, path, destination)
# prohibit overwriting the file that is currently being printed
currentOrigin, currentFilename = _getCurrentFile()
@ -525,7 +528,7 @@ def gcodeFileCommand(filename, target):
files = {}
location = url_for(".readGcodeFile", target=target, filename=full_path, _external=True)
result = {
"name": gcode_name,
"name": destination,
"path": full_path,
"origin": FileDestinations.LOCAL,
"refs": {

View file

@ -19,6 +19,8 @@ from octoprint.slicing import UnknownSlicer, SlicerNotConfigured, ProfileAlready
@api.route("/slicing", methods=["GET"])
def slicingListAll():
from octoprint.filemanager import get_extensions
default_slicer = s().get(["slicing", "defaultSlicer"])
if "configured" in request.values and request.values["configured"] in valid_boolean_trues:
@ -30,12 +32,21 @@ def slicingListAll():
for slicer in slicers:
try:
slicer_impl = slicingManager.get_slicer(slicer, require_configured=False)
extensions = set()
for source_file_type in slicer_impl.get_slicer_properties().get("source_file_types", ["model"]):
extensions = extensions.union(get_extensions(source_file_type))
result[slicer] = dict(
key=slicer,
displayName=slicer_impl.get_slicer_properties()["name"],
default=default_slicer == slicer,
configured = slicer_impl.is_slicer_configured(),
profiles=_getSlicingProfilesData(slicer)
configured=slicer_impl.is_slicer_configured(),
profiles=_getSlicingProfilesData(slicer),
extensions=dict(
source=list(extensions),
destination=slicer_impl.get_slicer_properties().get("destination_extensions", ["gco", "gcode", "g"])
)
)
except (UnknownSlicer, SlicerNotConfigured):
# this should never happen

View file

@ -393,7 +393,7 @@ $(function() {
};
self.enableSlicing = function(data) {
return self.loginState.isUser() && self.slicing.enableSlicingDialog();
return self.loginState.isUser() && self.slicing.enableSlicingDialog() && self.slicing.enableSlicingDialogForFile(data.name);
};
self.enableAdditionalData = function(data) {

View file

@ -5,15 +5,16 @@ $(function() {
self.loginState = parameters[0];
self.printerProfiles = parameters[1];
self.file = ko.observable(undefined);
self.target = undefined;
self.file = undefined;
self.path = undefined;
self.data = undefined;
self.defaultSlicer = undefined;
self.defaultProfile = undefined;
self.gcodeFilename = ko.observable();
self.destinationFilename = ko.observable();
self.gcodeFilename = self.destinationFilename; // TODO: for backwards compatiblity, mark deprecated ASAP
self.title = ko.observable();
self.slicer = ko.observable();
@ -22,91 +23,16 @@ $(function() {
self.profiles = ko.observableArray();
self.printerProfile = ko.observable();
self.configured_slicers = ko.pureComputed(function() {
return _.filter(self.slicers(), function(slicer) {
return slicer.configured;
});
});
self.afterSlicingOptions = [
{"value": "none", "text": gettext("Do nothing")},
{"value": "select", "text": gettext("Select for printing")},
{"value": "print", "text": gettext("Start printing")}
];
self.afterSlicing = ko.observable("none");
self.show = function(target, file, force, path) {
if (!self.enableSlicingDialog() && !force) {
return;
self.slicersForFile = function(file) {
if (file === undefined) {
return [];
}
var filename = file.substr(0, file.lastIndexOf("."));
if (filename.lastIndexOf("/") != 0) {
path = path || filename.substr(0, filename.lastIndexOf("/"));
filename = filename.substr(filename.lastIndexOf("/") + 1);
}
self.requestData();
self.target = target;
self.file = file;
self.path = path;
self.title(_.sprintf(gettext("Slicing %(filename)s"), {filename: filename}));
self.gcodeFilename(filename);
self.printerProfile(self.printerProfiles.currentProfile());
self.afterSlicing("none");
$("#slicing_configuration_dialog").modal("show");
};
self.slicer.subscribe(function(newValue) {
self.profilesForSlicer(newValue);
});
self.enableSlicingDialog = ko.pureComputed(function() {
return self.configured_slicers().length > 0;
});
self.enableSliceButton = ko.pureComputed(function() {
return self.gcodeFilename() != undefined
&& self.gcodeFilename().trim() != ""
&& self.slicer() != undefined
&& self.profile() != undefined;
});
self.requestData = function() {
return OctoPrint.slicing.listAllSlicersAndProfiles()
.done(function(data) {
self.fromResponse(data);
});
};
self.fromResponse = function(data) {
self.data = data;
var selectedSlicer = undefined;
self.slicers.removeAll();
_.each(_.values(data), function(slicer) {
var name = slicer.displayName;
if (name == undefined) {
name = slicer.key;
}
if (slicer.default && slicer.configured) {
selectedSlicer = slicer.key;
}
self.slicers.push({
key: slicer.key,
name: name,
configured: slicer.configured
return _.filter(self.configuredSlicers(), function(slicer) {
return _.any(slicer.sourceExtensions, function(extension) {
return _.endsWith(file.toLowerCase(), "." + extension.toLowerCase());
});
});
if (selectedSlicer != undefined) {
self.slicer(selectedSlicer);
self.profilesForSlicer(selectedSlicer);
}
self.defaultSlicer = selectedSlicer;
};
self.profilesForSlicer = function(key) {
@ -136,26 +62,171 @@ $(function() {
})
});
if (selectedProfile != undefined) {
self.profile(selectedProfile);
}
self.profile(selectedProfile);
self.defaultProfile = selectedProfile;
};
self.resetProfiles = function() {
self.profiles.removeAll();
self.profile(undefined);
};
self.configuredSlicers = ko.pureComputed(function() {
return _.filter(self.slicers(), function(slicer) {
return slicer.configured;
});
});
self.matchingSlicers = ko.computed(function() {
var slicers = self.slicersForFile(self.file());
var containsSlicer = function(key) {
return _.any(slicers, function(slicer) {
return slicer.key == key;
});
};
var current = self.slicer();
if (!containsSlicer(current)) {
if (self.defaultSlicer !== undefined && containsSlicer(self.defaultSlicer)) {
self.slicer(self.defaultSlicer);
} else {
self.slicer(undefined);
self.resetProfiles();
}
} else {
self.profilesForSlicer(self.slicer());
}
return slicers;
});
self.afterSlicingOptions = [
{"value": "none", "text": gettext("Do nothing")},
{"value": "select", "text": gettext("Select for printing")},
{"value": "print", "text": gettext("Start printing")}
];
self.afterSlicing = ko.observable("none");
self.show = function(target, file, force, path) {
if (!self.enableSlicingDialog() && !force) {
return;
}
var filename = file.substr(0, file.lastIndexOf("."));
if (filename.lastIndexOf("/") != 0) {
path = path || filename.substr(0, filename.lastIndexOf("/"));
filename = filename.substr(filename.lastIndexOf("/") + 1);
}
self.requestData();
self.target = target;
self.file(file);
self.path = path;
self.title(_.sprintf(gettext("Slicing %(filename)s"), {filename: filename}));
self.destinationFilename(filename);
self.printerProfile(self.printerProfiles.currentProfile());
self.afterSlicing("none");
$("#slicing_configuration_dialog").modal("show");
};
self.slicer.subscribe(function(newValue) {
if (newValue === undefined) {
self.resetProfiles();
} else {
self.profilesForSlicer(newValue);
}
});
self.enableSlicingDialog = ko.pureComputed(function() {
return self.configuredSlicers().length > 0;
});
self.enableSlicingDialogForFile = function(file) {
return self.slicersForFile(file).length > 0;
};
self.enableSliceButton = ko.pureComputed(function() {
return self.destinationFilename() != undefined
&& self.destinationFilename().trim() != ""
&& self.slicer() != undefined
&& self.profile() != undefined;
});
self.requestData = function() {
return OctoPrint.slicing.listAllSlicersAndProfiles()
.done(function(data) {
self.fromResponse(data);
});
};
self.destinationExtension = ko.pureComputed(function() {
var fallback = "???";
if (self.slicer() === undefined) {
return fallback;
}
var slicer = self.data[self.slicer()];
if (slicer === undefined) {
return fallback;
}
var extensions = slicer.extensions;
if (extensions === undefined) {
return fallback;
}
var destinationExtensions = extensions.destination;
if (destinationExtensions === undefined || !destinationExtensions.length) {
return fallback;
}
return destinationExtensions[0] || fallback;
});
self.fromResponse = function(data) {
self.data = data;
var selectedSlicer = undefined;
self.slicers.removeAll();
_.each(_.values(data), function(slicer) {
var name = slicer.displayName;
if (name == undefined) {
name = slicer.key;
}
if (slicer.default && slicer.configured) {
selectedSlicer = slicer.key;
}
var props = {
key: slicer.key,
name: name,
configured: slicer.configured,
sourceExtensions: slicer.extensions.source,
destinationExtensions: slicer.extensions.destination
};
self.slicers.push(props);
});
self.defaultSlicer = selectedSlicer;
};
self.slice = function() {
var gcodeFilename = self._sanitize(self.gcodeFilename());
if (!_.endsWith(gcodeFilename.toLowerCase(), ".gco")
&& !_.endsWith(gcodeFilename.toLowerCase(), ".gcode")
&& !_.endsWith(gcodeFilename.toLowerCase(), ".g")) {
gcodeFilename = gcodeFilename + ".gco";
var destinationFilename = self._sanitize(self.destinationFilename());
var destinationExtensions = self.data[self.slicer()] && self.data[self.slicer()].extensions && self.data[self.slicer()].extensions.destination
? self.data[self.slicer()].extensions.destination
: ["???"];
if (!_.any(destinationExtensions, function(extension) {
return _.endsWith(destinationFilename.toLowerCase(), "." + extension.toLowerCase());
})) {
destinationFilename = destinationFilename + "." + destinationExtensions[0];
}
var data = {
slicer: self.slicer(),
profile: self.profile(),
printerProfile: self.printerProfile(),
gcode: gcodeFilename
destination: destinationFilename
};
if (self.path != undefined) {
@ -168,11 +239,11 @@ $(function() {
data["select"] = true;
}
OctoPrint.files.slice(self.target, self.file, data)
OctoPrint.files.slice(self.target, self.file(), data)
.done(function() {
$("#slicing_configuration_dialog").modal("hide");
self.gcodeFilename(undefined);
self.destinationFilename(undefined);
self.slicer(self.defaultSlicer);
self.profile(self.defaultProfile);
});

View file

@ -13,7 +13,7 @@
<div class="control-group">
<label class="control-label">{{ _('Slicer') }}</label>
<div class="controls">
<select data-bind="options: configured_slicers, optionsText: 'name', optionsValue: 'key', optionsCaption: '{{ _('Select a slicer...') }}', value: slicer, valueAllowUnset: true"></select>
<select data-bind="options: matchingSlicers, optionsText: 'name', optionsValue: 'key', optionsCaption: '{{ _('Select a slicer...') }}', value: slicer, valueAllowUnset: true"></select>
</div>
</div>
<div class="control-group">
@ -29,11 +29,11 @@
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('GCode Filename') }}</label>
<label class="control-label">{{ _('Output Filename') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" data-bind="value: gcodeFilename">
<span class="add-on">.gco</span>
<input type="text" data-bind="value: destinationFilename">
<span class="add-on" data-bind="text: '.' + destinationExtension()"></span>
</div>
</div>
</div>