From e7c21b19780389a490226044174b946f0dedc613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 2 Jul 2016 18:05:11 +0200 Subject: [PATCH 1/3] Support alternative source file types in SlicerPlugin Also limit list of slicers in slicer dialog to compatible slicers. Implements #935 --- src/octoprint/filemanager/__init__.py | 2 +- src/octoprint/plugin/types.py | 11 +- src/octoprint/plugins/cura/__init__.py | 4 +- src/octoprint/server/api/files.py | 29 +- src/octoprint/server/api/slicing.py | 15 +- .../static/js/app/viewmodels/files.js | 2 +- .../static/js/app/viewmodels/slicing.js | 261 +++++++++++------- .../templates/dialogs/slicing.jinja2 | 8 +- 8 files changed, 214 insertions(+), 118 deletions(-) diff --git a/src/octoprint/filemanager/__init__.py b/src/octoprint/filemanager/__init__.py index 284864fb..184d8108 100644 --- a/src/octoprint/filemanager/__init__.py +++ b/src/octoprint/filemanager/__init__.py @@ -284,7 +284,7 @@ class FileManager(object): import time start_time = time.time() - eventManager().fire(Events.SLICING_STARTED, {"stl": source_path, "gcode": dest_path, "progressAvailable": slicer.get_slicer_properties()["progress_report"] if slicer else False}) + eventManager().fire(Events.SLICING_STARTED, {"stl": source_path, "gcode": dest_path, "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 d1987ea6..cd4b6a20 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -1067,6 +1067,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. @@ -1075,7 +1082,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 a525c0de..c8666ffa 100644 --- a/src/octoprint/plugins/cura/__init__.py +++ b/src/octoprint/plugins/cura/__init__.py @@ -166,7 +166,9 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin, type="cura", name="CuraEngine", same_device=True, - progress_report=True + progress_report=True, + source_file_types=["stl"], + destination_extension="gco" ) def get_slicer_default_profile(self): diff --git a/src/octoprint/server/api/files.py b/src/octoprint/server/api/files.py index 7cd55345..9ad0dccf 100644 --- a/src/octoprint/server/api/files.py +++ b/src/octoprint/server/api/files.py @@ -325,25 +325,28 @@ 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"]) # prohibit overwriting the file that is currently being printed currentOrigin, currentFilename = _getCurrentFile() - if currentFilename == gcode_name and currentOrigin == target and (printer.is_printing() or printer.is_paused()): - make_response("Trying to slice into file that is currently being printed: %s" % gcode_name, 409) + if currentFilename == destination and currentOrigin == target and (printer.is_printing() or printer.is_paused()): + make_response("Trying to slice into file that is currently being printed: %s" % destination, 409) if "profile" in data.keys() and data["profile"]: profile = data["profile"] @@ -391,24 +394,24 @@ def gcodeFileCommand(filename, target): printer.select_file(filenameToSelect, sd, print_after_slicing) try: - fileManager.slice(slicer, target, filename, target, gcode_name, + fileManager.slice(slicer, target, filename, target, destination, profile=profile, printer_profile_id=printerProfile, position=position, overrides=overrides, callback=slicing_done, - callback_args=(target, gcode_name, select_after_slicing, print_after_slicing)) + callback_args=(target, destination, select_after_slicing, print_after_slicing)) except octoprint.slicing.UnknownProfile: return make_response("Profile {profile} doesn't exist".format(**locals()), 400) files = {} - location = url_for(".readGcodeFile", target=target, filename=gcode_name, _external=True) + location = url_for(".readGcodeFile", target=target, filename=destination, _external=True) result = { - "name": gcode_name, + "name": destination, "origin": FileDestinations.LOCAL, "refs": { "resource": location, - "download": url_for("index", _external=True) + "downloads/files/" + target + "/" + gcode_name + "download": url_for("index", _external=True) + "downloads/files/" + target + "/" + destination } } diff --git a/src/octoprint/server/api/slicing.py b/src/octoprint/server/api/slicing.py index f1dc3763..3a8af4a0 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 c0086e9b..8768cfe5 100644 --- a/src/octoprint/static/js/app/viewmodels/files.js +++ b/src/octoprint/static/js/app/viewmodels/files.js @@ -298,7 +298,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 fc0b9ce6..cc1ac6c5 100644 --- a/src/octoprint/static/js/app/viewmodels/slicing.js +++ b/src/octoprint/static/js/app/viewmodels/slicing.js @@ -5,14 +5,15 @@ $(function() { self.loginState = parameters[0]; self.printerProfiles = parameters[1]; + self.file = ko.observable(undefined); self.target = undefined; - self.file = 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(); @@ -21,91 +22,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) { - if (!self.enableSlicingDialog() && !force) { - return; + self.slicersForFile = function(file) { + if (file === undefined) { + return []; } - self.requestData(); - self.target = target; - self.file = file; - self.title(_.sprintf(gettext("Slicing %(filename)s"), {filename: self.file})); - self.gcodeFilename(self.file.substr(0, self.file.lastIndexOf("."))); - 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(callback) { - $.ajax({ - url: API_BASEURL + "slicing", - type: "GET", - dataType: "json", - success: function(data) { - self.fromResponse(data); - if (callback !== undefined) { - callback(); - } - } - }); - }; - - 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) { @@ -135,19 +61,164 @@ $(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) { + if (!self.enableSlicingDialog() && !force) { + return; + } + + self.requestData(); + self.target = target; + self.file(file); + self.title(_.sprintf(gettext("Slicing %(filename)s"), {filename: self.file()})); + self.destinationFilename(self.file().substr(0, self.file().lastIndexOf("."))); + 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(callback) { + $.ajax({ + url: API_BASEURL + "slicing", + type: "GET", + dataType: "json", + success: function(data) { + self.fromResponse(data); + if (callback !== undefined) { + callback(); + } + } + }); + }; + + 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 = { @@ -155,7 +226,7 @@ $(function() { slicer: self.slicer(), profile: self.profile(), printerProfile: self.printerProfile(), - gcode: gcodeFilename + destination: destinationFilename }; if (self.afterSlicing() == "print") { @@ -165,7 +236,7 @@ $(function() { } $.ajax({ - url: API_BASEURL + "files/" + self.target + "/" + self.file, + url: API_BASEURL + "files/" + self.target + "/" + self.file(), type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", @@ -174,7 +245,7 @@ $(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 + +
From 944775abde69fa49a2800e00108152299fbde854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 2 Jul 2016 18:07:59 +0200 Subject: [PATCH 2/3] Fixed registered destination extensions in cura plugin --- src/octoprint/plugins/cura/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/octoprint/plugins/cura/__init__.py b/src/octoprint/plugins/cura/__init__.py index c8666ffa..0aa5cabc 100644 --- a/src/octoprint/plugins/cura/__init__.py +++ b/src/octoprint/plugins/cura/__init__.py @@ -168,7 +168,7 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin, same_device=True, progress_report=True, source_file_types=["stl"], - destination_extension="gco" + destination_extensions=["gco", "gcode", "g"] ) def get_slicer_default_profile(self): From dbf1273e9d65ac40cac9a12e6f45e95e217a0fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 2 Jul 2016 18:10:05 +0200 Subject: [PATCH 3/3] Use first destination extension for default target name --- src/octoprint/server/api/files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/octoprint/server/api/files.py b/src/octoprint/server/api/files.py index 9ad0dccf..2f7ceac2 100644 --- a/src/octoprint/server/api/files.py +++ b/src/octoprint/server/api/files.py @@ -341,7 +341,7 @@ def gcodeFileCommand(filename, target): else: import os name, _ = os.path.splitext(filename) - destination = name + "." + slicer_instance.get_slicer_properties().get("destination_extensions", ["gco", "gcode", "g"]) + destination = name + "." + slicer_instance.get_slicer_properties().get("destination_extensions", ["gco", "gcode", "g"])[0] # prohibit overwriting the file that is currently being printed currentOrigin, currentFilename = _getCurrentFile()