WIP to make cura plugin capable of displaying a wizard pane
This commit is contained in:
parent
33cbd3dcbd
commit
3f147c9272
7 changed files with 178 additions and 118 deletions
|
|
@ -25,7 +25,8 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
|
|||
octoprint.plugin.TemplatePlugin,
|
||||
octoprint.plugin.AssetPlugin,
|
||||
octoprint.plugin.BlueprintPlugin,
|
||||
octoprint.plugin.StartupPlugin):
|
||||
octoprint.plugin.StartupPlugin,
|
||||
octoprint.plugin.WizardPlugin):
|
||||
|
||||
def __init__(self):
|
||||
self._logger = logging.getLogger("octoprint.plugins.cura")
|
||||
|
|
@ -37,14 +38,28 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
|
|||
self._cancelled_jobs = []
|
||||
self._job_mutex = threading.Lock()
|
||||
|
||||
def _is_engine_configured(self, cura_engine=None):
|
||||
if cura_engine is None:
|
||||
cura_engine = normalize_path(self._settings.get(["cura_engine"]))
|
||||
return cura_engine is not None and os.path.exists(cura_engine)
|
||||
|
||||
def _is_profile_available(self):
|
||||
return bool(self._slicing_manager.all_profiles("cura", require_configured=False))
|
||||
|
||||
##~~ TemplatePlugin API
|
||||
|
||||
def get_template_configs(self):
|
||||
from flask.ext.babel import gettext
|
||||
return [
|
||||
dict(type="settings", name=gettext("CuraEngine"))
|
||||
dict(type="settings", name=gettext("CuraEngine")),
|
||||
dict(type="wizard", name=gettext("CuraEngine"))
|
||||
]
|
||||
|
||||
##~~ WizardPlugin API
|
||||
|
||||
def is_wizard_required(self):
|
||||
return not self._is_engine_configured() or not self._is_profile_available()
|
||||
|
||||
##~~ StartupPlugin API
|
||||
|
||||
def on_startup(self, host, port):
|
||||
|
|
@ -62,9 +77,6 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
|
|||
@octoprint.plugin.BlueprintPlugin.route("/import", methods=["POST"])
|
||||
def import_cura_profile(self):
|
||||
import datetime
|
||||
import tempfile
|
||||
|
||||
from octoprint.server import slicingManager
|
||||
|
||||
input_name = "file"
|
||||
input_upload_name = input_name + "." + self._settings.global_get(["server", "uploads", "nameSuffix"])
|
||||
|
|
@ -106,12 +118,12 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
|
|||
profile_allow_overwrite = flask.request.values["allowOverwrite"] in valid_boolean_trues
|
||||
|
||||
try:
|
||||
slicingManager.save_profile("cura",
|
||||
profile_name,
|
||||
profile_dict,
|
||||
allow_overwrite=profile_allow_overwrite,
|
||||
display_name=profile_display_name,
|
||||
description=profile_description)
|
||||
self._slicing_manager.save_profile("cura",
|
||||
profile_name,
|
||||
profile_dict,
|
||||
allow_overwrite=profile_allow_overwrite,
|
||||
display_name=profile_display_name,
|
||||
description=profile_description)
|
||||
except octoprint.slicing.ProfileAlreadyExists:
|
||||
self._logger.warn("Profile {profile_name} already exists, aborting".format(**locals()))
|
||||
return flask.make_response("A profile named {profile_name} already exists for slicer cura".format(**locals()), 409)
|
||||
|
|
@ -159,10 +171,11 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
|
|||
|
||||
def is_slicer_configured(self):
|
||||
cura_engine = normalize_path(self._settings.get(["cura_engine"]))
|
||||
if cura_engine is not None and os.path.exists(cura_engine):
|
||||
if self._is_engine_configured(cura_engine=cura_engine):
|
||||
return True
|
||||
else:
|
||||
self._logger.info("Path to CuraEngine has not been configured yet or does not exist (currently set to %r), Cura will not be selectable for slicing" % cura_engine)
|
||||
return False
|
||||
|
||||
def get_slicer_properties(self):
|
||||
return dict(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ $(function() {
|
|||
self.profileDescription = ko.observable();
|
||||
self.profileAllowOverwrite = ko.observable(true);
|
||||
|
||||
self.unconfiguredCuraEngine = ko.observable();
|
||||
self.unconfiguredSlicingProfile = ko.observable();
|
||||
|
||||
self.uploadElement = $("#settings-cura-import");
|
||||
self.uploadButton = $("#settings-cura-import-start");
|
||||
|
||||
|
|
@ -215,6 +218,13 @@ $(function() {
|
|||
});
|
||||
});
|
||||
self.profiles.updateItems(profiles);
|
||||
|
||||
if (self.unconfiguredCuraEngine() === undefined) {
|
||||
self.unconfiguredCuraEngine(self.settings.plugins.cura.cura_engine() == false);
|
||||
}
|
||||
if (self.unconfiguredSlicingProfile() === undefined) {
|
||||
self.unconfiguredSlicingProfile(profiles.length == 0);
|
||||
}
|
||||
};
|
||||
|
||||
self.onBeforeBinding = function () {
|
||||
|
|
@ -233,6 +243,6 @@ $(function() {
|
|||
OCTOPRINT_VIEWMODELS.push([
|
||||
CuraViewModel,
|
||||
["loginStateViewModel", "settingsViewModel", "slicingViewModel"],
|
||||
"#settings_plugin_cura"
|
||||
["#settings_plugin_cura", "#wizard_plugin_cura"]
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,111 +1,9 @@
|
|||
<h4>{{ _('General') }}</h4>
|
||||
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group" data-bind="css: {error: pathBroken, success: pathOk}">
|
||||
<label class="control-label" for="settings-cura-path">{{ _('Path to CuraEngine') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="text" class="input-block-level" data-bind="value: settings.plugins.cura.cura_engine">
|
||||
<button class="btn" type="button" data-bind="click: testEnginePath">{{ _('Test') }}</button>
|
||||
</div>
|
||||
<span class="help-block" data-bind="visible: pathBroken() || pathOk, text: pathText"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: settings.plugins.cura.debug_logging"> {{ _('Log the output of CuraEngine to plugin_cura_engine.log') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% include "snippets/cura_engine.jinja2" %}
|
||||
|
||||
<h4>{{ _('Profiles') }}</h4>
|
||||
|
||||
<div class="pull-right">
|
||||
<small>{{ _('Sort by') }}: <a href="#" data-bind="click: function() { profiles.changeSorting('id'); }">{{ _('Identifier') }} ({{ _('ascending') }})</a> | <a href="#" data-bind="click: function() { profiles.changeSorting('name'); }">{{ _('Name') }} ({{ _('ascending') }})</a></small>
|
||||
</div>
|
||||
<table class="table table-striped table-hover table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="settings_plugin_cura_profiles_key">{{ _('Identifier') }}</th>
|
||||
<th class="settings_plugin_cura_profiles_name">{{ _('Name') }}</th>
|
||||
<th class="settings_plugin_cura_profiles_actions">{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: profiles.paginatedItems">
|
||||
<tr data-bind="attr: {title: description}">
|
||||
<td class="settings_plugin_cura_profiles_key"><span class="icon-star" data-bind="invisible: !isdefault()"></span> <span data-bind="text: key"></span></td>
|
||||
<td class="settings_plugin_cura_profiles_name" data-bind="text: name"></td>
|
||||
<td class="settings_plugin_cura_profiles_actions">
|
||||
<a href="#" class="icon-star" title="{{ _('Make default') }}" data-bind="enable: !isdefault(), css: {disabled: isdefault()}, click: function() { if (!$data.isdefault()) { $root.makeProfileDefault($data); } }"></a> | <a href="#" class="icon-trash" title="{{ _('Delete Profile') }}" data-bind="enable: !isdefault(), css: {disabled: isdefault()}, click: function() { if (!$data.isdefault()) { $root.removeProfile($data); } }"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination pagination-mini pagination-centered">
|
||||
<ul>
|
||||
<li data-bind="css: {disabled: profiles.currentPage() === 0}"><a href="#" data-bind="click: profiles.prevPage">«</a></li>
|
||||
</ul>
|
||||
<ul data-bind="foreach: profiles.pages">
|
||||
<li data-bind="css: { active: $data.number === $root.profiles.currentPage(), disabled: $data.number === -1 }"><a href="#" data-bind="text: $data.text, click: function() { $root.profiles.changePage($data.number); }"></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li data-bind="css: {disabled: profiles.currentPage() === profiles.lastPage()}"><a href="#" data-bind="click: profiles.nextPage">»</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button class="btn pull-right" data-bind="click: function() { $root.showImportProfileDialog() }">{{ _('Import Profile...') }}</button>
|
||||
|
||||
<div id="settings_plugin_cura_import" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<h3>{{ _('Import Existing Cura Profile') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Profile ini file') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-prepend">
|
||||
<span class="btn fileinput-button">
|
||||
<span>{{ _('Browse...') }}</span>
|
||||
<input id="settings-cura-import" type="file" name="file" data-url="{{ url_for("plugin.cura.import_cura_profile") }}">
|
||||
</span>
|
||||
<span class="add-on" data-bind="text: fileName"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Identifier') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: profileName, attr: {placeholder: placeholderName}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Name') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: profileDisplayName, attr: {placeholder: placeholderDisplayName}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Description') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: profileDescription, attr: {placeholder: placeholderDescription}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: profileAllowOverwrite"> {{ _('Overwrite existing file') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" id="settings-cura-import-start">{{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% include "snippets/cura_profiles.jinja2" %}
|
||||
|
||||
{% include "snippets/cura_profile_importer.jinja2" %}
|
||||
|
|
|
|||
34
src/octoprint/plugins/cura/templates/cura_wizard.jinja2
Normal file
34
src/octoprint/plugins/cura/templates/cura_wizard.jinja2
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<div data-bind="visible: unconfiguredCuraEngine">
|
||||
<h3>{{ _('Configure CuraEngine') }}</h3>
|
||||
<p>{% trans %}
|
||||
The path to CuraEngine has not yet been set up, in order to be able to use
|
||||
CuraEngine for slicing it needs to be specified.
|
||||
{% endtrans %}</p>
|
||||
|
||||
<div style="margin-top: 1em">
|
||||
{% include "snippets/cura_engine.jinja2" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: unconfiguredSlicingProfile">
|
||||
<h3>{{ _('Set up a slicing profile') }}</h3>
|
||||
<p>{% trans %}You don't have imported a slicing profile to use for slicing with CuraEngine
|
||||
yet. You should do this now.{% endtrans %}</p>
|
||||
|
||||
{% include "snippets/cura_profiles.jinja2" %}
|
||||
|
||||
<p style="clear: both">{% trans %}
|
||||
<strong>Don't know where to get a profile?</strong> In order to export
|
||||
a slicing profile from the Cura desktop UI, open it, set up your
|
||||
profile, then click on "File" and there on "Save Profile". You can
|
||||
import the .ini-file this creates via the "Import Profile" button.
|
||||
{% endtrans %}</p>
|
||||
</div>
|
||||
|
||||
<p>{% trans %}
|
||||
<strong>Note:</strong> OctoPrint currently only supports slicing with
|
||||
CuraEngine up to 15.04 and importing Cura profiles from Cura versions
|
||||
up to 15.04. Newer Cura releases (e.g. 15.06) do not allow to
|
||||
export the slicing profile anymore and also use a different internal format
|
||||
that will <em>not</em> work with the current version of the Cura Plugin.
|
||||
{% endtrans %}</p>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<form class="form-horizontal">
|
||||
<div class="control-group" data-bind="css: {error: pathBroken, success: pathOk}">
|
||||
<label class="control-label" for="settings-cura-path">{{ _('Path to CuraEngine') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="text" class="input-block-level" data-bind="value: settings.plugins.cura.cura_engine">
|
||||
<button class="btn" type="button" data-bind="click: testEnginePath">{{ _('Test') }}</button>
|
||||
</div>
|
||||
<span class="help-block" data-bind="visible: pathBroken() || pathOk, text: pathText"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: settings.plugins.cura.debug_logging"> {{ _('Log the output of CuraEngine to plugin_cura_engine.log') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<div id="settings_plugin_cura_import" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<h3>{{ _('Import Existing Cura Profile') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Profile ini file') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-prepend">
|
||||
<span class="btn fileinput-button">
|
||||
<span>{{ _('Browse...') }}</span>
|
||||
<input id="settings-cura-import" type="file" name="file" data-url="{{ url_for("plugin.cura.import_cura_profile") }}">
|
||||
</span>
|
||||
<span class="add-on" data-bind="text: fileName"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Identifier') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: profileName, attr: {placeholder: placeholderName}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Name') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: profileDisplayName, attr: {placeholder: placeholderDisplayName}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Description') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: profileDescription, attr: {placeholder: placeholderDescription}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: profileAllowOverwrite"> {{ _('Overwrite existing file') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" id="settings-cura-import-start">{{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<div class="pull-right">
|
||||
<small>{{ _('Sort by') }}: <a href="#" data-bind="click: function() { profiles.changeSorting('id'); }">{{ _('Identifier') }} ({{ _('ascending') }})</a> | <a href="#" data-bind="click: function() { profiles.changeSorting('name'); }">{{ _('Name') }} ({{ _('ascending') }})</a></small>
|
||||
</div>
|
||||
<table class="table table-striped table-hover table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="settings_plugin_cura_profiles_key">{{ _('Identifier') }}</th>
|
||||
<th class="settings_plugin_cura_profiles_name">{{ _('Name') }}</th>
|
||||
<th class="settings_plugin_cura_profiles_actions">{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: profiles.paginatedItems">
|
||||
<tr data-bind="attr: {title: description}">
|
||||
<td class="settings_plugin_cura_profiles_key"><span class="icon-star" data-bind="invisible: !isdefault()"></span> <span data-bind="text: key"></span></td>
|
||||
<td class="settings_plugin_cura_profiles_name" data-bind="text: name"></td>
|
||||
<td class="settings_plugin_cura_profiles_actions">
|
||||
<a href="#" class="icon-star" title="{{ _('Make default') }}" data-bind="enable: !isdefault(), css: {disabled: isdefault()}, click: function() { if (!$data.isdefault()) { $root.makeProfileDefault($data); } }"></a> | <a href="#" class="icon-trash" title="{{ _('Delete Profile') }}" data-bind="enable: !isdefault(), css: {disabled: isdefault()}, click: function() { if (!$data.isdefault()) { $root.removeProfile($data); } }"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination pagination-mini pagination-centered">
|
||||
<ul>
|
||||
<li data-bind="css: {disabled: profiles.currentPage() === 0}"><a href="#" data-bind="click: profiles.prevPage">«</a></li>
|
||||
</ul>
|
||||
<ul data-bind="foreach: profiles.pages">
|
||||
<li data-bind="css: { active: $data.number === $root.profiles.currentPage(), disabled: $data.number === -1 }"><a href="#" data-bind="text: $data.text, click: function() { $root.profiles.changePage($data.number); }"></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li data-bind="css: {disabled: profiles.currentPage() === profiles.lastPage()}"><a href="#" data-bind="click: profiles.nextPage">»</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button class="btn pull-right" data-bind="click: function() { $root.showImportProfileDialog() }">{{ _('Import Profile...') }}</button>
|
||||
|
||||
Loading…
Reference in a new issue