More work on the printer profiles

This commit is contained in:
Gina Häußge 2014-11-29 12:32:55 +01:00
parent a56ddb9ebe
commit 985b0970f1
13 changed files with 151 additions and 22 deletions

View file

@ -150,7 +150,7 @@ class FileManager(object):
def default_slicer(self):
return self._slicing_manager.default_slicer
def slice(self, slicer_name, source_location, source_path, dest_location, dest_path, profile=None, overrides=None, callback=None, callback_args=None):
def slice(self, slicer_name, source_location, source_path, dest_location, dest_path, profile=None, printer_profile_id=None, overrides=None, callback=None, callback_args=None):
absolute_source_path = self.get_absolute_path(source_location, source_path)
def stlProcessed(source_location, source_path, tmp_path, dest_location, dest_path, start_time, callback, callback_args, _error=None, _cancelled=False):
@ -231,6 +231,7 @@ class FileManager(object):
stlProcessed,
callback_args=args,
overrides=overrides,
printer_profile_id=printer_profile_id,
on_progress=self.on_slicing_progress,
on_progress_args=(slicer_name, source_location, source_path, dest_location, dest_path))

View file

@ -11,7 +11,7 @@ import copy
import re
from octoprint.settings import settings
from octoprint.util import dict_merge
from octoprint.util import dict_merge, dict_clean
class SaveError(Exception):
pass
@ -25,6 +25,7 @@ class PrinterProfileManager(object):
default = dict(
id = "_default",
name = "Default",
model = "Generic RepRap Printer",
color = "default",
volume=dict(
width = 200,
@ -79,7 +80,7 @@ class PrinterProfileManager(object):
return False
return self._remove_from_path(self._get_profile_path(identifier))
def save(self, profile, allow_overwrite=False):
def save(self, profile, allow_overwrite=False, make_default=False):
if "id" in profile:
identifier = profile["id"]
elif "name" in profile:
@ -94,7 +95,12 @@ class PrinterProfileManager(object):
settings().set(["printerProfiles", "defaultProfile"], default_profile, defaults=dict(printerProfiles=dict(defaultProfile=self.__class__.default)))
profile["id"] = identifier
profile = dict_clean(profile, self.__class__.default)
self._save_to_path(self._get_profile_path(identifier), profile, allow_overwrite=allow_overwrite)
if make_default:
settings().set(["printerProfiles", "default"], identifier)
return self.get(identifier)
def get_default(self):
@ -207,3 +213,4 @@ class PrinterProfileManager(object):
sanitized_name = ''.join(c for c in name if c in valid_chars)
sanitized_name = sanitized_name.replace(" ", "_")
return sanitized_name

View file

@ -334,12 +334,18 @@ def gcodeFileCommand(filename, target):
else:
profile = None
if "printerProfile" in data.keys() and data["printerProfile"]:
printerProfile = data["printerProfile"]
del data["printerProfile"]
else:
printerProfile = None
override_keys = [k for k in data if k.startswith("profile.") and data[k] is not None]
overrides = dict()
for key in override_keys:
overrides[key[len("profile."):]] = data[key]
ok, result = fileManager.slice(slicer, target, filename, target, gcode_name, profile=profile, overrides=overrides)
ok, result = fileManager.slice(slicer, target, filename, target, gcode_name, profile=profile, printer_profile_id=printerProfile, overrides=overrides)
if ok:
files = {}
location = url_for(".readGcodeFile", target=target, filename=gcode_name, _external=True)

View file

@ -20,7 +20,7 @@ from octoprint.server import printerProfileManager
@api.route("/printerProfiles", methods=["GET"])
def printerProfilesList():
all_profiles = printerProfileManager.get_all()
return jsonify(dict(profiles=all_profiles.values()))
return jsonify(dict(profiles=_convert_profiles(all_profiles)))
@api.route("/printerProfiles", methods=["POST"])
@restricted_access
@ -53,6 +53,7 @@ def printerProfilesGet(identifier):
profile = printerProfileManager.get(identifier)
if profile is None:
make_response("Unknown profile: %s" % identifier, 404)
return jsonify(_convert_profile(profile))
@api.route("/printerProfiles/<string:identifier>", methods=["DELETE"])
@restricted_access
@ -68,7 +69,7 @@ def printerProfilesUpdate(identifier):
json_data = request.json
if not "profile" in json_data:
return None, None, make_response("No profile included in request", 400)
make_response("No profile included in request", 400)
profile = printerProfileManager.get(identifier)
if profile is None:
@ -77,14 +78,19 @@ def printerProfilesUpdate(identifier):
new_profile = json_data["profile"]
new_profile = dict_merge(profile, new_profile)
make_default = False
if "default" in new_profile:
make_default = True
del new_profile["default"]
new_profile["id"] = identifier
if not _validate_profile(new_profile):
return None, None, make_response("Combined profile is invalid, missing obligatory values", 400)
make_response("Combined profile is invalid, missing obligatory values", 400)
try:
saved_profile = printerProfileManager.save(new_profile, allow_overwrite=True)
saved_profile = printerProfileManager.save(new_profile, allow_overwrite=True, make_default=make_default)
except Exception as e:
return None, None, make_response("Could not save profile: %s" % e.message)
make_response("Could not save profile: %s" % e.message)
return jsonify(dict(profile=_convert_profile(saved_profile)))
@ -94,13 +100,14 @@ def _convert_profiles(profiles):
result[identifier] = _convert_profile(profile)
return result
def _convert_profile(profile, default=None):
if default is None:
default = printerProfileManager.get_default()["id"]
def _convert_profile(profile):
default = printerProfileManager.get_default()["id"]
current = printerProfileManager.get_current_or_default()["id"]
converted = copy.deepcopy(profile)
converted["resource"] = url_for(".printerProfilesGet", identifier=profile["id"], _external=True)
converted["default"] = (profile["id"] == default)
converted["current"] = (profile["id"] == current)
return converted
def _validate_profile(profile):

View file

@ -142,7 +142,7 @@ class SlicingManager(object):
import threading
slicer_worker_thread = threading.Thread(target=slicer_worker,
args=(slicer, source_path, dest_path, profile_name, overrides, printer_profile_id, callback, callback_args, callback_kwargs))
args=(slicer, source_path, dest_path, profile_name, overrides, printer_profile, callback, callback_args, callback_kwargs))
slicer_worker_thread.daemon = True
slicer_worker_thread.start()
return True, None

View file

@ -67,16 +67,17 @@ $(function() {
//~~ Initialize view models
var loginStateViewModel = new LoginStateViewModel();
var printerProfilesViewModel = new PrinterProfilesViewModel();
var usersViewModel = new UsersViewModel(loginStateViewModel);
var settingsViewModel = new SettingsViewModel(loginStateViewModel, usersViewModel);
var connectionViewModel = new ConnectionViewModel(loginStateViewModel, settingsViewModel);
var settingsViewModel = new SettingsViewModel(loginStateViewModel, usersViewModel, printerProfilesViewModel);
var connectionViewModel = new ConnectionViewModel(loginStateViewModel, settingsViewModel, printerProfilesViewModel);
var timelapseViewModel = new TimelapseViewModel(loginStateViewModel);
var printerStateViewModel = new PrinterStateViewModel(loginStateViewModel, timelapseViewModel);
var appearanceViewModel = new AppearanceViewModel(settingsViewModel);
var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel);
var controlViewModel = new ControlViewModel(loginStateViewModel, settingsViewModel);
var terminalViewModel = new TerminalViewModel(loginStateViewModel, settingsViewModel);
var slicingViewModel = new SlicingViewModel(loginStateViewModel, connectionViewModel);
var slicingViewModel = new SlicingViewModel(loginStateViewModel, printerProfilesViewModel);
var gcodeFilesViewModel = new GcodeFilesViewModel(printerStateViewModel, loginStateViewModel, slicingViewModel);
var gcodeViewModel = new GcodeViewModel(loginStateViewModel, settingsViewModel);
var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel);
@ -84,6 +85,7 @@ $(function() {
var viewModelMap = {
loginStateViewModel: loginStateViewModel,
printerProfilesViewModel: printerProfilesViewModel,
usersViewModel: usersViewModel,
settingsViewModel: settingsViewModel,
connectionViewModel: connectionViewModel,

View file

@ -17,6 +17,8 @@ function PrinterProfilesViewModel() {
[],
5
);
self.defaultProfile = ko.observable();
self.currentProfile = ko.observable();
self.editorName = ko.observable();
self.editorColor = ko.observable();
@ -51,7 +53,53 @@ function PrinterProfilesViewModel() {
};
self.fromResponse = function(data) {
self.profiles.updateItems(data.profiles);
var items = [];
var defaultProfile = undefined;
var currentProfile = undefined;
_.each(data.profiles, function(entry) {
if (entry.default) {
defaultProfile = entry.id;
}
if (entry.current) {
currentProfile = entry.id;
}
items.push({
id: ko.observable(entry.id),
name: ko.observable(entry.name),
model: ko.observable(entry.model),
volume: {
width: ko.observable(entry.volume.width),
depth: ko.observable(entry.volume.depth),
height: ko.observable(entry.volume.height),
formFactor: ko.observable(entry.volume.formFactor)
},
heatedBed: ko.observable(entry.heatedBed),
axes: {
x: {
speed: ko.observable(entry.axes.x.speed),
inverted: ko.observable(entry.axes.x.inverted)
},
y: {
speed: ko.observable(entry.axes.y.speed),
inverted: ko.observable(entry.axes.y.inverted)
},
z: {
speed: ko.observable(entry.axes.z.speed),
inverted: ko.observable(entry.axes.z.inverted)
},
e: {
speed: ko.observable(entry.axes.e.speed),
inverted: ko.observable(entry.axes.e.inverted)
}
},
isdefault: ko.observable(entry.default),
iscurrent: ko.observable(entry.current),
resource: ko.observable(entry.resource)
});
});
self.profiles.updateItems(items);
self.defaultProfile(defaultProfile);
self.currentProfile(currentProfile);
};
self.addProfile = function() {
@ -132,5 +180,9 @@ function PrinterProfilesViewModel() {
}
return profile;
}
};
self.onStartup = function() {
self.requestData();
};
}

View file

@ -1,8 +1,9 @@
function SettingsViewModel(loginStateViewModel, usersViewModel) {
function SettingsViewModel(loginStateViewModel, usersViewModel, printerProfilesViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.users = usersViewModel;
self.printerProfiles = printerProfilesViewModel;
self.api_enabled = ko.observable(undefined);
self.api_key = ko.observable(undefined);

View file

@ -1,8 +1,8 @@
function SlicingViewModel(loginStateViewModel, connectionViewModel) {
function SlicingViewModel(loginStateViewModel, printerProfilesViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.connection = connectionViewModel;
self.printerProfiles = printerProfilesViewModel;
self.target = undefined;
self.file = undefined;
@ -18,12 +18,14 @@ function SlicingViewModel(loginStateViewModel, connectionViewModel) {
self.slicers = ko.observableArray();
self.profile = ko.observable();
self.profiles = ko.observableArray();
self.printerProfile = ko.observable();
self.show = function(target, file) {
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());
$("#slicing_configuration_dialog").modal("show");
};
@ -121,6 +123,7 @@ function SlicingViewModel(loginStateViewModel, connectionViewModel) {
command: "slice",
slicer: self.slicer(),
profile: self.profile(),
printerProfile: self.printerProfile(),
gcode: gcodeFilename
};

View file

@ -15,7 +15,13 @@
<div class="control-group">
<label class="control-label">{{ _('Slicing Profile') }}</label>
<div class="controls">
<select data-bind="options: profiles, optionsText: 'name', optionsValue: 'key', optionsCaption: '{{ _('Select a profile...') }}', value: profile, valueAllowUnset: true"></select>
<select data-bind="options: profiles, optionsText: 'name', optionsValue: 'key', optionsCaption: '{{ _('Select a slicing profile...') }}', value: profile, valueAllowUnset: true"></select>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Printer Profile') }}</label>
<div class="controls">
<select data-bind="options: printerProfiles.profiles.items, optionsText: 'name', optionsValue: 'id', value: printerProfile, optionsCaption: '{{ _('Select a printer profile...') }}'"></select>
</div>
</div>
<div class="control-group">

View file

@ -695,6 +695,7 @@
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/loginstate.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/navigation.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/printerstate.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/printerprofiles.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/settings.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/slicing.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/temperature.js') }}"></script>

View file

@ -9,6 +9,7 @@
<li class="nav-header">{{ _('Printer') }}</li>
<li class="active"><a href="#settings_serialConnection" data-toggle="tab">{{ _('Serial Connection') }}</a></li>
<li><a href="#settings_printerParameters" data-toggle="tab">{{ _('Printer Parameters') }}</a></li>
<li><a href="#settings_printerProfiles" data-toggle="tab">{{ _('Printer Profiles') }}</a></li>
<li><a href="#settings_temperature" data-toggle="tab">{{ _('Temperatures') }}</a></li>
<li><a href="#settings_terminalFilters" data-toggle="tab">{{ _('Terminal filters') }}</a></li>
<li class="nav-header">{{ _('Features') }}</li>
@ -105,6 +106,31 @@
</div>
</div>
</form>
</div>
<div class="tab-pane" id="settings_printerProfiles">
<h3>{{ _('Printer Profiles') }}</h3>
<table class="table table-striped table-hover table-condensed table-hover" id="settings_printerProfiles_profiles">
<thead>
<tr>
<th class="settings_printerProfiles_profiles_name">{{ _('Name') }}</th>
<th class="settings_printerProfiles_profiles_model">{{ _('Model') }}</th>
<th class="settings_printerProfiles_profiles_action">{{ _('Action') }}</th>
</tr>
</thead>
<tbody data-bind="foreach: printerProfiles.profiles.paginatedItems">
<tr data-bind="attr: {title: name}">
<td class="settings_printerProfiles_profiles_name"><span class="icon-star" data-bind="invisible: !isdefault()"></span> <span data-bind="text: name"></span></td>
<td class="settings_printerProfiles_profiles_model" data-bind="model"></td>
<td class="settings_printerProfiles_profiles_action">
<a href="#" class="icon-pencil" title="{{ _('Edit Profile') }}" data-bind="click: function() { $root.printerProfiles.showEditProfileDialog($data); }"></a>&nbsp;|&nbsp;<a href="#" class="icon-trash" title="{{ _('Delete Profile') }}" data-bind="click: function() { $root.printerProfiles.removeProfile($data); }"></a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane" id="settings_printerParameters">
<form class="form-horizontal">

View file

@ -250,6 +250,23 @@ def dict_merge(a, b):
return result
def dict_clean(a, b):
from copy import deepcopy
if not isinstance(b, dict):
return a
result = deepcopy(a)
for k, v in a.iteritems():
if not k in b:
del result[k]
elif isinstance(v, dict):
result[k] = dict_clean(v, b[k])
else:
result[k] = deepcopy(v)
return result
class Object(object):
pass