More work on the printer profiles
This commit is contained in:
parent
a56ddb9ebe
commit
985b0970f1
13 changed files with 151 additions and 22 deletions
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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> | <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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue