diff --git a/src/octoprint/filemanager/__init__.py b/src/octoprint/filemanager/__init__.py index 14f2b99a..94175345 100644 --- a/src/octoprint/filemanager/__init__.py +++ b/src/octoprint/filemanager/__init__.py @@ -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) diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index 4cf435fc..b75b3e1b 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -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): diff --git a/src/octoprint/plugins/cura/__init__.py b/src/octoprint/plugins/cura/__init__.py index 4517efd9..ef2358a6 100644 --- a/src/octoprint/plugins/cura/__init__.py +++ b/src/octoprint/plugins/cura/__init__.py @@ -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): diff --git a/src/octoprint/server/api/files.py b/src/octoprint/server/api/files.py index 7aeb5554..e4e2ef8c 100644 --- a/src/octoprint/server/api/files.py +++ b/src/octoprint/server/api/files.py @@ -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": { diff --git a/src/octoprint/server/api/slicing.py b/src/octoprint/server/api/slicing.py index 96423195..a51e88b9 100644 --- a/src/octoprint/server/api/slicing.py +++ b/src/octoprint/server/api/slicing.py @@ -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 diff --git a/src/octoprint/static/js/app/viewmodels/files.js b/src/octoprint/static/js/app/viewmodels/files.js index 8c7324f1..5be8a68f 100644 --- a/src/octoprint/static/js/app/viewmodels/files.js +++ b/src/octoprint/static/js/app/viewmodels/files.js @@ -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) { diff --git a/src/octoprint/static/js/app/viewmodels/slicing.js b/src/octoprint/static/js/app/viewmodels/slicing.js index 88a92c26..25f4f7f5 100644 --- a/src/octoprint/static/js/app/viewmodels/slicing.js +++ b/src/octoprint/static/js/app/viewmodels/slicing.js @@ -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); }); diff --git a/src/octoprint/templates/dialogs/slicing.jinja2 b/src/octoprint/templates/dialogs/slicing.jinja2 index cdcf88b3..51021027 100644 --- a/src/octoprint/templates/dialogs/slicing.jinja2 +++ b/src/octoprint/templates/dialogs/slicing.jinja2 @@ -13,7 +13,7 @@
- +
@@ -29,11 +29,11 @@
- +
- - .gco + +