work in progress. still buggy.
This commit is contained in:
parent
5c9567309f
commit
040035b7ff
11 changed files with 948 additions and 9 deletions
234
src/octoprint/plugins/lasercutterprofiles/__init__.py
Normal file
234
src/octoprint/plugins/lasercutterprofiles/__init__.py
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
# 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 logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import flask
|
||||
import socket
|
||||
|
||||
import octoprint.plugin
|
||||
import octoprint.util
|
||||
from octoprint.util import dict_merge
|
||||
import octoprint.settings
|
||||
from octoprint.server import NO_CONTENT
|
||||
|
||||
|
||||
from .profile import LaserCutterProfileManager, InvalidProfileError, CouldNotOverwriteError
|
||||
|
||||
import copy
|
||||
from octoprint.server.util.flask import restricted_access
|
||||
from flask import Blueprint, request, jsonify, abort, current_app, session, make_response, url_for
|
||||
|
||||
|
||||
blueprint = flask.Blueprint("plugin.lasercutterprofiles", __name__)
|
||||
laserCutterProfileManager = LaserCutterProfileManager()
|
||||
|
||||
@blueprint.route("/profiles", methods=["GET"])
|
||||
def laserCutterProfilesList():
|
||||
all_profiles = laserCutterProfileManager.get_all()
|
||||
return jsonify(dict(profiles=_convert_profiles(all_profiles)))
|
||||
|
||||
@blueprint.route("/profiles", methods=["POST"])
|
||||
@restricted_access
|
||||
def laserCutterProfilesAdd():
|
||||
if not "application/json" in request.headers["Content-Type"]:
|
||||
return make_response("Expected content-type JSON", 400)
|
||||
|
||||
try:
|
||||
json_data = request.json
|
||||
except JSONBadRequest:
|
||||
return make_response("Malformed JSON body in request", 400)
|
||||
|
||||
if not "profile" in json_data:
|
||||
return make_response("No profile included in request", 400)
|
||||
|
||||
base_profile = laserCutterProfileManager.get_default()
|
||||
if "basedOn" in json_data and isinstance(json_data["basedOn"], basestring):
|
||||
other_profile = laserCutterProfileManager.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"]
|
||||
if "default" in base_profile:
|
||||
del base_profile["default"]
|
||||
|
||||
new_profile = json_data["profile"]
|
||||
make_default = False
|
||||
if "default" in new_profile:
|
||||
make_default = True
|
||||
del new_profile["default"]
|
||||
|
||||
profile = dict_merge(base_profile, new_profile)
|
||||
try:
|
||||
saved_profile = laserCutterProfileManager.save(profile, allow_overwrite=False, make_default=make_default)
|
||||
except InvalidProfileError:
|
||||
return make_response("Profile is invalid", 400)
|
||||
except CouldNotOverwriteError:
|
||||
return make_response("Profile already exists and overwriting was not allowed", 400)
|
||||
except Exception as e:
|
||||
return make_response("Could not save profile: %s" % e.message, 500)
|
||||
else:
|
||||
return jsonify(dict(profile=_convert_profile(saved_profile)))
|
||||
|
||||
@blueprint.route("/profiles/<string:identifier>", methods=["GET"])
|
||||
def laserCutterProfilesGet(identifier):
|
||||
profile = laserCutterProfileManager.get(identifier)
|
||||
if profile is None:
|
||||
return make_response("Unknown profile: %s" % identifier, 404)
|
||||
else:
|
||||
return jsonify(_convert_profile(profile))
|
||||
|
||||
@blueprint.route("/profiles/<string:identifier>", methods=["DELETE"])
|
||||
@restricted_access
|
||||
def laserCutterProfilesDelete(identifier):
|
||||
laserCutterProfileManager.remove(identifier)
|
||||
return NO_CONTENT
|
||||
|
||||
@blueprint.route("/profiles/<string:identifier>", methods=["PATCH"])
|
||||
@restricted_access
|
||||
def laserCutterProfilesUpdate(identifier):
|
||||
if not "application/json" in request.headers["Content-Type"]:
|
||||
return make_response("Expected content-type JSON", 400)
|
||||
|
||||
try:
|
||||
json_data = request.json
|
||||
except JSONBadRequest:
|
||||
return make_response("Malformed JSON body in request", 400)
|
||||
|
||||
if not "profile" in json_data:
|
||||
return make_response("No profile included in request", 400)
|
||||
|
||||
profile = laserCutterProfileManager.get(identifier)
|
||||
if profile is None:
|
||||
profile = laserCutterProfileManager.get_default()
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
saved_profile = laserCutterProfileManager.save(new_profile, allow_overwrite=True, make_default=make_default)
|
||||
except InvalidProfileError:
|
||||
return make_response("Profile is invalid", 400)
|
||||
except CouldNotOverwriteError:
|
||||
return make_response("Profile already exists and overwriting was not allowed", 400)
|
||||
except Exception as e:
|
||||
return make_response("Could not save profile: %s" % e.message, 500)
|
||||
else:
|
||||
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 = laserCutterProfileManager.get_default()["id"]
|
||||
current = laserCutterProfileManager.get_current_or_default()["id"]
|
||||
|
||||
converted = copy.deepcopy(profile)
|
||||
converted["resource"] = url_for(".laserCutterProfilesGet", identifier=profile["id"], _external=True)
|
||||
converted["default"] = (profile["id"] == default)
|
||||
converted["current"] = (profile["id"] == current)
|
||||
return converted
|
||||
|
||||
|
||||
|
||||
default_settings = {
|
||||
"zAxis": False,
|
||||
"working_area_width": 216,
|
||||
"working_area_height": 297
|
||||
}
|
||||
s = octoprint.plugin.plugin_settings("lasercutterprofiles", defaults=default_settings)
|
||||
|
||||
|
||||
class LaserCutterProfilesPlugin(octoprint.plugin.SettingsPlugin,
|
||||
octoprint.plugin.StartupPlugin,
|
||||
octoprint.plugin.BlueprintPlugin,
|
||||
octoprint.plugin.AssetPlugin,
|
||||
octoprint.plugin.TemplatePlugin):
|
||||
|
||||
global laserCutterProfileManager
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
##~~ StartupPlugin API
|
||||
def on_startup(self, host, port):
|
||||
pass
|
||||
|
||||
##~~ AssetPlugin API
|
||||
|
||||
def get_assets(self):
|
||||
return {
|
||||
"js": ["js/lasercutterprofiles.js"],
|
||||
"less": [],
|
||||
"css": []
|
||||
}
|
||||
|
||||
##~~ SettingsPlugin API
|
||||
|
||||
def on_settings_load(self):
|
||||
return dict(
|
||||
workingAreaHeight=s.get(["workingAreaHeight"]),
|
||||
workingAreaWidth=s.get(["workingAreaWidth"]),
|
||||
zAxis=s.getBoolean(["zAxis"])
|
||||
)
|
||||
|
||||
def on_settings_save(self, data):
|
||||
if "workingAreaHeight" in data and data["workingAreaHeight"]:
|
||||
s.set(["workingAreaHeight"], data["workingAreaHeight"])
|
||||
if "workingAreaWidth" in data and data["workingAreaWidth"]:
|
||||
s.set(["workingAreaWidth"], data["workingAreaWidth"])
|
||||
if "zAxis" in data:
|
||||
zAxis = data["zAxis"] in octoprint.settings.valid_boolean_trues
|
||||
s.setBoolean(["zAxis"], zAxis)
|
||||
|
||||
##~~ TemplatePlugin API
|
||||
|
||||
def get_template_vars(self):
|
||||
return dict(
|
||||
_settings_menu_entry="Laser cutter profile"
|
||||
)
|
||||
|
||||
def get_template_folder(self):
|
||||
import os
|
||||
return os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates")
|
||||
|
||||
##~~ BlueprintPlugin API
|
||||
|
||||
def get_blueprint(self):
|
||||
global blueprint
|
||||
return blueprint
|
||||
|
||||
def _sanitize_name(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.lower()
|
||||
|
||||
__plugin_name__ = "lasercutterprofiles"
|
||||
__plugin_version__ = "0.1"
|
||||
__plugin_implementations__ = [LaserCutterProfilesPlugin()]
|
||||
297
src/octoprint/plugins/lasercutterprofiles/profile.py
Normal file
297
src/octoprint/plugins/lasercutterprofiles/profile.py
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
# 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
|
||||
import logging
|
||||
|
||||
from octoprint.settings import settings
|
||||
from octoprint.util import dict_merge, dict_clean, dict_contains_keys
|
||||
|
||||
class SaveError(Exception):
|
||||
pass
|
||||
|
||||
class CouldNotOverwriteError(SaveError):
|
||||
pass
|
||||
|
||||
class InvalidProfileError(Exception):
|
||||
pass
|
||||
|
||||
class LaserCutterProfileManager(object):
|
||||
|
||||
default = dict(
|
||||
id = "_mrbeam_junior",
|
||||
name = "Mr Beam",
|
||||
model = "Junior",
|
||||
volume=dict(
|
||||
width = 216,
|
||||
depth = 297,
|
||||
height = 0,
|
||||
),
|
||||
zAxis = False,
|
||||
axes=dict(
|
||||
x = dict(speed=5000, inverted=False),
|
||||
y = dict(speed=5000, inverted=False),
|
||||
z = dict(speed=1000, inverted=False)
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self._current = None
|
||||
self._folder = settings().getBaseFolder("plugins")+"/lasercutterprofiles"
|
||||
if not os.path.exists(self._folder):
|
||||
os.makedirs(self._folder)
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
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):
|
||||
try:
|
||||
if identifier == "_default":
|
||||
return self._load_default()
|
||||
elif self.exists(identifier):
|
||||
return self._load_from_path(self._get_profile_path(identifier))
|
||||
else:
|
||||
return None
|
||||
except InvalidProfileError:
|
||||
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, make_default=False):
|
||||
if "id" in profile and profile['id'] != '':
|
||||
identifier = profile["id"]
|
||||
elif "name" in profile:
|
||||
identifier = profile["name"]
|
||||
else:
|
||||
raise InvalidProfileError("profile must contain either id or name")
|
||||
|
||||
identifier = self._sanitize(identifier)
|
||||
profile["id"] = identifier
|
||||
profile = dict_clean(profile, self.__class__.default)
|
||||
|
||||
print("save", profile)
|
||||
if identifier == "_default":
|
||||
default_profile = dict_merge(self._load_default(), profile)
|
||||
if not self._ensure_valid_profile(default_profile):
|
||||
raise InvalidProfileError()
|
||||
|
||||
settings().set(["printerProfiles", "defaultProfile"], default_profile, defaults=dict(printerProfiles=dict(defaultProfile=self.__class__.default)))
|
||||
settings().save()
|
||||
else:
|
||||
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):
|
||||
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():
|
||||
try:
|
||||
profile = self._load_from_path(path)
|
||||
except InvalidProfileError:
|
||||
continue
|
||||
|
||||
if profile is None:
|
||||
continue
|
||||
|
||||
results[identifier] = dict_merge(self._load_default("_mrbeam_junior"), profile)
|
||||
|
||||
if(not results.has_key("_mrbeam_junior")):
|
||||
results["_mrbeam_junior"] = self._load_default("_mrbeam_junior")
|
||||
if(not results.has_key("_mrbeam_senior")):
|
||||
results["_mrbeam_senior"] = self._load_default("_mrbeam_senior")
|
||||
return results
|
||||
|
||||
def _load_all_identifiers(self):
|
||||
results = dict()
|
||||
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)
|
||||
profile = self._ensure_valid_profile(profile)
|
||||
if not profile:
|
||||
self._logger.warn("Invalid profile: %s" % path)
|
||||
raise InvalidProfileError()
|
||||
return profile
|
||||
|
||||
def _save_to_path(self, path, profile, allow_overwrite=False):
|
||||
validated_profile = self._ensure_valid_profile(profile)
|
||||
print("_save_to_path", validated_profile, path)
|
||||
if not validated_profile:
|
||||
raise InvalidProfileError()
|
||||
|
||||
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, default_flow_style=False, indent=" ", allow_unicode=True)
|
||||
except Exception as e:
|
||||
print (e.message)
|
||||
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, defaultModel = None):
|
||||
default_overrides = settings().get(["laserCutterProfiles", defaultModel])
|
||||
default = copy.deepcopy(self.__class__.default)
|
||||
if(defaultModel is not None and defaultModel == "_mrbeam_senior"):
|
||||
default['volume']['width'] *= 2
|
||||
default['volume']['depth'] *= 2
|
||||
default['model'] = "Senior"
|
||||
default['id'] = "_mrbeam_senior"
|
||||
|
||||
if(default_overrides is None):
|
||||
merge = default
|
||||
else:
|
||||
merge = dict_merge(default, default_overrides)
|
||||
|
||||
profile = self._ensure_valid_profile(merge)
|
||||
if not profile:
|
||||
self._logger.warn("Invalid default profile after applying overrides")
|
||||
raise InvalidProfileError()
|
||||
return 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
|
||||
|
||||
def _ensure_valid_profile(self, profile):
|
||||
# ensure all keys are present
|
||||
if not dict_contains_keys(self.default, profile):
|
||||
print("key error")
|
||||
return False
|
||||
|
||||
# conversion helper
|
||||
def convert_value(profile, path, converter):
|
||||
value = profile
|
||||
for part in path[:-1]:
|
||||
if not isinstance(value, dict) or not part in value:
|
||||
raise RuntimeError("%s is not contained in profile" % ".".join(path))
|
||||
value = value[part]
|
||||
|
||||
if not isinstance(value, dict) or not path[-1] in value:
|
||||
raise RuntimeError("%s is not contained in profile" % ".".join(path))
|
||||
|
||||
value[path[-1]] = converter(value[path[-1]])
|
||||
|
||||
|
||||
# convert ints
|
||||
for path in (("axes", "x", "speed"), ("axes", "y", "speed"), ("axes", "z", "speed")):
|
||||
try:
|
||||
convert_value(profile, path, int)
|
||||
except:
|
||||
print("int error")
|
||||
return False
|
||||
|
||||
# convert floats
|
||||
for path in (("volume", "width"), ("volume", "depth"), ("volume", "height")):
|
||||
try:
|
||||
convert_value(profile, path, float)
|
||||
except:
|
||||
print("float error")
|
||||
return False
|
||||
|
||||
# convert booleans
|
||||
for path in (("axes", "x", "inverted"), ("axes", "y", "inverted"), ("axes", "z", "inverted")):
|
||||
try:
|
||||
convert_value(profile, path, bool)
|
||||
except:
|
||||
print("bool error")
|
||||
return False
|
||||
|
||||
return profile
|
||||
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
$(function() {
|
||||
|
||||
function LaserCutterProfilesViewModel(params) {
|
||||
var self = this;
|
||||
|
||||
self.settings = params[0];
|
||||
|
||||
self._cleanProfile = function() {
|
||||
return {
|
||||
id: "",
|
||||
name: "",
|
||||
model: "",
|
||||
color: "default",
|
||||
volume: {
|
||||
formFactor: "rectangular",
|
||||
width: 200,
|
||||
depth: 200,
|
||||
height: 200
|
||||
},
|
||||
heatedBed: false,
|
||||
axes: {
|
||||
x: {speed: 6000, inverted: false},
|
||||
y: {speed: 6000, inverted: false},
|
||||
z: {speed: 200, inverted: false},
|
||||
e: {speed: 300, inverted: false}
|
||||
},
|
||||
extruder: {
|
||||
count: 1,
|
||||
offsets: [
|
||||
[0,0]
|
||||
],
|
||||
nozzleDiameter: 0.4
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.profiles = new ItemListHelper(
|
||||
"laserCutterProfiles",
|
||||
{
|
||||
"name": function(a, b) {
|
||||
// sorts ascending
|
||||
if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
|
||||
if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
{},
|
||||
"name",
|
||||
[],
|
||||
[],
|
||||
10
|
||||
);
|
||||
self.defaultProfile = ko.observable();
|
||||
self.currentProfile = ko.observable();
|
||||
|
||||
self.currentProfileData = ko.observable(ko.mapping.fromJS(self._cleanProfile()));
|
||||
|
||||
self.editorNew = ko.observable(false);
|
||||
|
||||
self.editorName = ko.observable();
|
||||
// self.editorColor = ko.observable();
|
||||
self.editorIdentifier = ko.observable();
|
||||
self.editorModel = ko.observable();
|
||||
|
||||
self.editorVolumeWidth = ko.observable();
|
||||
self.editorVolumeDepth = ko.observable();
|
||||
self.editorVolumeHeight = ko.observable();
|
||||
// self.editorVolumeFormFactor = ko.observable();
|
||||
|
||||
// self.editorHeatedBed = ko.observable();
|
||||
self.editorZAxis = ko.observable();
|
||||
|
||||
// self.editorNozzleDiameter = 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.makeDefault = function(data) {
|
||||
var profile = {
|
||||
id: data.id,
|
||||
default: true
|
||||
};
|
||||
|
||||
self.updateProfile(profile);
|
||||
};
|
||||
|
||||
self.requestData = function() {
|
||||
$.ajax({
|
||||
url: BASEURL + "plugin/lasercutterprofiles/profiles",
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: self.fromResponse
|
||||
})
|
||||
};
|
||||
|
||||
self.fromResponse = function(data) {
|
||||
var items = [];
|
||||
var defaultProfile = undefined;
|
||||
var currentProfile = undefined;
|
||||
var currentProfileData = undefined;
|
||||
_.each(data.profiles, function(entry) {
|
||||
if (entry.default) {
|
||||
defaultProfile = entry.id;
|
||||
}
|
||||
if (entry.current) {
|
||||
currentProfile = entry.id;
|
||||
currentProfileData = ko.mapping.fromJS(entry, self.currentProfileData);
|
||||
}
|
||||
entry["isdefault"] = ko.observable(entry.default);
|
||||
entry["iscurrent"] = ko.observable(entry.current);
|
||||
items.push(entry);
|
||||
});
|
||||
self.profiles.updateItems(items);
|
||||
self.defaultProfile(defaultProfile);
|
||||
self.currentProfile(currentProfile);
|
||||
self.currentProfileData(currentProfileData);
|
||||
};
|
||||
|
||||
self.addProfile = function(callback) {
|
||||
var profile = self._editorData();
|
||||
$.ajax({
|
||||
url: BASEURL + "plugin/lasercutterprofiles/profiles",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify({profile: profile}),
|
||||
success: function() {
|
||||
if (callback !== undefined) {
|
||||
callback();
|
||||
}
|
||||
self.requestData();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
self.removeProfile = function(data) {
|
||||
$.ajax({
|
||||
url: data.resource,
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
success: self.requestData
|
||||
})
|
||||
};
|
||||
|
||||
self.updateProfile = function(profile, callback) {
|
||||
if (profile == undefined) {
|
||||
profile = self._editorData();
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: BASEURL + "plugin/lasercutterprofiles/profiles/" + profile.id,
|
||||
type: "PATCH",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify({profile: profile}),
|
||||
success: function() {
|
||||
if (callback !== undefined) {
|
||||
callback();
|
||||
}
|
||||
self.requestData();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
self.showEditProfileDialog = function(data) {
|
||||
var add = false;
|
||||
if (data == undefined) {
|
||||
data = self._cleanProfile();
|
||||
add = true;
|
||||
}
|
||||
|
||||
self.editorNew(add);
|
||||
|
||||
self.editorIdentifier(data.id);
|
||||
self.editorName(data.name);
|
||||
self.editorModel(data.model);
|
||||
|
||||
self.editorVolumeWidth(data.volume.width);
|
||||
self.editorVolumeDepth(data.volume.depth);
|
||||
self.editorVolumeHeight(data.volume.height);
|
||||
|
||||
self.editorZAxis(data.zAxis);
|
||||
|
||||
self.editorAxisXSpeed(data.axes.x.speed);
|
||||
self.editorAxisXInverted(data.axes.x.inverted);
|
||||
self.editorAxisYSpeed(data.axes.y.speed);
|
||||
self.editorAxisYInverted(data.axes.y.inverted);
|
||||
self.editorAxisZSpeed(data.axes.z.speed);
|
||||
self.editorAxisZInverted(data.axes.z.inverted);
|
||||
|
||||
var editDialog = $("#settings_laserCutterProfiles_editDialog");
|
||||
var confirmButton = $("button.btn-confirm", editDialog);
|
||||
var dialogTitle = $("h3.modal-title", editDialog);
|
||||
|
||||
dialogTitle.text(add ? gettext("Add Profile") : _.sprintf(gettext("Edit Profile \"%(name)s\""), {name: data.name}));
|
||||
confirmButton.unbind("click");
|
||||
confirmButton.bind("click", function() {
|
||||
self.confirmEditProfile(add);
|
||||
});
|
||||
editDialog.modal("show");
|
||||
};
|
||||
|
||||
self.confirmEditProfile = function(add) {
|
||||
var callback = function() {
|
||||
$("#settings_laserCutterProfiles_editDialog").modal("hide");
|
||||
};
|
||||
|
||||
if (add) {
|
||||
self.addProfile(callback);
|
||||
} else {
|
||||
self.updateProfile(undefined, callback);
|
||||
}
|
||||
};
|
||||
|
||||
self._editorData = function() {
|
||||
var profile = {
|
||||
id: self.editorIdentifier(),
|
||||
name: self.editorName(),
|
||||
model: self.editorModel(),
|
||||
volume: {
|
||||
width: parseFloat(self.editorVolumeWidth()),
|
||||
depth: parseFloat(self.editorVolumeDepth()),
|
||||
height: parseFloat(self.editorVolumeHeight()),
|
||||
},
|
||||
zAxis: self.editorZAxis(),
|
||||
axes: {
|
||||
x: {
|
||||
speed: parseInt(self.editorAxisXSpeed()),
|
||||
inverted: self.editorAxisXInverted()
|
||||
},
|
||||
y: {
|
||||
speed: parseInt(self.editorAxisYSpeed()),
|
||||
inverted: self.editorAxisYInverted()
|
||||
},
|
||||
z: {
|
||||
speed: parseInt(self.editorAxisZSpeed()),
|
||||
inverted: self.editorAxisZInverted()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return profile;
|
||||
};
|
||||
|
||||
self.onSettingsShown = self.requestData;
|
||||
self.onStartup = function(){
|
||||
console.log("lasercutter profiles onStartup");
|
||||
self.settings.laserCutterProfiles = self;
|
||||
self.requestData;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// view model class, identifier, parameters for constructor, container to bind to
|
||||
ADDITIONAL_VIEWMODELS.push([LaserCutterProfilesViewModel, "laserCutterProfilesViewModel",
|
||||
["settingsViewModel"],
|
||||
document.getElementById("laserCutterProfiles")]);
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<h3>{{ _('Machine Profiles') }}</h3>
|
||||
<div id="laserCutterProfiles">
|
||||
|
||||
<table class="table table-striped table-hover table-condensed table-hover" id="settings_laserCutterProfiles_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: 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="text: model"></td>
|
||||
<td class="settings_printerProfiles_profiles_action">
|
||||
<a href="#" class="icon-star" title="{{ _('Set as default profile') }}" data-bind="click: function() { $root.makeDefault($data); }"></a> | <a href="#" class="icon-pencil" title="{{ _('Edit Profile') }}" data-bind="click: function() { $root.showEditProfileDialog($data); }"></a> | <a href="#" class="icon-trash" title="{{ _('Delete Profile') }}" data-bind="click: function() { $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.showEditProfileDialog(); }">{{ _('Add Profile...') }}</button>
|
||||
|
||||
<div id="settings_laserCutterProfiles_editDialog" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<h3 class="modal-title"></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Name') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" data-bind="value: editorName">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" style="display:none;">
|
||||
<label class="control-label">{{ _('Identifier') }}</label>
|
||||
<div class="controls">
|
||||
<input type="hidden" data-bind="value: editorIdentifier, enable: $root.editorNew, css: {disabled: !$root.editorNew()}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Model') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" data-bind="value: editorModel">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Working Area Size') }}</label>
|
||||
<div class="controls form-inline">
|
||||
<label>{{ _('X') }}</label>
|
||||
<div class="input-append">
|
||||
<input type="number" step="1" class="input-mini text-right" data-bind="value: editorVolumeWidth">
|
||||
<span class="add-on">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls form-inline">
|
||||
<label>{{ _('Y') }}</label>
|
||||
<div class="input-append">
|
||||
<input type="number" step="1" class="input-mini text-right" data-bind="value: editorVolumeDepth">
|
||||
<span class="add-on">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="controls form-inline">
|
||||
<label>{{ _('Z') }}</label>
|
||||
<div class="input-append">
|
||||
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: editorVolumeHeight">
|
||||
<span class="add-on">mm</span>
|
||||
</div>
|
||||
</div>-->
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Z-Axis') }}</label>
|
||||
<div class="controls">
|
||||
<input type="checkbox" data-bind="checked: editorZAxis">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Axis') }}</label>
|
||||
<div class="controls form-inline">
|
||||
<label>{{ _('X') }}</label>
|
||||
<div class="input-append">
|
||||
<input type="number" class="input-mini text-right" data-bind="value: editorAxisXSpeed">
|
||||
<span class="add-on">mm/min</span>
|
||||
</div>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: editorAxisXInverted"> {{ _('Invert control') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="controls form-inline">
|
||||
<label>{{ _('Y') }}</label>
|
||||
<div class="input-append">
|
||||
<input type="number" class="input-mini text-right" data-bind="value: editorAxisYSpeed">
|
||||
<span class="add-on">mm/min</span>
|
||||
</div>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: editorAxisYInverted"> {{ _('Invert control') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="controls form-inline">
|
||||
<label>{{ _('Z') }}</label>
|
||||
<div class="input-append">
|
||||
<input type="number" class="input-mini text-right" data-bind="value: editorAxisZSpeed">
|
||||
<span class="add-on">mm/min</span>
|
||||
</div>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: editorAxisZInverted"> {{ _('Invert control') }}
|
||||
</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 btn-confirm">{{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -23,6 +23,8 @@ function WorkingAreaViewModel(params) {
|
|||
self.px2mm_factor = 1; // initial value
|
||||
self.hwRatio = ko.computed(function(){
|
||||
// y/x = 297/216 respectively 594/432
|
||||
// var h = self.settings.laserCutterProfiles.currentProfileData().volume.depth();
|
||||
// var w = self.settings.laserCutterProfiles.currentProfileData().volume.width();
|
||||
var h = self.settings.printerProfiles.currentProfileData().volume.depth();
|
||||
var w = self.settings.printerProfiles.currentProfileData().volume.width();
|
||||
var ratio = h / w;
|
||||
|
|
@ -161,6 +163,7 @@ function WorkingAreaViewModel(params) {
|
|||
};
|
||||
|
||||
self.onStartup = function(){
|
||||
console.log("working_area_onstartup");
|
||||
self.files.workingArea = self;
|
||||
$(window).resize(function(){
|
||||
self.trigger_resize();
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ def index():
|
|||
settings_entries = [
|
||||
# (gettext("Printer"), None),
|
||||
(gettext("Serial Connection"), dict(template="dialogs/settings/serialconnection.jinja2", _div="settings_serialConnection", custom_bindings=False)),
|
||||
(gettext("Machine Profiles"), dict(template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles", custom_bindings=False)),
|
||||
# (gettext("Machine Profiles"), dict(template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles", custom_bindings=False)),
|
||||
# (gettext("Temperatures"), dict(template="dialogs/settings/temperatures.jinja2", _div="settings_temperature", custom_bindings=False)),
|
||||
(gettext("Terminal Filters"), dict(template="dialogs/settings/terminalfilters.jinja2", _div="settings_terminalFilters", custom_bindings=False)),
|
||||
# (gettext("Features"), None),
|
||||
|
|
@ -677,7 +677,7 @@ class Server():
|
|||
# enable debug logging to serial.log
|
||||
logging.getLogger("SERIAL").setLevel(logging.DEBUG)
|
||||
logging.getLogger("SERIAL").debug("Enabling serial logging")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = Server()
|
||||
server.run()
|
||||
|
|
|
|||
|
|
@ -460,7 +460,7 @@ $(function() {
|
|||
// gcodeViewModel.initialize();
|
||||
// ko.applyBindings(gcodeViewModel, gcode);
|
||||
// }
|
||||
ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog"));
|
||||
// ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog"));
|
||||
// ko.applyBindings(navigationViewModel, document.getElementById("navbar"));
|
||||
// ko.applyBindings(appearanceViewModel, document.getElementsByTagName("head")[0]);
|
||||
// ko.applyBindings(printerStateViewModel, document.getElementById("drop_overlay"));
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel, printerProfilesV
|
|||
|
||||
self.removeTerminalFilter = function(filter) {
|
||||
self.terminalFilters.remove(filter);
|
||||
self.saveall();
|
||||
};
|
||||
|
||||
self.onSettingsShown = function() {
|
||||
|
|
@ -203,7 +204,6 @@ function SettingsViewModel(loginStateViewModel, usersViewModel, printerProfilesV
|
|||
self.temperature_profiles(response.temperature.profiles);
|
||||
|
||||
self.system_actions(response.system.actions);
|
||||
|
||||
self.terminalFilters(response.terminalFilters);
|
||||
};
|
||||
|
||||
|
|
@ -274,6 +274,8 @@ function SettingsViewModel(loginStateViewModel, usersViewModel, printerProfilesV
|
|||
},
|
||||
"terminalFilters": self.terminalFilters()
|
||||
});
|
||||
|
||||
console.log("data", data.terminalFilters);
|
||||
return data;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div id="settings_dialog" class="container" role="dialog" aria-labelledby="settings_dialog_label" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3 id="settings_dialog_label">{{ _('OctoPrint Settings') }}</h3>
|
||||
<!--<h3 id="settings_dialog_label">{{ _('OctoPrint Settings') }}</h3>-->
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="tabbable">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_gcodeViewer" id="settings-featureGcodeViewer"> {{ _('Enable GCode Visualizer') }}
|
||||
<input type="checkbox" data-bind="event: {change: saveall}, checked: feature_gcodeViewer" id="settings-featureGcodeViewer"> {{ _('Enable GCode Visualizer') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_keyboardControl" id="settings-featureKeyboardControl"> {{ _("Enable Keyboard Control") }}
|
||||
<input type="checkbox" data-bind="event: {change: saveall}, checked: feature_keyboardControl" id="settings-featureKeyboardControl"> {{ _("Enable Keyboard Control") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
<div data-bind="foreach: terminalFilters">
|
||||
<div class="row-fluid" style="margin-bottom: 5px">
|
||||
<div class="span4">
|
||||
<input type="text" class="span12" data-bind="value: name">
|
||||
<input type="text" class="span12" data-bind="event: {change: $parent.saveall}, value: name">
|
||||
</div>
|
||||
<div class="span6">
|
||||
<input type="text" class="span12" data-bind="value: regex">
|
||||
<input type="text" class="span12" data-bind="event: {change: $parent.saveall}, value: regex">
|
||||
</div>
|
||||
<div class="span2">
|
||||
<a title="Remove Filter" class="btn btn-danger" data-bind="click: $parent.removeTerminalFilter"><i class="icon-trash"></i></a>
|
||||
|
|
|
|||
Loading…
Reference in a new issue