WIP First work towards printer profiles

This commit is contained in:
Gina Häußge 2014-11-28 09:32:44 +01:00
parent e55f11c5b9
commit 1957d2bb58
18 changed files with 568 additions and 46 deletions

View file

@ -221,7 +221,7 @@ class SlicerPlugin(Plugin):
def save_slicer_profile(self, path, profile, allow_overwrite=True, overrides=None):
pass
def do_slice(self, model_path, machinecode_path=None, profile_path=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None):
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None):
pass
def cancel_slicing(self, machinecode_path):

View file

@ -229,7 +229,7 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
self._save_profile(path, new_profile, allow_overwrite=allow_overwrite)
def do_slice(self, model_path, machinecode_path=None, profile_path=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None):
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None):
if not profile_path:
profile_path = s.get(["default_profile"])
if not machinecode_path:
@ -244,7 +244,7 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
self._cura_logger.info("### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path))
engine_settings = self._convert_to_engine(profile_path)
engine_settings = self._convert_to_engine(profile_path, printer_profile)
executable = s.get(["cura_engine"])
if not executable:
@ -385,8 +385,8 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
with open(path, "wb") as f:
yaml.safe_dump(profile, f, default_flow_style=False, indent=" ", allow_unicode=True)
def _convert_to_engine(self, profile_path):
profile = Profile(self._load_profile(profile_path))
def _convert_to_engine(self, profile_path, printer_profile):
profile = Profile(self._load_profile(profile_path), printer_profile)
return profile.convert_to_engine()
def _sanitize_name(name):

View file

@ -515,8 +515,9 @@ class Profile(object):
return result
def __init__(self, profile, overrides=None):
def __init__(self, profile, printer_profile, overrides=None):
self._profile = self.__class__.merge_profile(profile, overrides=overrides)
self._printer_profile = printer_profile
def profile(self):
import copy
@ -524,23 +525,22 @@ class Profile(object):
def get(self, key):
if key in ("machine_width", "machine_depth", "machine_center_is_zero"):
bedDimensions = s.globalGet(["printerParameters", "bedDimensions"])
circular = bedDimensions["circular"] if "circular" in bedDimensions else False
radius = bedDimensions["radius"] if "radius" in bedDimensions and bedDimensions["radius"] is not None else 0
if key == "machine_width":
return radius * 2 if circular else bedDimensions["x"]
return self._printer_profile["volume"]["width"]
elif key == "machine_depth":
return radius * 2 if circular else bedDimensions["y"]
return self._printer_profile["volume"]["depth"]
elif key == "machine_height":
return self._printer_profile["volume"]["height"]
elif key == "machine_center_is_zero":
return circular
return self._printer_profile["volume"]["formFactor"] == "circular"
else:
return None
elif key == "extruder_amount":
return s.globalGetInt(["printerParameters", "numExtruders"])
return self._printer_profile["extruder"]["count"]
elif key.startswith("extruder_offset_"):
extruder_offsets = s.globalGet(["printerParameters", "extruderOffsets"])
extruder_offsets = self._printer_profile["extruder"]["offsets"]
match = Profile.regex_extruder_offset.match(key)
if not match:
return 0.0
@ -553,9 +553,13 @@ class Profile(object):
return 0.0
if number >= len(extruder_offsets):
return 0.0
if not axis in extruder_offsets[number]:
if axis == "x":
return extruder_offsets[number][0]
elif axis == "y":
return extruder_offsets[number][1]
else:
return 0.0
return extruder_offsets[number][axis]
elif key.startswith("filament_diameter"):
match = Profile.regex_filament_diameter.match(key)

View file

@ -30,13 +30,14 @@ def getConnectionOptions():
}
class Printer():
def __init__(self, fileManager, analysisQueue):
def __init__(self, fileManager, analysisQueue, printerProfileManager):
from collections import deque
self._logger = logging.getLogger(__name__)
self._analysisQueue = analysisQueue
self._fileManager = fileManager
self._printerProfileManager = printerProfileManager
# state
# TODO do we really need to hold the temperature here?
@ -159,7 +160,7 @@ class Printer():
#~~ printer commands
def connect(self, port=None, baudrate=None):
def connect(self, port=None, baudrate=None, profile=None):
"""
Connects to the printer. If port and/or baudrate is provided, uses these settings, otherwise autodetection
will be attempted.
@ -167,6 +168,7 @@ class Printer():
if self._comm is not None:
self._comm.close()
self._comm = comm.MachineCom(port, baudrate, callbackObject=self)
self._printerProfileManager.select(profile)
def disconnect(self):
"""
@ -175,6 +177,7 @@ class Printer():
if self._comm is not None:
self._comm.close()
self._comm = None
self._printerProfileManager.deselect()
eventManager().fire(Events.DISCONNECTED)
def command(self, command):
@ -652,7 +655,8 @@ class Printer():
return "Closed", None, None
port, baudrate = self._comm.getConnection()
return self._comm.getStateString(), port, baudrate
printer_profile = self._printerProfileManager.get_current_or_default()
return self._comm.getStateString(), port, baudrate, printer_profile
def isClosedOrError(self):
return self._comm is None or self._comm.isClosedOrError()

