Support alternative source file types in SlicerPlugin
Also limit list of slicers in slicer dialog to compatible slicers. Implements #935
This commit is contained in:
parent
73a85646c5
commit
e7c21b1978
8 changed files with 214 additions and 118 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue