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:
commit
b07c352b48
8 changed files with 211 additions and 115 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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