View file

@ -0,0 +1,209 @@
# coding=utf-8
from __future__ import absolute_import
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
import os
import copy
import re
from octoprint.settings import settings
from octoprint.util import dict_merge
class SaveError(Exception):
pass
class BedTypes(object):
RECTANGULAR = "rectangular"
CIRCULAR = "circular"
class PrinterProfileManager(object):
default = dict(
id = "_default",
name = "Default",
color = "default",
volume=dict(
width = 200,
depth = 200,
height = 200,
formFactor = BedTypes.RECTANGULAR,
),
heatedBed = False,
extruder=dict(
count = 1,
offsets = [
(0, 0)
]
),
axes=dict(
x = dict(speed=6000, inverted=False),
y = dict(speed=6000, inverted=False),
z = dict(speed=200, inverted=False),
e = dict(speed=300, inverted=False)
)
)
def __init__(self):
self._current = None
self._folder = settings().getBaseFolder("printerProfiles")
def select(self, identifier):
if identifier is None or not self.exists(identifier):
self._current = self.get_default()
return False
else:
self._current = self.get(identifier)
return True
def deselect(self):
self._current = None
def get_all(self):
return self._load_all()
def get(self, identifier):
if identifier == "_default":
return self._load_default()
elif self.exists(identifier):
return self._load_from_path(self._get_profile_path(identifier))
else:
return None
def remove(self, identifier):
if identifier == "_default":
return False
return self._remove_from_path(self._get_profile_path(identifier))
def save(self, profile, allow_overwrite=False):
if "id" in profile:
identifier = profile["id"]
elif "name" in profile:
identifier = profile["name"]
else:
raise ValueError("profile must contain either id or name")
identifier = self._sanitize(identifier)
if identifier == "_default":
default_profile = dict_merge(self._load_default(), profile)
settings().set(["printerProfiles", "defaultProfile"], default_profile, defaults=dict(printerProfiles=dict(defaultProfile=self.__class__.default)))
profile["id"] = identifier
self._save_to_path(self._get_profile_path(identifier), profile, allow_overwrite=allow_overwrite)
return self.get(identifier)
def get_default(self):
default = settings().get(["printerProfiles", "default"])
if default is not None and self.exists(default):
profile = self.get(default)
if profile is not None:
return profile
return self._load_default()
def set_default(self, identifier):
all_identifiers = self._load_all_identifiers().keys()
if identifier is not None and not identifier in all_identifiers:
return
settings().set(["printerProfile", "default"], identifier)
settings().save()
def get_current_or_default(self):
if self._current is not None:
return self._current
else:
return self.get_default()
def get_current(self):
return self._current
def exists(self, identifier):
if identifier is None:
return False
elif identifier == "_default":
return True
else:
path = self._get_profile_path(identifier)
return os.path.exists(path) and os.path.isfile(path)
def _load_all(self):
all_identifiers = self._load_all_identifiers()
results = dict()
for identifier, path in all_identifiers.items():
if identifier == "_default":
profile = self._load_default()
else:
profile = self._load_from_path(path)
if profile is None:
continue
results[identifier] = profile
return results
def _load_all_identifiers(self):
results = dict(_default=None)
for entry in os.listdir(self._folder):
if entry.startswith(".") or not entry.endswith(".profile") or entry == "_default.profile":
continue
path = os.path.join(self._folder, entry)
if not os.path.isfile(path):
continue
identifier = entry[:-len(".profile")]
results[identifier] = path
return results
def _load_from_path(self, path):
if not os.path.exists(path) or not os.path.isfile(path):
return None
import yaml
with open(path) as f:
profile = yaml.safe_load(f)
return profile
def _save_to_path(self, path, profile, allow_overwrite=False):
if os.path.exists(path) and not allow_overwrite:
raise SaveError("Profile %s already exists and not allowed to overwrite" % profile["id"])
import yaml
with open(path, "wb") as f:
try:
yaml.safe_dump(profile, f)
except Exception as e:
raise SaveError("Cannot save profile %s: %s" % (profile["id"], e.message))
def _remove_from_path(self, path):
try:
os.remove(path)
return True
except:
return False
def _load_default(self):
default_profile = settings().get(["printerProfiles", "defaultProfile"])
return dict_merge(copy.deepcopy(self.__class__.default), default_profile)
def _get_profile_path(self, identifier):
return os.path.join(self._folder, "%s.profile" % identifier)
def _sanitize(self, name):
if name is None:
return None
if "/" in name or "\\" in name:
raise ValueError("name must not contain / or \\")
import string
valid_chars = "-_.() {ascii}{digits}".format(ascii=string.ascii_letters, digits=string.digits)
sanitized_name = ''.join(c for c in name if c in valid_chars)
sanitized_name = sanitized_name.replace(" ", "_")
return sanitized_name

View file

@ -27,6 +27,7 @@ babel = Babel(app)
debug = False
printer = None
printerProfileManager = None
fileManager = None
slicingManager = None
analysisQueue = None
@ -42,6 +43,7 @@ user_permission = Permission(RoleNeed("user"))
# only import the octoprint stuff down here, as it might depend on things defined above to be initialized already
from octoprint.printer import Printer, getConnectionOptions
from octoprint.printer.profile import PrinterProfileManager
from octoprint.settings import settings
import octoprint.users as users
import octoprint.events as events
@ -200,6 +202,7 @@ class Server():
self._checkForRoot()
global printer
global printerProfileManager
global fileManager
global slicingManager
global analysisQueue
@ -226,13 +229,14 @@ class Server():
# then initialize the plugin manager
pluginManager = octoprint.plugin.plugin_manager(init=True)
printerProfileManager = PrinterProfileManager()
eventManager = events.eventManager()
analysisQueue = octoprint.filemanager.analysis.AnalysisQueue()
slicingManager = octoprint.slicing.SlicingManager(settings().getBaseFolder("slicingProfiles"))
slicingManager = octoprint.slicing.SlicingManager(settings().getBaseFolder("slicingProfiles"), printerProfileManager)
storage_managers = dict()
storage_managers[octoprint.filemanager.FileDestinations.LOCAL] = octoprint.filemanager.storage.LocalFileStorage(settings().getBaseFolder("uploads"))
fileManager = octoprint.filemanager.FileManager(analysisQueue, slicingManager, initial_storage_managers=storage_managers)
printer = Printer(fileManager, analysisQueue)
printer = Printer(fileManager, analysisQueue, printerProfileManager)
appSessionManager = util.flask.AppSessionManager()
# configure additional template folders for jinja2
@ -341,9 +345,10 @@ class Server():
eventManager.fire(events.Events.STARTUP)
if settings().getBoolean(["serial", "autoconnect"]):
(port, baudrate) = settings().get(["serial", "port"]), settings().getInt(["serial", "baudrate"])
printer_profile = printerProfileManager.get_default()
connectionOptions = getConnectionOptions()
if port in connectionOptions["ports"]:
printer.connect(port, baudrate)
printer.connect(port=port, baudrate=baudrate, profile=printer_profile["id"] if "id" in printer_profile else "_default")
# start up watchdogs
observer = Observer()

View file

@ -36,6 +36,7 @@ from . import timelapse as api_timelapse
from . import users as api_users
from . import log as api_logs
from . import slicing as api_slicing
from . import printer_profiles as api_printer_profiles
VERSION = "0.1"

View file

@ -9,7 +9,7 @@ from flask import request, jsonify, make_response
from octoprint.settings import settings
from octoprint.printer import getConnectionOptions
from octoprint.server import printer, NO_CONTENT
from octoprint.server import printer, printerProfileManager, NO_CONTENT
from octoprint.server.api import api
from octoprint.server.util.flask import restricted_access
import octoprint.util as util
@ -17,13 +17,15 @@ import octoprint.util as util
@api.route("/connection", methods=["GET"])
def connectionState():
state, port, baudrate = printer.getCurrentConnection()
state, port, baudrate, printer_profile = printer.getCurrentConnection()
current = {
"state": state,
"port": port,
"baudrate": baudrate
"baudrate": baudrate,
"printerProfile": printer_profile["id"] if printer_profile is not None and "id" in printer_profile else "_default"
}
return jsonify({"current": current, "options": getConnectionOptions()})
return jsonify({"current": current, "options": _get_options()})
@api.route("/connection", methods=["POST"])
@ -39,28 +41,48 @@ def connectionCommand():
return response
if command == "connect":
options = getConnectionOptions()
connection_options = getConnectionOptions()
port = None
baudrate = None
printerProfile = None
if "port" in data.keys():
port = data["port"]
if port not in options["ports"]:
if port not in connection_options["ports"]:
return make_response("Invalid port: %s" % port, 400)
if "baudrate" in data.keys():
baudrate = data["baudrate"]
if baudrate not in options["baudrates"]:
if baudrate not in connection_options["baudrates"]:
return make_response("Invalid baudrate: %d" % baudrate, 400)
if "printerProfile" in data.keys():
printerProfile = data["printerProfile"]
if not printerProfileManager.exists(printerProfile):
return make_response("Invalid printer profile: %s" % printerProfile, 400)
if "save" in data.keys() and data["save"]:
settings().set(["serial", "port"], port)
settings().setInt(["serial", "baudrate"], baudrate)
printerProfileManager.set_default(printerProfile)
if "autoconnect" in data.keys():
settings().setBoolean(["serial", "autoconnect"], data["autoconnect"])
settings().save()
printer.connect(port=port, baudrate=baudrate)
printer.connect(port=port, baudrate=baudrate, profile=printerProfile)
elif command == "disconnect":
printer.disconnect()
return NO_CONTENT
def _get_options():
connection_options = getConnectionOptions()
profile_options = printerProfileManager.get_all()
default_profile = printerProfileManager.get_default()
options = dict(
ports=connection_options["ports"],
baudrates=connection_options["baudrates"],
printerProfiles=[dict(id=printer_profile["id"], name=printer_profile["name"] if "name" in printer_profile else printer_profile["id"]) for printer_profile in profile_options.values() if "id" in printer_profile],
portPreference=connection_options["portPreference"],
baudratePreference=connection_options["baudratePreference"],
printerProfilePreference=default_profile["id"] if "id" in default_profile else None
)
return options

View file

