WIP to make cura plugin capable of displaying a wizard pane

This commit is contained in:
Gina Häußge 2015-07-14 20:22:02 +02:00
parent 33cbd3dcbd
commit 3f147c9272
7 changed files with 178 additions and 118 deletions

View file

@ -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(

View file

@ -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"]
]);
});

View file

@ -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>&nbsp;|&nbsp;<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">&times;</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" %}

View 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>

View file

@ -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>

View file

@ -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">&times;</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>

View file

@ -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>&nbsp;|&nbsp;<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>