@ -0,0 +1,108 @@
# coding=utf-8
from __future__ import absolute_import
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
import copy
from flask import jsonify, make_response, request, url_for
from octoprint.server.api import api, NO_CONTENT
from octoprint.server.util.flask import restricted_access
from octoprint.util import dict_merge
from octoprint.server import printerProfileManager
@api.route("/printerProfiles", methods=["GET"])
def printerProfilesList():
all_profiles = printerProfileManager.get_all()
return jsonify(dict(profiles=all_profiles.values()))
@api.route("/printerProfiles", methods=["POST"])
def printerProfilesAdd():
if not "application/json" in request.headers["Content-Type"]:
return None, None, make_response("Expected content-type JSON", 400)
json_data = request.json
if not "profile" in json_data:
return None, None, make_response("No profile included in request", 400)
base_profile = printerProfileManager.get_default()
if "basedOn" in json_data and isinstance(json_data["basedOn"], basestring):
other_profile = printerProfileManager.get(json_data["basedOn"])
if other_profile is not None:
base_profile = other_profile
if "id" in base_profile:
del base_profile["id"]
if "name" in base_profile:
del base_profile["name"]
profile = dict_merge(base_profile, json_data["profile"])
if not "id" in profile and not "name" in profile:
return None, None, make_response("Profile must contain either id or name")
elif not "name" in profile:
return None, None, make_response("Profile must contain a name")
try:
saved_profile = printerProfileManager.save(profile, allow_overwrite=False)
except Exception as e:
return None, None, make_response("Could not save profile: %s" % e.message)
return jsonify(dict(profile=_convert_profile(saved_profile)))
@api.route("/printerProfiles/<string:identifier>", methods=["GET"])
def printerProfilesGet(identifier):
profile = printerProfileManager.get(identifier)
if profile is None:
make_response("Unknown profile: %s" % identifier, 404)
@api.route("/printerProfiles/<string:identifier>", methods=["DELETE"])
@restricted_access
def printerProfilesDelete(identifier):
printerProfileManager.remove(identifier)
return NO_CONTENT
@api.route("/printerProfiles/<string:identifier>", methods=["PATCH"])
@restricted_access
def printerProfilesUpdate(identifier ):
if not "application/json" in request.headers["Content-Type"]:
return None, None, make_response("Expected content-type JSON", 400)
json_data = request.json
if not "profile" in json_data:
return None, None, make_response("No profile included in request", 400)
profile = printerProfileManager.get(identifier)
if profile is None:
profile = printerProfileManager.get_default()
new_profile = json_data["profile"]
new_profile = dict_merge(profile, new_profile)
new_profile["id"] = identifier
try:
saved_profile = printerProfileManager.save(new_profile, allow_overwrite=True)
except Exception as e:
return None, None, make_response("Could not save profile: %s" % e.message)
return jsonify(dict(profile=_convert_profile(saved_profile)))
def _convert_profiles(profiles):
result = dict()
for identifier, profile in profiles.items():
result[identifier] = _convert_profile(profile)
return result
def _convert_profile(profile, default=None):
if default is None:
default = printerProfileManager.get_default()["id"]
converted = copy.deepcopy(profile)
converted["resource"] = url_for(".printerProfilesGet", identifier=profile["id"], _external=True)
converted["default"] = (profile["id"] == default)
return converted

View file

@ -94,7 +94,8 @@ default_settings = {
"virtualSd": None,
"watched": None,
"plugins": None,
"slicingProfiles": None
"slicingProfiles": None,
"printerProfiles": None
},
"temperature": {
"profiles": [
@ -102,6 +103,10 @@ default_settings = {
{"name": "PLA", "extruder" : 180, "bed" : 60 }
]
},
"printerProfiles": {
"default": None,
"defaultProfile": {}
},
"printerParameters": {
"movementSpeed": {
"x": 6000,

View file

@ -49,8 +49,9 @@ class SlicingCancelled(BaseException):
class SlicingManager(object):
def __init__(self, profile_path):
def __init__(self, profile_path, printer_profile_manager):
self._profile_path = profile_path
self._printer_profile_manager = printer_profile_manager
self._slicers = dict()
self._slicer_names = dict()
@ -94,7 +95,7 @@ class SlicingManager(object):
def get_slicer(self, slicer, require_configured=True):
return self._slicers[slicer] if slicer in self._slicers and (not require_configured or self._slicers[slicer].is_slicer_configured()) else None
def slice(self, slicer_name, source_path, dest_path, profile_name, callback, callback_args=None, callback_kwargs=None, overrides=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None):
def slice(self, slicer_name, source_path, dest_path, profile_name, callback, callback_args=None, callback_kwargs=None, overrides=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None, printer_profile_id=None):
if callback_args is None:
callback_args = ()
if callback_kwargs is None:
@ -111,12 +112,20 @@ class SlicingManager(object):
slicer = self.get_slicer(slicer_name)
def slicer_worker(slicer, model_path, machinecode_path, profile_name, overrides, callback, callback_args, callback_kwargs):
printer_profile = None
if printer_profile_id is not None:
printer_profile = self._printer_profile_manager.get(printer_profile_id)
if printer_profile is None:
printer_profile = self._printer_profile_manager.get_current_or_default()["id"]
def slicer_worker(slicer, model_path, machinecode_path, profile_name, overrides, printer_profile, callback, callback_args, callback_kwargs):
try:
slicer_name = slicer.get_slicer_properties()["type"]
with self.temporary_profile(slicer_name, name=profile_name, overrides=overrides) as profile_path:
ok, result = slicer.do_slice(
model_path,
printer_profile,
machinecode_path=machinecode_path,
profile_path=profile_path,
on_progress=on_progress,
@ -133,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, callback, callback_args, callback_kwargs))
args=(slicer, source_path, dest_path, profile_name, overrides, printer_profile_id, callback, callback_args, callback_kwargs))
slicer_worker_thread.daemon = True
slicer_worker_thread.start()
return True, None

File diff suppressed because one or more lines are too long

View file

@ -76,7 +76,7 @@ $(function() {
var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel);
var controlViewModel = new ControlViewModel(loginStateViewModel, settingsViewModel);
var terminalViewModel = new TerminalViewModel(loginStateViewModel, settingsViewModel);
var slicingViewModel = new SlicingViewModel(loginStateViewModel);
var slicingViewModel = new SlicingViewModel(loginStateViewModel, connectionViewModel);
var gcodeFilesViewModel = new GcodeFilesViewModel(printerStateViewModel, loginStateViewModel, slicingViewModel);
var gcodeViewModel = new GcodeViewModel(loginStateViewModel, settingsViewModel);
var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel);

View file

@ -6,8 +6,10 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
self.portOptions = ko.observableArray(undefined);
self.baudrateOptions = ko.observableArray(undefined);
self.printerOptions = ko.observableArray(undefined);
self.selectedPort = ko.observable(undefined);
self.selectedBaudrate = ko.observable(undefined);
self.selectedPrinter = ko.observable(undefined);
self.saveSettings = ko.observable(undefined);
self.autoconnect = ko.observable(undefined);
@ -24,7 +26,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
return gettext("Connect");
else
return gettext("Disconnect");
})
});
self.previousIsOperational = undefined;
@ -37,32 +39,37 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
self.fromResponse(response);
}
})
}
};
self.fromResponse = function(response) {
var ports = response.options.ports;
var baudrates = response.options.baudrates;
var printerProfiles = response.options.printerProfiles;
var portPreference = response.options.portPreference;
var baudratePreference = response.options.baudratePreference;
var printerPreference = response.options.printerProfilePreference;
self.portOptions(ports);
self.baudrateOptions(baudrates);
self.printerOptions(printerProfiles);
if (!self.selectedPort() && ports && ports.indexOf(portPreference) >= 0)
self.selectedPort(portPreference);
if (!self.selectedBaudrate() && baudrates && baudrates.indexOf(baudratePreference) >= 0)
self.selectedBaudrate(baudratePreference);
if (!self.selectedPrinter() && printerProfiles && printerProfiles.indexOf(printerPreference) >= 0)
self.selectedPrinter(printerPreference);
self.saveSettings(false);
}
};
self.fromHistoryData = function(data) {
self._processStateData(data.state);
}
};
self.fromCurrentData = function(data) {
self._processStateData(data.state);
}
};
self._processStateData = function(data) {
self.previousIsOperational = self.isOperational();
@ -85,7 +92,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
connectionTab.collapse("show");
}
}
}
};
self.connect = function() {
if (self.isErrorOrClosed()) {
@ -93,6 +100,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
"command": "connect",
"port": self.selectedPort(),
"baudrate": self.selectedBaudrate(),
"printerProfile": self.selectedPrinter(),
"autoconnect": self.settings.serial_autoconnect()
};
@ -119,7 +127,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
data: JSON.stringify({"command": "disconnect"})
})
}
}
};
self.onStartup = function() {
self.requestData();

View file

@ -0,0 +1,144 @@
function PrinterProfilesViewModel() {
var self = this;
self.selected = ko.observable();
self.default = ko.observable();
self.available = ko.observableArray();
self.currentProfile = ko.computed(function() {
var currentProfile = undefined;
var defaultProfile = undefined;
_.each(self.available(), function(profile) {
if (profile.id() == self.selected()) {
currentProfile = profile;
}
if (profile.id() == self.default()) {
defaultProfile = profile;
}
});
if (currentProfile != undefined) {
return currentProfile;
} else {
return defaultProfile;
}
});
self.editorName = ko.observable();
self.editorColor = ko.observable();
self.editorIdentifier = ko.observable();
self.editorWidth = ko.observable();
self.editorDepth = ko.observable();
self.editorHeight = ko.observable();
self.editorFormFactor = ko.observable();
self.editorHeatedBed = ko.observable();
self.editorExtruders = ko.observable();
self.editorExtruderOffsets = ko.observableArray();
self.editorAxisXSpeed = ko.observable();
self.editorAxisYSpeed = ko.observable();
self.editorAxisZSpeed = ko.observable();
self.editorAxisESpeed = ko.observable();
self.editorAxisXInverted = ko.observable(false);
self.editorAxisYInverted = ko.observable(false);
self.editorAxisZInverted = ko.observable(false);
self.requestData = function() {
$.ajax({
url: API_BASEURL + "printerProfiles",
type: "GET",
dataType: "json",
success: self.fromResponse
})
};
self.fromResponse = function(data) {
self.selectedProfile(data.current);
self.defaultProfile(data.default);
self.availableProfiles(data.profiles);
};
self.addProfile = function() {
var profile = self._editorData();
$.ajax({
url: API_BASEURL + "printerProfiles/" + profile.id,
type: "PUT",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({profile: profile})
});
};
self.removeProfile = function(data) {
$.ajax({
url: data.resource,
type: "DELETE",
dataType: "json"
})
};
self.updateProfile = function(identifier, profile) {
if (profile == undefined) {
profile = self._editorData();
}
$.ajax({
url: API_BASEURL + "printerProfiles/" + profile.id,
type: "PATCH",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({profile: profile})
});
};
self._editorData = function() {
var profile = {
name: self.editorName(),
color: self.editorColor(),
id: self.editorIdentifier(),
volume: {
width: self.editorWidth(),
depth: self.editorDepth(),
height: self.editorHeight(),
type: self.editorFormFactor()
},
heatedBed: self.editorHeatedBed(),
extruder: {
count: self.editorExtruders(),
offsets: [
[0.0, 0.0]
]
},
axes: {
x: {
speed: self.editorAxisXSpeed(),
inverted: self.editorAxisXInverted()
},
y: {
speed: self.editorAxisYSpeed(),
inverted: self.editorAxisYInverted()
},
z: {
speed: self.editorAxisZSpeed(),
inverted: self.editorAxisZInverted()
}
}
};
if (self.editorExtruders() > 1) {
for (var i = 1; i < self.editorExtruders(); i++) {
var offset = [0.0, 0.0];
if (i < self.editorExtruderOffsets().length) {
offset = self.editorExtruderOffsets()[i];
}
profile.extruder.offsets.push(offset);
}
}
return profile;
}
}

View file

@ -1,7 +1,8 @@
function SlicingViewModel(loginStateViewModel) {
function SlicingViewModel(loginStateViewModel, connectionViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.connection = connectionViewModel;
self.target = undefined;
self.file = undefined;

View file

@ -338,7 +338,7 @@ ul.dropdown-menu li a {
/** Connection settings */
#connection_ports, #connection_baudrates {
#connection_ports, #connection_baudrates, #connection_printers {
width: 100%;
}

View file

@ -133,6 +133,8 @@
<select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
<label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Baudrate') }}</label>
<select id="connection_baudrates" data-bind="options: baudrateOptions, optionsCaption: 'AUTO', value: selectedBaudrate, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
<label for="connection_printers" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Printer Profile') }}</label>
<select id="connection_printers" data-bind="options: printerOptions, optionsText: 'name', optionsValue: 'id', value: selectedPrinter, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
<label class="checkbox">
<input type="checkbox" id="connection_save" data-bind="checked: saveSettings, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> {{ _('Save connection settings') }}
</label>