Merge pull request #71 from thelastWallE/rc

OctoKlipper 0.3.9
This commit is contained in:
thelastWallE 2021-11-16 21:23:03 +01:00 committed by GitHub
commit 55f8d70687
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 5484 additions and 1192 deletions

View File

@ -9,7 +9,9 @@ charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
max_line_length = 90
max_line_length = 180
indent_size = 2
[**.py]
indent_size = 4

5
.gitignore vendored
View File

@ -12,4 +12,7 @@ dist
.vscode
.vscode/**
thunder-tests
thunder-tests
vscode.env
.venv
OctoKlipper.egg-info

View File

@ -19,7 +19,7 @@ This plugin assists in managing and monitoring the [Klipper](https://github.com/
- PID Tuning Dialog.
- Dialog to set a coordinate offset for future GCODE move commands.
- Message log displaying messages from Klipper prepended with "//" and "!!".
- Basic Klipper configuration editor
- Klipper configuration editor
- Performance graph displaying key parameters extracted from the Klipper logs.
## Installation
@ -46,7 +46,7 @@ Also for the moment this plugin does what I wanted it to do, it is far from fini
* The [devel](https://github.com/thelastWallE/OctoprintKlipperPlugin/tree/devel) branch is the branch to merge new features and bugfixes to.
* The [rc](https://github.com/thelastWallE/OctoprintKlipperPlugin/tree/rc) branch is for Release Candidates and bugfixing them.
* The [master](https://github.com/thelastWallE/OctoprintKlipperPlugin/tree/master) branch is for Stable Releases.
* The [master](https://github.com/thelastWallE/OctoprintKlipperPlugin/tree/master) branch is for Stable Releases.
## Screenshots

View File

@ -14,35 +14,38 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function, unicode_literals
import datetime
import logging
import octoprint.plugin
import octoprint.plugin.core
import glob
import os
import time
import sys
import io
from octoprint.server import NO_CONTENT
from octoprint.util import is_hidden_path
from octoprint.util import get_formatted_size
from . import util, cfgUtils, logger
from octoprint.util.comm import parse_firmware_line
from octoprint.access.permissions import Permissions, ADMIN_GROUP, USER_GROUP
from octoprint.access.permissions import Permissions, ADMIN_GROUP
from .modules import KlipperLogAnalyzer
from octoprint.server.util.flask import restricted_access
import flask
from flask_babel import gettext
try:
import configparser
except ImportError:
import ConfigParser as configparser
if sys.version_info[0] < 3:
import StringIO
MAX_UPLOAD_SIZE = 5 * 1024 * 1024 # 5Mb
class KlipperPlugin(
octoprint.plugin.StartupPlugin,
octoprint.plugin.TemplatePlugin,
octoprint.plugin.SettingsPlugin,
octoprint.plugin.AssetPlugin,
octoprint.plugin.SimpleApiPlugin,
octoprint.plugin.EventHandlerPlugin):
octoprint.plugin.EventHandlerPlugin,
octoprint.plugin.BlueprintPlugin):
_parsing_response = False
_parsing_check_response = True
@ -75,8 +78,10 @@ class KlipperPlugin(
self._settings.global_set(
["serial", "additionalPorts"], additional_ports)
self._settings.save()
self.log_info(
"Added klipper serial port {} to list of additional ports.".format(klipper_port))
logger.log_info(
self,
"Added klipper serial port {} to list of additional ports.".format(klipper_port)
)
# -- Settings Plugin
@ -101,6 +106,7 @@ class KlipperPlugin(
]
def get_settings_defaults(self):
# TODO #69 put some settings on the localStorage
return dict(
connection=dict(
port="/tmp/printer",
@ -125,103 +131,20 @@ class KlipperPlugin(
),
configuration=dict(
debug_logging=False,
configpath="~/printer.cfg",
old_config="",
config_path="~/",
baseconfig="printer.cfg",
logpath="/tmp/klippy.log",
reload_command="RESTART",
restart_onsave=True,
confirm_reload=True,
shortStatus_navbar=True,
shortStatus_sidebar=True,
parse_check=False,
fontsize=9
fontsize=12
)
)
def on_settings_load(self):
data = octoprint.plugin.SettingsPlugin.on_settings_load(self)
configpath = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
try:
with io.open(configpath, "r", encoding="utf8") as f:
data["config"] = f.read()
f.close()
except IOError:
self.log_error(
"Error: Klipper config file not found at: {}".format(
configpath)
)
except UnicodeDecodeError as e:
self.log_debug(
"Loading config with utf-8 failed. Trying to load config file with ISO-8859-1 now."
)
try:
with io.open(configpath, "r", encoding="ISO-8859-1") as f:
data["config"] = f.read()
f.close()
except UnicodeDecodeError as e:
self.log_error(
"Error: Klipper config file cannot be decoded: {}".format(e)
)
else:
self.log_debug(
"Loading config with ISO-8859-1 finished."
)
self.send_message("reload", "config", "", data["config"])
# send the configdata to frontend to update ace editor
else:
self.send_message("reload", "config", "", data["config"])
# send the configdata to frontend to update ace editor
return data
def on_settings_save(self, data):
self.log_debug(
"Save klipper configs"
)
if "config" in data:
if self.key_exist(data, "configuration", "parse_check"):
check_parse = data["configuration"]["parse_check"]
else:
check_parse = self._settings.get(["configuration", "parse_check"])
# check for configpath if it was changed during changing of the configfile
if self.key_exist(data, "configuration", "configpath"):
configpath = os.path.expanduser(
data["configuration"]["configpath"]
)
else:
# if the configpath was not changed during changing the printer.cfg. Then the configpath would not be in data[]
configpath = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
if self.file_exist(configpath) and (self._parsing_check_response or not check_parse):
try:
with io.open(configpath, "w", encoding="utf-8") as f:
f.write(data["config"])
f.close()
self.log_debug("Writing Klipper config to {}".format(configpath))
except IOError:
self.log_error("Error: Couldn't write Klipper config file: {}".format(configpath))
else:
#load the reload command from changed data if it is not existing load the saved setting
if self.key_exist(data, "configuration", "reload_command"):
reload_command = os.path.expanduser(
data["configuration"]["reload_command"]
)
else:
reload_command = self._settings.get(["configuration", "reload_command"])
if reload_command != "manually":
# Restart klippy to reload config
self._printer.commands(reload_command)
self.log_info("Restarting Klipper.")
# we don't want to write the klipper conf to the octoprint settings
data.pop("config", None)
# save the rest of changed settings into config.yaml of octoprint
old_debug_logging = self._settings.get_boolean(["configuration", "debug_logging"])
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
@ -237,7 +160,7 @@ class KlipperPlugin(
return dict(
admin=[
["connection", "port"],
["configuration", "configpath"],
["configuration", "config_path"],
["configuration", "replace_connection_panel"]
],
user=[
@ -247,66 +170,61 @@ class KlipperPlugin(
)
def get_settings_version(self):
return 3
# Settings_Versionhistory:
# 3 = add shortstatus on navbar. migrate the navbar setting for this
# 4 = -change of configpath to config_path with only path without filename
# -parse configpath into config_path and baseconfig
# -switch setting for 'restart on editor save' to true if it was not set to manually
# -remove old_config
# -remove config on root settingsdirectory
return 4
#migrate Settings
def on_settings_migrate(self, target, current):
settings = self._settings
if current is None:
settings = self._settings
util.migrate_old_settings(settings)
if settings.has(["serialport"]):
settings.set(["connection", "port"],
settings.get(["serialport"]))
settings.remove(["serialport"])
if current is not None and current < 3:
self.migrate_settings_3(settings)
if settings.has(["replace_connection_panel"]):
settings.set(
["connection", "replace_connection_panel"],
settings.get(["replace_connection_panel"])
)
settings.remove(["replace_connection_panel"])
if current is not None and current < 4:
self.migrate_settings_4(settings)
if settings.has(["probeHeight"]):
settings.set(["probe", "height"],
settings.get(["probeHeight"]))
settings.remove(["probeHeight"])
def migrate_settings_3(self, settings):
util.migrate_settings_configuration(
settings,
"shortStatus_navbar",
"navbar",
)
if settings.has(["probeLift"]):
settings.set(["probe", "lift"], settings.get(["probeLift"]))
settings.remove(["probeLift"])
def migrate_settings_4(self, settings):
if settings.has(["configuration", "configpath"]):
cfg_path = settings.get(["configuration", "configpath"])
new_cfg_path, baseconfig = os.path.split(cfg_path)
logger.log_info(self, "migrate setting for 'configuration/config_path': " + cfg_path + " -> " + new_cfg_path)
logger.log_info(self, "migrate setting for 'configuration/baseconfig': printer.cfg -> " + baseconfig)
settings.set(["configuration", "config_path"], new_cfg_path)
settings.set(["configuration", "baseconfig"], baseconfig)
settings.remove(["configuration", "configpath"])
if (
settings.has(["configuration", "reload_command"])
and settings.get(["configuration", "reload_command"]) == "manually"
):
logger.log_info(self, "migrate setting for 'configuration/restart_onsave': True -> False")
settings.set(["configuration", "restart_onsave"], False)
settings.remove(["configuration", "reload_command"])
if settings.has(["probeSpeedXy"]):
settings.set(["probe", "speed_xy"],
settings.get(["probeSpeedXy"]))
settings.remove(["probeSpeedXy"])
if settings.has(["config"]):
logger.log_info(self, "remove old setting for 'config'")
settings.remove(["config"])
if settings.has(["probeSpeedZ"]):
settings.set(["probe", "speed_z"],
settings.get(["probeSpeedZ"]))
settings.remove(["probeSpeedZ"])
if settings.has(["configuration", "old_config"]):
logger.log_info(self, "remove old setting for 'configuration/old_config'")
settings.remove(["configuration", "old_config"])
if settings.has(["probePoints"]):
points = settings.get(["probePoints"])
points_new = []
for p in points:
points_new.append(
dict(name="", x=int(p["x"]), y=int(p["y"]), z=0))
settings.set(["probe", "points"], points_new)
settings.remove(["probePoints"])
if settings.has(["configPath"]):
self.log_info("migrate setting for: configPath")
settings.set(["config_path"], settings.get(["configPath"]))
settings.remove(["configPath"])
if target is 3 and current is 2:
settings = self._settings
if settings.has(["configuration", "navbar"]):
self.log_info("migrate setting for: configuration/navbar")
settings.set(["configuration", "shortStatus_navbar"], settings.get(["configuration", "navbar"]))
settings.remove(["configuration", "navbar"])
# -- Template Plugin
def get_template_configs(self):
return [
dict(type="navbar", custom_bindings=True),
@ -337,17 +255,29 @@ class KlipperPlugin(
custom_bindings=True
),
dict(type="sidebar",
custom_bindings=True,
icon="rocket",
replaces="connection" if self._settings.get_boolean(
["connection", "replace_connection_panel"]) else ""
),
custom_bindings=True,
icon="rocket",
replaces="connection" if self._settings.get_boolean(
["connection", "replace_connection_panel"]) else ""
),
dict(
type="generic",
name="Performance Graph",
template="klipper_graph_dialog.jinja2",
custom_bindings=True
),
dict(
type="generic",
name="Config Backups",
template="klipper_backups_dialog.jinja2",
custom_bindings=True
),
dict(
type="generic",
name="Config Editor",
template="klipper_editor.jinja2",
custom_bindings=True
),
dict(
type="generic",
name="Macro Dialog",
@ -356,6 +286,12 @@ class KlipperPlugin(
)
]
def get_template_vars(self):
return {
"max_upload_size": MAX_UPLOAD_SIZE,
"max_upload_size_str": get_formatted_size(MAX_UPLOAD_SIZE),
}
# -- Asset Plugin
def get_assets(self):
@ -366,51 +302,66 @@ class KlipperPlugin(
"js/klipper_pid_tuning.js",
"js/klipper_offset.js",
"js/klipper_param_macro.js",
"js/klipper_graph.js"
],
"js/klipper_graph.js",
"js/klipper_backup.js",
"js/klipper_editor.js"
],
clientjs=["clientjs/klipper.js"],
css=["css/klipper.css"]
)
# -- Event Handler Plugin
def on_event(self, event, payload):
if "UserLoggedIn" == event:
self.update_status("info", "Klipper: Standby")
if "Connecting" == event:
self.update_status("info", "Klipper: Connecting ...")
elif "Connected" == event:
self.update_status("info", "Klipper: Connected to host")
self.log_info(
if event == "UserLoggedIn":
logger.log_info(self, "Klipper: Standby")
if event == "Connecting":
logger.log_info(self, "Klipper: Connecting ...")
elif event == "Connected":
logger.log_info(self, "Klipper: Connected to host")
logger.log_info(
self,
"Connected to host via {} @{}bps".format(payload["port"], payload["baudrate"]))
elif "Disconnected" == event:
self.update_status("info", "Klipper: Disconnected from host")
elif "Error" == event:
self.update_status("error", "Klipper: Error")
self.log_error(payload["error"])
elif event == "Disconnected":
logger.log_info(self, "Klipper: Disconnected from host")
elif event == "Error":
logger.log_error(self, payload["error"])
def processAtCommand(self, comm_instance, phase, command, parameters, tags=None, *args, **kwargs):
if command != "SWITCHCONFIG":
return
config = parameters
logger.log_info(self, "SWITCHCONFIG detected config:{}".format(config))
return None
# -- GCODE Hook
def process_sent_GCODE(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
if cmd == "SAVE_CONFIG":
logger.log_info(self, "SAVE_CONFIG detected")
util.send_message(self, type = "reload", subtype = "config")
def on_parse_gcode(self, comm, line, *args, **kwargs):
if "FIRMWARE_VERSION" in line:
printerInfo = parse_firmware_line(line)
if "FIRMWARE_VERSION" in printerInfo:
self.log_info("Firmware version: {}".format(
logger.log_info(self, "Firmware version: {}".format(
printerInfo["FIRMWARE_VERSION"]))
elif "// probe" in line or "// Failed to verify BLTouch" in line:
msg = line.strip('/')
self.log_info(msg)
logger.log_info(self, msg)
self.write_parsing_response_buffer()
elif "//" in line:
# add lines with // to a buffer
self._message = self._message + line.strip('/')
if not self._parsing_response:
self.update_status("info", self._message)
util.update_status(self, "info", self._message)
self._parsing_response = True
elif "!!" in line:
msg = line.strip('!')
self.update_status("error", msg)
self.log_error(msg)
logger.log_error(self, msg)
self.write_parsing_response_buffer()
else:
self.write_parsing_response_buffer()
@ -420,15 +371,13 @@ class KlipperPlugin(
# write buffer with // lines after a gcode response without //
if self._parsing_response:
self._parsing_response = False
self.log_info(self._message)
logger.log_info(self, self._message)
self._message = ""
def get_api_commands(self):
return dict(
listLogFiles=[],
getStats=["logFile"],
reloadConfig=[],
checkConfig=["config"]
getStats=["logFile"]
)
def on_api_command(self, command, data):
@ -437,70 +386,182 @@ class KlipperPlugin(
logpath = os.path.expanduser(
self._settings.get(["configuration", "logpath"])
)
if self.file_exist(logpath):
if util.file_exist(self, logpath):
for f in glob.glob(self._settings.get(["configuration", "logpath"]) + "*"):
filesize = os.path.getsize(f)
filemdate = time.strftime("%d.%m.%Y %H:%M",time.localtime(os.path.getctime(f)))
files.append(dict(
name=os.path.basename(
f) + " ({:.1f} KB)".format(filesize / 1000.0),
name=os.path.basename(f) + " (" + filemdate + ")",
file=f,
size=filesize
))
return flask.jsonify(data=files)
else:
return flask.jsonify(data=files)
return flask.jsonify(data=files)
elif command == "getStats":
if "logFile" in data:
log_analyzer = KlipperLogAnalyzer.KlipperLogAnalyzer(
data["logFile"])
return flask.jsonify(log_analyzer.analyze())
elif command == "reloadConfig":
data = octoprint.plugin.SettingsPlugin.on_settings_load(self)
configpath = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
def is_blueprint_protected(self):
return False
def route_hook(self, server_routes, *args, **kwargs):
from octoprint.server.util.tornado import LargeResponseHandler, path_validation_factory
from octoprint.util import is_hidden_path
configpath = os.path.expanduser(
self._settings.get(["configuration", "config_path"])
)
bak_path = os.path.join(self.get_plugin_data_folder(), "configs", "")
return [
(r"/download/configs/(.*)", LargeResponseHandler, dict(path=configpath,
as_attachment=True,
path_validation=path_validation_factory(lambda path: not is_hidden_path(path),
status_code=404))),
(r"/download/backup/(.*)", LargeResponseHandler, dict(path=bak_path,
as_attachment=True,
path_validation=path_validation_factory(lambda path: not is_hidden_path(path),
status_code=404)))
]
# API for Backups
# Get Content of a Backupconfig
@octoprint.plugin.BlueprintPlugin.route("/backup/<filename>", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def get_backup(self, filename):
data_folder = self.get_plugin_data_folder()
full_path = os.path.realpath(os.path.join(data_folder, "configs", filename))
response = cfgUtils.get_cfg(self, full_path)
return flask.jsonify(response = response)
# Delete a Backupconfig
@octoprint.plugin.BlueprintPlugin.route("/backup/<filename>", methods=["DELETE"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def delete_backup(self, filename):
data_folder = self.get_plugin_data_folder()
full_path = os.path.realpath(os.path.join(data_folder, "configs", filename))
if (
full_path.startswith(data_folder)
and os.path.exists(full_path)
and not is_hidden_path(full_path)
):
try:
with io.open(configpath, "r", encoding="utf-8") as f:
data["config"] = f.read()
f.close()
except IOError:
self.log_error(
"Error: Klipper config file not found at: {}".format(
configpath)
)
except UnicodeDecodeError as e:
self.log_debug(
"Loading config with utf-8 failed. Trying to load config file with ISO-8859-1 now."
)
try:
with io.open(configpath, "r", encoding="ISO-8859-1") as f:
data["config"] = f.read()
f.close()
except UnicodeDecodeError as e:
self.log_error(
"Error: Klipper config file cannot be decoded: {}".format(e)
)
else:
self.log_debug(
"Loading config with ISO-8859-1 finished."
)
self._settings.set(["config"], data["config"])
return flask.jsonify(data=data["config"])
else:
os.remove(full_path)
except Exception:
self._octoklipper_logger.exception("Could not delete {}".format(filename))
raise
return NO_CONTENT
# Get a list of all backed up configfiles
@octoprint.plugin.BlueprintPlugin.route("/backup/list", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def list_backups(self):
files = cfgUtils.list_cfg_files(self, "backup")
return flask.jsonify(files = files)
# restore a backed up configfile
@octoprint.plugin.BlueprintPlugin.route("/backup/restore/<filename>", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def restore_backup(self, filename):
configpath = os.path.expanduser(
self._settings.get(["configuration", "config_path"])
)
data_folder = self.get_plugin_data_folder()
backupfile = os.path.realpath(os.path.join(data_folder, "configs", filename))
return flask.jsonify(restored = cfgUtils.copy_cfg(self, backupfile, configpath))
# API for Configs
# Get Content of a Configfile
@octoprint.plugin.BlueprintPlugin.route("/config/<filename>", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def get_config(self, filename):
cfg_path = os.path.expanduser(
self._settings.get(["configuration", "config_path"])
)
full_path = os.path.realpath(os.path.join(cfg_path, filename))
response = cfgUtils.get_cfg(self, full_path)
return flask.jsonify(response = response)
# Delete a Configfile
@octoprint.plugin.BlueprintPlugin.route("/config/<filename>", methods=["DELETE"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def delete_config(self, filename):
cfg_path = os.path.expanduser(
self._settings.get(["configuration", "config_path"])
)
full_path = os.path.realpath(os.path.join(cfg_path, filename))
if (
full_path.startswith(cfg_path)
and os.path.exists(full_path)
and not is_hidden_path(full_path)
):
try:
os.remove(full_path)
except Exception:
self._octoklipper_logger.exception("Could not delete {}".format(filename))
raise
return NO_CONTENT
# Get a list of all configfiles
@octoprint.plugin.BlueprintPlugin.route("/config/list", methods=["GET"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def list_configs(self):
files = cfgUtils.list_cfg_files(self, "")
path = os.path.expanduser(
self._settings.get(["configuration", "config_path"])
)
return flask.jsonify(files = files, path = path, max_upload_size = MAX_UPLOAD_SIZE)
# check syntax of a given data
@octoprint.plugin.BlueprintPlugin.route("/config/check", methods=["POST"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def check_config(self):
data = flask.request.json
data_to_check = data.get("DataToCheck", [])
response = cfgUtils.check_cfg_ok(self, data_to_check)
return flask.jsonify(is_syntax_ok = response)
# save a configfile
@octoprint.plugin.BlueprintPlugin.route("/config/save", methods=["POST"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def save_config(self):
data = flask.request.json
filename = data.get("filename", [])
if filename == []:
flask.abort(
400,
description="Invalid request, the filename is not set",
)
Filecontent = data.get("DataToSave", [])
saved = cfgUtils.save_cfg(self, Filecontent, filename)
if saved == True:
util.send_message(self, type = "reload", subtype = "configlist")
return flask.jsonify(saved = saved)
# restart klipper
@octoprint.plugin.BlueprintPlugin.route("/restart", methods=["POST"])
@restricted_access
@Permissions.PLUGIN_KLIPPER_CONFIG.require(403)
def restart_klipper(self):
reload_command = self._settings.get(["configuration", "reload_command"])
if reload_command != "manually":
# Restart klippy to reload config
self._printer.commands(reload_command)
logger.log_info(self, "Restarting Klipper.")
return flask.jsonify(command = reload_command)
# APIs end
self._settings.set(["config"], data["config"])
return flask.jsonify(data=data["config"])
elif command == "checkConfig":
if "config" in data:
if not self.validate_configfile(data["config"]):
self.log_debug("validateConfig not ok")
self._settings.set(["configuration", "old_config"], data["config"])
return flask.jsonify(checkConfig="not OK")
else:
self.log_debug("validateConfig ok")
self._settings.set(["configuration", "old_config"], "")
return flask.jsonify(checkConfig="OK")
def get_update_information(self):
return dict(
@ -526,129 +587,28 @@ class KlipperPlugin(
)
)
#-- Helpers
def send_message(self, type, subtype, title, payload):
self._plugin_manager.send_plugin_message(
self._identifier,
dict(
time=datetime.datetime.now().strftime("%H:%M:%S"),
type=type,
subtype=subtype,
title=title,
payload=payload
)
)
def poll_status(self):
self._printer.commands("STATUS")
def update_status(self, type, status):
self.send_message("status", type, status, status)
def log_info(self, message):
self._octoklipper_logger.info(message)
self.send_message("log", "info", message, message)
def log_debug(self, message):
self._octoklipper_logger.debug(message)
self._logger.info(message)
# sends a message to frontend(in klipper.js -> self.onDataUpdaterPluginMessage) and write it to the console.
# _mtype, subtype=debug/info, title of message, message)
self.send_message("console", "debug", message, message)
def log_error(self, error):
self._octoklipper_logger.error(error)
self._logger.info(error)
self.send_message("log", "error", error, error)
def file_exist(self, filepath):
if not os.path.isfile(filepath):
self.send_message("PopUp", "warning", "OctoKlipper Settings",
"Klipper " + filepath + " does not exist!")
return False
else:
return True
def key_exist(self, dict, key1, key2):
try:
dict[key1][key2]
except KeyError:
return False
else:
return True
def validate_configfile(self, dataToBeValidated):
"""
--->SyntaxCheck for a given data<----
"""
try:
dataToValidated = configparser.RawConfigParser(strict=False)
#
if sys.version_info[0] < 3:
buf = StringIO.StringIO(dataToBeValidated)
dataToValidated.readfp(buf)
else:
dataToValidated.read_string(dataToBeValidated)
sections_search_list = ["bltouch",
"probe"]
value_search_list = [ "x_offset",
"y_offset",
"z_offset"]
try:
# cycle through sections and then values
for y in sections_search_list:
for x in value_search_list:
if dataToValidated.has_option(y, x):
a_float = dataToValidated.getfloat(y, x)
except ValueError as error:
self.log_error(
"Error: Invalid Value for <b>"+x+"</b> in Section: <b>"+y+"</b>\n" +
"{}".format(str(error))
)
self.send_message("PopUp", "warning", "OctoKlipper: Invalid Config\n",
"Config got not saved!\n" +
"You can reload your last changes\n" +
"on the 'Klipper Configuration' tab.\n\n" +
"Invalid Value for <b>"+x+"</b> in Section: <b>"+y+"</b>\n" + "{}".format(str(error)))
self._parsing_check_response = False
return False
except configparser.Error as error:
if sys.version_info[0] < 3:
error.message = error.message.replace("\\n","")
error.message = error.message.replace("file: u","Klipper Configuration", 1)
error.message = error.message.replace("'","", 2)
error.message = error.message.replace("u'","'", 1)
else:
error.message = error.message.replace("\\n","")
error.message = error.message.replace("file:","Klipper Configuration", 1)
error.message = error.message.replace("'","", 2)
self.log_error(
"Error: Invalid Klipper config file:\n" +
"{}".format(str(error))
)
self.send_message("PopUp", "warning", "OctoKlipper: Invalid Config data\n",
"Config got not saved!\n" +
"You can reload your last changes\n" +
"on the 'Klipper Configuration' tab.\n\n" + str(error))
self._parsing_check_response = False
return False
else:
self._parsing_check_response = True
return True
__plugin_name__ = "OctoKlipper"
__plugin_pythoncompat__ = ">=2.7,<4"
__plugin_settings_overlay__ = {
'system': {
'actions': [{
'action': 'octoklipper_restart',
'command': 'sudo service klipper restart',
'name': gettext('Restart Klipper'),
'confirm': '<h3><center><b>' + gettext("You are about to restart Klipper!") + '<br>' + gettext("This will stop ongoing prints!") + '</b></center></h3><br>Command = "sudo service klipper restart"'
}]
}
}
def __plugin_load__():
global __plugin_implementation__
global __plugin_hooks__
__plugin_implementation__ = KlipperPlugin()
__plugin_hooks__ = {
"octoprint.server.http.routes": __plugin_implementation__.route_hook,
"octoprint.access.permissions": __plugin_implementation__.get_additional_permissions,
"octoprint.comm.protocol.atcommand.sending": __plugin_implementation__.processAtCommand,
"octoprint.comm.protocol.gcode.sent": __plugin_implementation__.process_sent_GCODE,
"octoprint.comm.protocol.gcode.received": __plugin_implementation__.on_parse_gcode,
"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
}

View File

@ -0,0 +1,301 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import glob
import os, time, sys
import io
import flask
from . import util, logger
from flask_babel import gettext
from shutil import copy, copyfile
try:
import configparser
except ImportError:
import ConfigParser as configparser
if sys.version_info[0] < 3:
import StringIO
def list_cfg_files(self, path):
"""Generate list of config files.
Args:
path (str): Path to the config files.
Returns:
list: for every file a dict with keys for name, file, size, mdate, url.
"""
files = []
if path=="backup":
cfg_path = os.path.join(self.get_plugin_data_folder(), "configs", "*")
else:
cfg_path = os.path.expanduser(
self._settings.get(["configuration", "config_path"])
)
cfg_path = os.path.join(cfg_path, "*.cfg")
cfg_files = glob.glob(cfg_path)
logger.log_debug(self, "list_cfg_files Path: " + cfg_path)
for f in cfg_files:
filesize = os.path.getsize(f)
filemdate = time.localtime(os.path.getmtime(f))
if path != "backup":
url = flask.url_for("index") + "plugin/klipper/download/configs/" + os.path.basename(f)
else:
url = flask.url_for("index") + "plugin/klipper/download/backup/" + os.path.basename(f)
files.append(dict(
name= os.path.basename(f),
file= f,
size= " ({:.1f} KB)".format(filesize / 1000.0),
mdate= time.strftime("%d.%m.%Y %H:%M", filemdate),
url= url,
))
logger.log_debug(self, "list_cfg_files " + str(len(files)) + ": " + f)
return files
def get_cfg(self, file):
"""Get the content of a configuration file.
Args:
file (str): The name of the file to read
Returns:
dict:
config (str): The configuration of the file
text (str): The text of the error
"""
response = {"config":"",
"text": ""}
if not file:
cfg_path = os.path.expanduser(
self._settings.get(["configuration", "configpath"])
)
file = os.path.join(cfg_path, self._settings.get(["configuration", "baseconfig"]))
if util.file_exist(self, file):
logger.log_debug(self, "get_cfg_files Path: " + file)
try:
with io.open(file, "r", encoding='utf-8') as f:
response['config'] = f.read()
except IOError as Err:
logger.log_error(
self,
gettext("Error: Klipper config file not found at:")
+ " {}".format(file)
+ "\n"
+ gettext("IOError:") + " {}".format(Err)
)
response['text'] = Err
return response
except UnicodeDecodeError as Err:
logger.log_error(
self,
gettext("Decode Error:")
+"\n"
+ "{}".format(Err)
+ "\n\n"
+ gettext("Please convert your config files to utf-8!")
+ "\n"
+ gettext("Or you can also paste your config \ninto the Editor and save it.")
)
response['text'] = Err
return response
else:
return response
else:
response['text'] = gettext("File not found!")
return response
def save_cfg(self, content, filename):
"""Save the configuration file to given file.
Args:
content (str): The content of the configuration.
filename (str): The filename of the configuration file. Default is "printer.cfg"
Returns:
bool: True if the configuration file was saved successfully. Otherwise False
"""
logger.log_debug(
self,
"Save klipper config"
)
configpath = os.path.expanduser(self._settings.get(["configuration", "config_path"]))
if filename == "":
filename = self._settings.get(["configuration", "baseconfig"])
if filename[-4:] != ".cfg":
filename += ".cfg"
filepath = os.path.join(configpath, filename)
logger.log_debug(self, "Writing Klipper config to {}".format(filepath))
try:
with io.open(filepath, "w", encoding='utf-8') as f:
f.write(content)
except IOError:
logger.log_error(self, "Error: Couldn't open Klipper config file: {}".format(filepath))
return False
else:
logger.log_debug(self, "Written Klipper config to {}".format(filepath))
return True
finally:
copy_cfg_to_backup(self, filepath)
def check_cfg_ok(self, data):
"""Checks the given data on parsing errors.
Args:
data (str): Content to be validated.
Returns:
bool: True if the data is valid. False if it is not.
"""
try:
dataToValidated = configparser.RawConfigParser(strict=False)
if sys.version_info[0] < 3:
import StringIO
buf = StringIO.StringIO(data)
dataToValidated.readfp(buf)
else:
dataToValidated.read_string(data)
except configparser.Error as error:
show_error_message(self, error)
logger.log_debug(self, 'check_cfg: NOK!')
return False
else:
if not is_float_ok(self, dataToValidated):
logger.log_debug(self, "check_cfg: NOK!")
return False
logger.log_debug(self, "check_cfg: OK")
return True
def show_error_message(self, error):
error.message = error.message.replace('\\n', '')
if sys.version_info[0] < 3:
error.message = error.message.replace('file: u', 'Klipper Configuration', 1)
error.message = error.message.replace("'", '', 2)
error.message = error.message.replace("u'", "'", 1)
else:
error.message = error.message.replace('file:', 'Klipper Configuration', 1)
error.message = error.message.replace("'", '', 2)
logger.log_error(
self,
('Error: Invalid Klipper config file:\n' + '{}'.format(str(error))),
)
def is_float_ok(self, dataToValidated):
sections_search_list = [
"bltouch",
"probe"
]
value_search_list = [
"x_offset",
"y_offset",
"z_offset"
]
try:
# cycle through sections and then values
for y in sections_search_list:
for x in value_search_list:
if dataToValidated.has_option(y, x):
a_float = dataToValidated.getfloat(y, x)
except ValueError as error:
logger.log_error(
self,
"Error: Invalid Value for <b>" + x + "</b> in Section: <b>" + y + "</b>\n"
+ "{}".format(str(error))
)
util.send_message(
self,
type = "PopUp",
subtype = "warning",
title = "Invalid Config data\n",
payload = "\n"
+ "Invalid Value for <b>" + x + "</b> in Section: <b>" + y + "</b>\n"
+ "{}".format(str(error))
)
return False
else:
return True
def copy_cfg(self, file, dst):
"""Copy the config file to the destination.
Args:
file (str): Filepath of the config file to copy.
dst (str): Path to copy the config file to.
Returns:
bool: True if the copy succeeded, False otherwise.
"""
if os.path.isfile(file):
try:
copy(file, dst)
except IOError:
logger.log_error(
self,
"Error: Klipper config file not found at: {}".format(file)
)
return False
else:
logger.log_debug(
self,
"File copied: "
+ file
)
return True
return False
def copy_cfg_to_backup(self, src):
"""Copy the config file to backup directory of OctoKlipper.
Args:
src (str): Path to the config file to copy.
Returns:
bool: True if the config file was copied successfully. False otherwise.
"""
if not os.path.isfile(src):
return False
cfg_path = os.path.join(self.get_plugin_data_folder(), "configs", "")
filename = os.path.basename(src)
if not os.path.exists(cfg_path):
try:
os.mkdir(cfg_path)
except OSError:
logger.log_error(self, "Error: Creation of the backup directory {} failed".format(cfg_path))
return False
else:
logger.log_debug(self, "Directory {} created".format(cfg_path))
dst = os.path.join(cfg_path, filename)
logger.log_debug(self, "copy_cfg_to_backup:" + src + " to " + dst)
if src == dst:
return False
try:
copyfile(src, dst)
except IOError:
logger.log_error(
self,
"Error: Couldn't copy Klipper config file to {}".format(dst)
)
return False
else:
logger.log_debug(self, "CfgBackup " + dst + " written")
return True

View File

@ -0,0 +1,33 @@
from . import util
def log_info(self, message):
self._octoklipper_logger.info(message)
util.send_message(
self,
type = "log",
subtype = "info",
title = message,
payload = message
)
def log_debug(self, message):
self._octoklipper_logger.debug(message)
self._logger.info(message)
util.send_message(
self,
type = "console",
subtype = "debug",
title = message,
payload = message
)
def log_error(self, error):
self._octoklipper_logger.error(error)
self._logger.error(error)
util.send_message(
self,
type = "log",
subtype = "error",
title = error,
payload = error
)

View File

@ -15,6 +15,7 @@
import flask
import optparse, datetime
from .. import logger
class KlipperLogAnalyzer():
MAXBANDWIDTH=25000.
@ -81,6 +82,7 @@ class KlipperLogAnalyzer():
out.append(keyparts)
f.close()
except IOError:
logger.log_error(self, "Couldn't open log file: {}".format(logname))
print("Couldn't open log file")
return out

View File

@ -0,0 +1,76 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrintClient"], factory);
} else {
factory(global.OctoPrintClient);
}
})(this, function (OctoPrintClient) {
var OctoKlipperClient = function (base) {
this.base = base;
this.url = this.base.getBlueprintUrl("klipper");
};
OctoKlipperClient.prototype.restartKlipper = function (opts) {
return this.base.post(this.url + "restart", opts);
};
OctoKlipperClient.prototype.getCfg = function (config, opts) {
return this.base.get(this.url + "config/" + config, opts);
};
OctoKlipperClient.prototype.getCfgBak = function (backup, opts) {
return this.base.get(this.url + "backup/" + backup, opts);
};
OctoKlipperClient.prototype.listCfg = function (opts) {
return this.base.get(this.url + "config/list", opts);
};
OctoKlipperClient.prototype.listCfgBak = function (opts) {
return this.base.get(this.url + "backup/list", opts);
};
OctoKlipperClient.prototype.checkCfg = function (content, opts) {
content = content || [];
var data = {
DataToCheck: content,
};
return this.base.postJson(this.url + "config/check", data, opts);
};
OctoKlipperClient.prototype.saveCfg = function (content, filename, opts) {
content = content || [];
filename = filename || [];
var data = {
DataToSave: content,
filename: filename,
};
return this.base.postJson(this.url + "config/save", data, opts);
};
OctoKlipperClient.prototype.deleteCfg = function (config, opts) {
return this.base.delete(this.url + "config/" + config, opts);
};
OctoKlipperClient.prototype.deleteBackup = function (backup, opts) {
return this.base.delete(this.url + "backup/" + backup, opts);
};
OctoKlipperClient.prototype.restoreBackup = function (backup, opts) {
return this.base.get(this.url + "backup/restore/" + backup, opts);
};
OctoKlipperClient.prototype.restoreBackupFromUpload = function (file, data) {
data = data || {};
var filename = data.filename || undefined;
return this.base.upload(this.url + "restore", file, filename, data);
};
OctoPrintClient.registerPluginComponent("klipper", OctoKlipperClient);
return OctoKlipperClient;
});

View File

@ -1,238 +1,384 @@
.plugin-klipper-sidebar {
padding: 1px;
height: auto;
border: 1px solid #aaa;
width: 98%;
text-align: center;
word-break: break-all;
margin: auto;
padding: 1px;
height: auto;
border: 1px solid #aaa;
width: 98%;
text-align: center;
word-break: break-word;
margin: auto;
}
li#navbar_plugin_klipper {
cursor: pointer;
max-width:360px;
max-height:80px;
word-break: break-all;
cursor: pointer;
max-width: 360px;
max-height: 80px;
word-break: break-all;
}
.plugin-klipper-sidebar a {
padding: 2px 2px;
text-align: center;
text-decoration: none;
display: inline-block;
cursor: pointer;
padding: 2px 2px;
text-align: center;
text-decoration: none;
display: inline-block;
cursor: pointer;
}
.plugin-klipper-sidebar a:hover, .plugin-klipper-sidebar a:active {
cursor: pointer;
}
.plugin-klipper-sidebar a:hover,
.plugin-klipper-sidebar a:active {
cursor: pointer;
}
.plugin-klipper-log {
padding: 0px;
overflow-y: scroll;
height: 400px;
border: 1px solid #eee;
width: 100%;
word-break: break-all;
padding: 0px;
overflow-y: scroll;
height: 400px;
border: 1px solid #eee;
width: 100%;
word-break: break-all;
}
.plugin-klipper-log .log-item {
margin: 3px auto 0 auto;
border: 1px solid #ddd;
border-radius: 3px;
background-color: #efefef;
color: #333;
margin: 3px auto 0 auto;
border: 1px solid #ddd;
border-radius: 3px;
background-color: #efefef;
color: #333;
}
.plugin-klipper-log .error {
background-color: #eebabb;
background-color: #eebabb;
}
.plugin-klipper-log .log-item .ts {
display: inline-block;
width: 13%;
height: 100%;
vertical-align: top;
font-size: 0.8em;
padding: 0 0 0 5px;
display: inline-block;
width: 13%;
height: 100%;
vertical-align: top;
font-size: 0.8em;
padding: 0 0 0 5px;
}
.plugin-klipper-log .log-item .msg {
display: inline-block;
width: 84%;
height: 100%;
display: inline-block;
width: 84%;
height: 100%;
}
.clear-btn {
margin-top: 6px;
margin-bottom: 6px;
margin-top: 6px;
margin-bottom: 6px;
}
#level .controls {
padding: 1px;
padding: 1px;
}
ul#klipper-settings {
margin: 0;
margin: 0;
}
#klipper-settings a{
margin: 5px;
#klipper-settings a {
margin: 5px;
}
#tab_plugin_klipper_main .row-fluid {
display: flex;
flex: row wrap;
align-items: stretch;
display: flex;
flex-flow: row wrap;
align-items: stretch;
}
@media all and (max-width: 940px) {
#tab_plugin_klipper_main .row-fluid {
/* On small screens, we are no longer using row direction but column */
flex-direction: column;
}
}
#tab_plugin_klipper_main .row-fluid {
/* On small screens, we are no longer using row direction but column */
flex-direction: column;
}
}
#tab_plugin_klipper_main #left-side {
flex: 3 1;
padding-right: 10px;
flex: 3 1;
padding-right: 10px;
padding-top: 5px;
}
#tab_plugin_klipper_main .span8 label {
float: left;
float: left;
}
#tab_plugin_klipper_main #right-side {
flex: 1 1;
max-width: 200px;
min-width: 100px;
flex: 1 1;
max-width: 200px;
min-width: 100px;
}
.klipper-row-fluid {
display: flex;
flex-flow: row wrap;
align-items: stretch;
}
.klipper-column-fluid {
display: flex;
flex-flow: column nowrap;
align-items: stretch;
}
.klipper-fluid-item-1 {
flex: 1 auto;
}
.klipper-fluid-item-2 {
flex: 2 auto;
}
.klipper-fluid-item-3 {
flex: 3 auto;
}
.gap {
justify-content: space-evenly;
}
@media all and (max-width: 940px) {
.klipper-row-fluid {
/* On small screens, we are no longer using row direction but column */
flex-direction: column;
}
}
#settings_plugin_klipper {
height: 100%;
height: 100%;
height: -webkit-fill-available;
}
div#klipper_backups_dialog {
display: flex;
flex-flow: column;
min-height: 400px;
}
div#klipper_backups_dialog div.modal-body {
min-height: 350px;
display: flex;
flex-flow: column;
flex-grow: 1;
}
div#klipper_backups_dialog .editor-controls {
flex: 0 auto;
display: flex;
align-items: stretch;
flex-flow: row wrap;
}
div#klipper_backups_dialog div.modal-body .textarea {
overflow: auto;
}
div#klipper_backups_dialog div.modal-body textarea {
margin-bottom: 0px !important;
padding-left: 0px !important;
padding-right: 0px !important;
box-sizing: border-box;
resize: none;
width: 100%;
}
/* UIcustomizer fix */
body.UICResponsiveMode #settings_dialog_content {
height: calc(100% - 60px);
margin-right: -18px;
margin-top: 50px;
width: calc(100% - 15px);
}
div#settings_plugin_klipper form {
margin: 0px;
height: 100%;
margin: 0px;
height: 100%;
}
div#settings_plugin_klipper form .tab-content {
height: calc(100% - 40px);
overflow: auto;
div#settings_plugin_klipper div.tab-content {
height: calc(100% - 76px);
overflow: auto;
}
div#settings_plugin_klipper div.tab-content .border{
border-bottom: 1px solid;
}
div#settings_plugin_klipper div.tab-footer {
height: 20px;
width: 100%;
top: 10px;
position: relative;
border-top: 1px solid #eee;
padding-top: 3px;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane {
height: 100%;
height: 100%;
min-height: 200px;
width: 100%;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group {
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.editor-controls{
margin-bottom: 0px;
height: 26px;
.klipper-settings-tab {
height: 100%;
width: 100%;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.conf-editor {
height: 95%;
height: calc(100% - 28px);
width: 99%;
width: calc(100% - 4px);
padding-top: 2px;
flex: 1 1;
overflow: auto;
#settings_plugin_klipper .m-0 {
margin: 0;
}
div#settings_plugin_klipper div.tab-content div#conf.tab-pane div.control-group div.conf-editor div#plugin-klipper-config {
font-family: monospace;
overflow: auto;
height: 100%;
height: -webkit-fill-available;
#settings_plugin_klipper .scroll-y {
overflow-y: scroll;
}
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div#conf.tab-pane.active button.btn.btn-small {
width: 30%;
display: inline-block;
margin: 0px 2px 2px 2px;
#settings_plugin_klipper table {
table-layout: unset !important;
}
#settings_plugin_klipper .table-fixed thead th {
position: sticky;
top: 0;
background-color: rgba(12, 5, 5, 0.85);
color: rgb(200, 200, 200);
}
#settings_plugin_klipper .pagination-mini {
margin: 5;
}
@media (max-width: 979px) {
/* div#klipper_editor.modal {
height: unset !important;
} */
div#klipper_editor .modal-footer .btn {
margin-bottom: 1px;
margin-left: 5px;
}
}
.octoprint-container.UICMainCont.container-fluid {
margin-top:0px !important;
}
div#klipper_editor .modal-header div button.btn + button.btn {
margin-right: 12px;
}
div#klipper_editor .modal-body {
overflow: auto;
}
.klipper-btn-group {
display: inline-block;
}
div#klipper_editor .modal-footer input[type="text"] {
margin-bottom: 0px !important;
}
div#klipper_editor .modal-footer .editor-controls {
flex: 0 auto;
display: flex;
align-items: center;
}
div#klipper_editor div.conf-editor {
width: 99%;
width: calc(100% - 4px);
margin-top: 5px;
overflow: auto;
}
div#klipper_editor div.conf-editor div#plugin-klipper-config {
font-family: monospace;
overflow: auto;
width: 100%;
margin: auto;
}
/*checkboxes*/
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active input.inline-checkbox {
vertical-align: -0.2em;
vertical-align: -0.2em;
}
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active label.inline {
display: inline;
.klipper-inline {
display: inline;
}
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div.tab-pane.active div.controls input.controls-checkbox {
margin-top: 8px;
margin-top: 8px;
}
/*macros*/
div#settings_plugin_klipper.tab-pane.active form.form-horizontal div.tab-content div#macros.tab-pane.active div div#item.control-group label.control-label {
width: 80px;
div#settings_plugin_klipper div#macros label.control-label {
width: 80px;
}
#macros #item.control-group {
margin-bottom: 2px;
border: 2px solid #ccc;
border-radius: 3px;
background-color: #eeeeee;
color: #333;
padding-bottom: 2px;
padding-top: 2px;
margin-bottom: 2px;
border: 2px solid #ccc;
border-radius: 3px;
background-color: #eeeeee;
color: #333;
padding-bottom: 2px;
padding-top: 2px;
}
#klipper_graph_dialog {
width: 1050px;
}
#klipper_graph_dialog .full-sized-box{
width: 1000px;
margin: 0 auto;
#klipper_graph_dialog .full-sized-box {
width: 100%;
margin: 0 auto;
}
#klipper_graph_dialog form {
margin: 0;
margin: 0;
}
#klipper_graph_dialog select {
width: auto;
width: auto;
}
#klipper_graph_dialog .graph-footer {
bottom:0;
bottom: 0;
}
#klipper_graph_dialog input {
display: inline-block;
display: inline-block;
}
#klipper_graph_dialog .status-label {
display: block;
position: absolute;
margin: 5px 0 0 10px;
display: block;
position: absolute;
margin: 5px 0 0 10px;
}
#klipper_graph_dialog .fill-checkbox {
display: block;
position: absolute;
top: 0%;
left: 50%;
display: block;
position: absolute;
top: 0%;
left: 50%;
}
#klipper_graph_dialog .help-inline {
display: block;
position: absolute;
top: 0px;
display: block;
position: absolute;
top: 0px;
}
#klipper_graph_canvas {
margin-top: 15px;
margin-top: 15px;
}

View File

@ -14,251 +14,337 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
$(function () {
function KlipperViewModel(parameters) {
var self = this;
function KlipperViewModel(parameters) {
var self = this;
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache"
});
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache",
});
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
self.Url = OctoPrint.getBlueprintUrl("klipper");
self.settings = parameters[0];
self.loginState = parameters[1];
self.connectionState = parameters[2];
self.levelingViewModel = parameters[3];
self.paramMacroViewModel = parameters[4];
self.access = parameters[5];
self.settings = parameters[0];
self.loginState = parameters[1];
self.connectionState = parameters[2];
self.levelingViewModel = parameters[3];
self.paramMacroViewModel = parameters[4];
self.access = parameters[5];
self.shortStatus_navbar = ko.observable();
self.shortStatus_sidebar = ko.observable();
self.logMessages = ko.observableArray();
self.shortStatus_navbar = ko.observable();
self.shortStatus_navbar_hover = ko.observable();
self.shortStatus_sidebar = ko.observable();
self.logMessages = ko.observableArray();
self.showPopUp = function(popupType, popupTitle, message){
var title = popupType.toUpperCase() + ": " + popupTitle;
new PNotify({
title: title,
text: message,
type: popupType,
hide: false
});
};
self.showPopUp = function (popupType, popupTitle, message) {
var title = "OctoKlipper: <br />" + popupTitle + "<br />";
var hide = false;
if (popupType == "success") {
hide = true
}
new PNotify({
title: title,
text: message,
type: popupType,
hide: hide,
icon: true
});
};
self.onSettingsShown = function () {
self.reloadConfig();
}
self.showLevelingDialog = function () {
var dialog = $("#klipper_leveling_dialog");
dialog.modal({
show: "true",
backdrop: "static",
keyboard: false
});
self.levelingViewModel.initView();
};
self.showPidTuningDialog = function () {
var dialog = $("#klipper_pid_tuning_dialog");
dialog.modal({
show: "true",
backdrop: "static",
keyboard: false
});
};
self.showOffsetDialog = function () {
var dialog = $("#klipper_offset_dialog");
dialog.modal({
show: "true",
backdrop: "static"
});
};
self.showGraphDialog = function () {
var dialog = $("#klipper_graph_dialog");
dialog.modal({
show: "true",
minHeight: "500px",
maxHeight: "600px"
});
};
self.executeMacro = function (macro) {
var paramObjRegex = /{(.*?)}/g;
if (!self.hasRight("MACRO")) return;
if (macro.macro().match(paramObjRegex) == null) {
OctoPrint.control.sendGcode(
// Use .split to create an array of strings which is sent to
// OctoPrint.control.sendGcode instead of a single string.
macro.macro().split(/\r\n|\r|\n/)
);
} else {
self.paramMacroViewModel.process(macro);
var dialog = $("#klipper_macro_dialog");
dialog.modal({
show: "true",
backdrop: "static"
});
}
};
self.navbarClicked = function () {
$("#tab_plugin_klipper_main_link").find("a").click();
};
self.onGetStatus = function () {
OctoPrint.control.sendGcode("Status");
};
self.onRestartFirmware = function () {
OctoPrint.control.sendGcode("FIRMWARE_RESTART");
};
self.onRestartHost = function () {
OctoPrint.control.sendGcode("RESTART");
};
self.onAfterBinding = function () {
self.connectionState.selectedPort(
self.settings.settings.plugins.klipper.connection.port()
);
};
self.onDataUpdaterPluginMessage = function(plugin, data) {
if(plugin == "klipper") {
switch(data.type) {
case "PopUp":
self.showPopUp(data.subtype, data.title, data.payload);
break;
case "reload":
break;
case "console":
self.consoleMessage(data.subtype, data.payload);
break;
case "status":
if (data.payload.length > 36) {
var shortText = data.payload.substring(0, 31) + " [..]"
self.shortStatus_navbar(shortText);
} else {
self.shortStatus_navbar(data.payload);
}
self.shortStatus_sidebar(data.payload);
break;
default:
self.logMessage(data.time, data.subtype, data.payload);
self.consoleMessage(data.subtype, data.payload);
}
//if ("warningPopUp" == data.type){
// self.showPopUp(data.subtype, data.title, data.payload);
// return;
//} else if ("errorPopUp" == data.type){
// self.showPopUp(data.subtype, data.title, data.payload);
// return;
//} else if ("reload" == data.type){
// return;
//} else if ("console" == data.type) {
// self.consoleMessage(data.subtype, data.payload);
//} else if (data.type == "status") {
// self.shortStatus(data.payload);
//} else {
// self.logMessage(data.time, data.subtype, data.payload);
//}
}
};
self.logMessage = function (timestamp, type="info", message) {
if (!timestamp) {
var today = new Date();
var timestamp = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
}
self.logMessages.push({
time: timestamp,
type: type,
msg: message.replace(/\n/gi, "<br>")
});
};
self.consoleMessage = function (type, message) {
if (self.settings.settings.plugins.klipper.configuration.debug_logging() === true) {
if (type == "info"){
console.info("OctoKlipper : " + message);
} else if (type == "debug"){
console.debug("OctoKlipper : " + message);
} else {
console.error("OctoKlipper : " + message);
}
}
return
};
self.reloadConfig = function() {
var settings = {
"crossDomain": true,
"url": self.apiUrl,
"method": "POST",
"headers": self.header,
"processData": false,
"dataType": "json",
"data": JSON.stringify({command: "reloadConfig"})
}
$.ajax(settings).done(function (response) {
self.consoleMessage(
"debug",
"Reloaded config file from Backend");
});
}
self.onClearLog = function () {
self.logMessages.removeAll();
};
self.isActive = function () {
return self.connectionState.isOperational();
};
self.hasRight = function (right_role, type) {
var arg = eval("self.access.permissions.PLUGIN_KLIPPER_" + right_role);
if (type == "Ko") {
return self.loginState.hasPermissionKo(arg);
}
return self.loginState.hasPermission(arg);
};
// OctoKlipper settings link
self.openOctoKlipperSettings = function (profile_type) {
if (!self.hasRight("CONFIG")) return;
$("a#navbar_show_settings").click();
$("li#settings_plugin_klipper_link a").click();
if (profile_type) {
var query = "#klipper-settings a[data-profile-type='" + profile_type + "']";
$(query).click();
}
};
self.showEditorDialog = function () {
if (!self.hasRight("CONFIG")) return;
var editorDialog = $("#klipper_editor");
editorDialog.modal({
show: "true",
width: "90%",
backdrop: "static",
});
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperViewModel,
dependencies: [
"settingsViewModel",
"loginStateViewModel",
"connectionViewModel",
"klipperLevelingViewModel",
"klipperMacroDialogViewModel",
"accessViewModel"
],
elements: [
"#tab_plugin_klipper_main",
"#sidebar_plugin_klipper",
"#navbar_plugin_klipper"
]
});
self.showLevelingDialog = function () {
var dialog = $("#klipper_leveling_dialog");
dialog.modal({
show: "true",
backdrop: "static",
keyboard: false,
});
self.levelingViewModel.initView();
};
self.showPidTuningDialog = function () {
var dialog = $("#klipper_pid_tuning_dialog");
dialog.modal({
show: "true",
backdrop: "static",
keyboard: false,
});
};
self.showOffsetDialog = function () {
var dialog = $("#klipper_offset_dialog");
dialog.modal({
show: "true",
backdrop: "static",
});
};
self.showGraphDialog = function () {
var dialog = $("#klipper_graph_dialog");
dialog.modal({
show: "true",
width: "90%",
minHeight: "500px",
maxHeight: "600px",
});
};
self.executeMacro = function (macro) {
var paramObjRegex = /{(.*?)}/g;
if (!self.hasRight("MACRO")) return;
if (macro.macro().match(paramObjRegex) == null) {
OctoPrint.control.sendGcode(
// Use .split to create an array of strings which is sent to
// OctoPrint.control.sendGcode instead of a single string.
macro.macro().split(/\r\n|\r|\n/)
);
} else {
self.paramMacroViewModel.process(macro);
var dialog = $("#klipper_macro_dialog");
dialog.modal({
show: "true",
backdrop: "static",
});
}
};
self.navbarClicked = function () {
$("#tab_plugin_klipper_main_link").find("a").click();
};
self.onGetStatus = function () {
OctoPrint.control.sendGcode("Status");
};
self.onRestartFirmware = function () {
OctoPrint.control.sendGcode("FIRMWARE_RESTART");
};
self.onRestartHost = function () {
OctoPrint.control.sendGcode("RESTART");
};
self.onAfterBinding = function () {
self.connectionState.selectedPort(
self.settings.settings.plugins.klipper.connection.port()
);
};
self.onDataUpdaterPluginMessage = function (plugin, data) {
if (plugin == "klipper") {
switch (data.type) {
case "PopUp":
self.showPopUp(data.subtype, data.title, data.payload);
break;
case "reload":
break;
case "console":
self.consoleMessage(data.subtype, data.payload);
break;
case "status":
self.shortStatus(data.payload, data.subtype);
break;
default:
self.logMessage(data.time, data.subtype, data.payload);
self.shortStatus(data.payload, data.subtype)
self.consoleMessage(data.subtype, data.payload);
}
}
};
self.shortStatus = function(msg, type) {
var baseText = gettext("Go to OctoKlipper Tab");
if (msg.length > 36) {
var shortText = msg.substring(0, 31) + " [..]";
self.shortStatus_navbar(shortText);
self.shortStatus_navbar_hover(msg);
} else {
self.shortStatus_navbar(msg);
self.shortStatus_navbar_hover(baseText);
}
message = msg.replace(/\n/gi, "<br />");
self.shortStatus_sidebar(message);
};
self.logMessage = function (timestamp, type = "info", message) {
if (!timestamp) {
var today = new Date();
var timestamp =
today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
}
if (type == "error") {
self.showPopUp(type, "Error:", message);
}
self.logMessages.push({
time: timestamp,
type: type,
msg: message.replace(/\n/gi, "<br />"),
});
};
self.consoleMessage = function (type, message) {
if (
self.settings.settings.plugins.klipper.configuration.debug_logging() === true
) {
if (type == "info") {
console.info("OctoKlipper : " + message);
} else if (type == "debug") {
console.debug("OctoKlipper : " + message);
} else {
console.error("OctoKlipper : " + message);
}
}
return;
};
self.onClearLog = function () {
self.logMessages.removeAll();
};
self.isActive = function () {
return self.connectionState.isOperational();
};
self.hasRight = function (right_role) {
//if (self.loginState.isAdmin) return true;
if (right_role == "CONFIG") {
return self.loginState.hasPermission(
self.access.permissions.PLUGIN_KLIPPER_CONFIG
);
} else if (right_role == "MACRO") {
return self.loginState.hasPermission(
self.access.permissions.PLUGIN_KLIPPER_MACRO
);
}
};
self.hasRightKo = function (right_role) {
//if (self.loginState.isAdmin) return true;
if (right_role == "CONFIG") {
return self.loginState.hasPermissionKo(
self.access.permissions.PLUGIN_KLIPPER_CONFIG
);
} else if (right_role == "MACRO") {
return self.loginState.hasPermissionKo(
self.access.permissions.PLUGIN_KLIPPER_MACRO
);
}
};
self.saveOption = function(dir, option, value) {
if (! (_.includes(["fontsize", "confirm_reload", "parse_check"], option)) ) {
return;
}
if (option && dir) {
let data = {
plugins: {
klipper:{
[dir]: {
[option]: value
}
}
}
};
OctoPrint.settings
.save(data);
} else if (option) {
let data = {
plugins: {
klipper:{
[option]: value
}
}
};
OctoPrint.settings
.save(data);
}
}
self.requestRestart = function () {
if (!self.loginState.hasPermission(self.access.permissions.PLUGIN_KLIPPER_CONFIG)) return;
var request = function (index) {
OctoPrint.plugins.klipper.restartKlipper().done(function (response) {
self.consoleMessage("debug", "restartingKlipper");
self.showPopUp("success", gettext("Restarted Klipper"), "command: " + response.command);
});
if (index == 1) {
self.saveOption("configuration", "confirm_reload", false);
}
};
var html = "<h4>" +
gettext("All ongoing Prints will be stopped!") +
"</h4>";
if (self.settings.settings.plugins.klipper.configuration.confirm_reload() == true) {
showConfirmationDialog({
title: gettext("Restart Klipper?"),
html: html,
proceed: [gettext("Restart"), gettext("Restart and don't ask this again.")],
onproceed: function (idx) {
if (idx > -1) {
request(idx);
}
},
});
} else {
request(0);
}
};
// OctoKlipper settings link
self.openOctoKlipperSettings = function (profile_type) {
if (!self.hasRight("CONFIG")) return;
$("a#navbar_show_settings").click();
$("li#settings_plugin_klipper_link a").click();
if (profile_type) {
var query = "#klipper-settings a[data-profile-type='" + profile_type + "']";
$(query).click();
}
};
self.sleep = function (ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperViewModel,
dependencies: [
"settingsViewModel",
"loginStateViewModel",
"connectionViewModel",
"klipperLevelingViewModel",
"klipperMacroDialogViewModel",
"accessViewModel",
],
elements: [
"#tab_plugin_klipper_main",
"#sidebar_plugin_klipper",
"#navbar_plugin_klipper",
],
});
});

View File

@ -0,0 +1,297 @@
// <Octoprint Klipper Plugin>
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
$(function () {
function KlipperBackupViewModel(parameters) {
var self = this;
self.loginState = parameters[0];
self.klipperViewModel = parameters[1];
self.access = parameters[2];
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache",
});
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
self.Url = OctoPrint.getBlueprintUrl("klipper");
self.markedForFileRestore = ko.observableArray([]);
self.CfgContent = ko.observable();
//uploads
self.maxUploadSize = ko.observable(0);
self.backupUploadData = undefined;
self.backupUploadName = ko.observable();
self.isAboveUploadSize = function (data) {
return data.size > self.maxUploadSize();
};
self.onStartupComplete = function () {
$('#klipper_backups_dialog').css('display', 'none');
if (self.loginState.loggedIn()) {
self.listBakFiles();
}
};
// initialize list helper
self.backups = new ItemListHelper(
"klipperBakFiles",
{
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;
},
date: function (a, b) {
// sorts descending
if (a["date"] > b["date"]) return -1;
if (a["date"] < b["date"]) return 1;
return 0;
},
size: function (a, b) {
// sorts descending
if (a["bytes"] > b["bytes"]) return -1;
if (a["bytes"] < b["bytes"]) return 1;
return 0;
},
},
{},
"name",
[],
[],
5
);
self.listBakFiles = function () {
self.klipperViewModel.consoleMessage("debug", "listBakFiles");
OctoPrint.plugins.klipper.listCfgBak()
.done(function (response) {
self.backups.updateItems(response.files);
self.backups.resetPage();
});
};
self.showCfg = function (backup) {
if (!self.loginState.hasPermission(self.access.permissions.PLUGIN_KLIPPER_CONFIG)) return;
OctoPrint.plugins.klipper.getCfgBak(backup).done(function (response) {
$('#klipper_backups_dialog textarea').attr('rows', response.response.config.split(/\r\n|\r|\n/).length);
self.CfgContent(response.response.config);
});
};
self.removeCfg = function (backup) {
if (!self.loginState.hasPermission(self.access.permissions.PLUGIN_KLIPPER_CONFIG)) return;
var perform = function () {
OctoPrint.plugins.klipper
.deleteBackup(backup)
.done(function () {
self.listBakFiles();
})
.fail(function (response) {
var html = "<p>" + _.sprintf(gettext("Failed to remove config %(name)s.</p><p>Please consult octoprint.log for details.</p>"), { name: _.escape(backup) });
html += pnotifyAdditionalInfo('<pre style="overflow: auto">' + _.escape(response.responseText) + "</pre>");
new PNotify({
title: gettext("Could not remove config"),
text: html,
type: "error",
hide: false,
});
});
};
showConfirmationDialog(
_.sprintf(gettext('You are about to delete backed config file "%(name)s".'), {
name: _.escape(backup),
}),
perform
);
};
self.restoreBak = function (backup) {
if (!self.loginState.hasPermission(self.access.permissions.PLUGIN_KLIPPER_CONFIG)) return;
var restore = function () {
OctoPrint.plugins.klipper.restoreBackup(backup).done(function (response) {
self.klipperViewModel.consoleMessage("debug", "restoreCfg: " + backup + " / " + response.restored);
});
};
var html = "<p>" + gettext("This will overwrite any file with the same name on the configpath.") + "</p>" + "<p>" + backup + "</p>";
showConfirmationDialog({
title: gettext("Are you sure you want to restore now?"),
html: html,
proceed: gettext("Proceed"),
onproceed: restore,
});
};
self.markFilesOnPage = function () {
self.markedForFileRestore(_.uniq(self.markedForFileRestore().concat(_.map(self.backups.paginatedItems(), "file"))));
};
self.markAllFiles = function () {
self.markedForFileRestore(_.map(self.backups.allItems, "file"));
};
self.clearMarkedFiles = function () {
self.markedForFileRestore.removeAll();
};
self.restoreMarkedFiles = function () {
var perform = function () {
self._bulkRestore(self.markedForFileRestore()).done(function () {
self.markedForFileRestore.removeAll();
});
};
showConfirmationDialog(
_.sprintf(gettext("You are about to restore %(count)d backed config files."), {
count: self.markedForFileRestore().length,
}),
perform
);
};
self.removeMarkedFiles = function () {
var perform = function () {
self._bulkRemove(self.markedForFileRestore()).done(function () {
self.markedForFileRestore.removeAll();
});
};
showConfirmationDialog(
_.sprintf(gettext("You are about to delete %(count)d backed config files."), {
count: self.markedForFileRestore().length,
}),
perform
);
};
self._bulkRestore = function (files) {
var title, message, handler;
title = gettext("Restoring klipper config files");
self.klipperViewModel.consoleMessage("debug", title);
message = _.sprintf(gettext("Restoring %(count)d backed config files..."), {
count: files.length,
});
handler = function (filename) {
return OctoPrint.plugins.klipper
.restoreBackup(filename)
.done(function (response) {
deferred.notify(
_.sprintf(gettext("Restored %(filename)s..."), {
filename: _.escape(filename),
}),
true
);
self.klipperViewModel.consoleMessage("debug", "restoreCfg: " + filename + " / " + response);
self.markedForFileRestore.remove(function (item) {
return item.name == filename;
});
})
.fail(function () {
deferred.notify(_.sprintf(gettext("Restoring of %(filename)s failed, continuing..."), { filename: _.escape(filename) }), false);
});
};
var deferred = $.Deferred();
var promise = deferred.promise();
var options = {
title: title,
message: message,
max: files.length,
output: true,
};
showProgressModal(options, promise);
var requests = [];
_.each(files, function (filename) {
var request = handler(filename);
requests.push(request);
});
$.when.apply($, _.map(requests, wrapPromiseWithAlways)).done(function () {
deferred.resolve();
});
return promise;
};
self._bulkRemove = function (files) {
var title, message, handler;
title = gettext("Deleting backup files");
message = _.sprintf(gettext("Deleting %(count)d backed files..."), {
count: files.length,
});
handler = function (filename) {
return OctoPrint.plugins.klipper
.deleteBackup(filename)
.done(function () {
deferred.notify(_.sprintf(gettext("Deleted %(filename)s..."), { filename: _.escape(filename) }), true);
self.markedForFileRestore.remove(function (item) {
return item.name == filename;
});
})
.fail(function () {
deferred.notify(_.sprintf(gettext("Deleting of %(filename)s failed, continuing..."), { filename: _.escape(filename) }), false);
});
};
var deferred = $.Deferred();
var promise = deferred.promise();
var options = {
title: title,
message: message,
max: files.length,
output: true,
};
showProgressModal(options, promise);
var requests = [];
_.each(files, function (filename) {
var request = handler(filename);
requests.push(request);
});
$.when.apply($, _.map(requests, wrapPromiseWithAlways)).done(function () {
deferred.resolve();
self.listBakFiles();
});
return promise;
};
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperBackupViewModel,
dependencies: ["loginStateViewModel", "klipperViewModel", "accessViewModel"],
elements: ["#klipper_backups_dialog"],
});
});

View File

@ -0,0 +1,398 @@
// <Octoprint Klipper Plugin>
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
$(function () {
function KlipperEditorViewModel(parameters) {
var self = this;
var editor = null;
var editordialog = $("#klipper_editor");
self.settings = parameters[0];
self.klipperViewModel = parameters[1];
self.CfgFilename = ko.observable("");
self.CfgContent = ko.observable("");
self.loadedConfig = "";
self.CfgChangedExtern = false;
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache",
});
$(window).on('resize', function() {
self.klipperViewModel.sleep(200).then(
function () {
self.setEditorDivSize();
}
);
});
self.onShown = function () {
self.checkExternChange();
editor.focus();
self.setEditorDivSize();
};
self.close_selection = function (index) {
switch (index) {
case 0:
editordialog.modal('hide');
break;
case 1:
self.editorFocusDelay(1000);
break;
case 2:
self.saveCfg({closing: true});
break;
}
};
self.closeEditor = function () {
self.CfgContent(editor.getValue());
if (self.loadedConfig != self.CfgContent()) {
var opts = {
title: gettext("Closing without saving"),
message: gettext("Your file seems to have changed.")
+ "<br />"
+ gettext("Do you really want to close it?"),
selections: [gettext("Close"), gettext("Do not close"), gettext("Save & Close")],
maycancel: false,
onselect: function (index) {
if (index > -1) {
self.close_selection(index);
}
},
};
showSelectionDialog(opts);
} else {
editordialog.modal('hide');
}
};
self.addStyleAttribute = function ($element, styleAttribute) {
$element.attr('style', styleAttribute);
};
self.setEditorDivSize = function () {
var klipper_modal_body= $('#klipper_editor .modal-body');
var klipper_config= $('#plugin-klipper-config');
var height = $(window).height() - $('#klipper_editor .modal-header').outerHeight() - $('#klipper_editor .modal-footer').outerHeight() - 118;
self.addStyleAttribute(klipper_modal_body, 'height: ' + height + 'px !important;');
klipper_config.css('height', height);
if (editor) {
editor.resize();
}
};
//initialize the modal window and return done when finished
self.process = function (config) {
return new Promise(function (resolve) {
self.loadedConfig = config.content;
self.CfgFilename(config.file);
self.CfgContent(config.content);
if (editor) {
editor.session.setValue(self.CfgContent());
self.CfgChangedExtern = false;
editor.setFontSize(self.settings.settings.plugins.klipper.configuration.fontsize());
editor.clearSelection();
self.klipperViewModel.sleep(500).then(
function() {
self.setEditorDivSize();
resolve("done");
}
);
}
});
};
self.onDataUpdaterPluginMessage = function (plugin, data) {
//receive from backend after a SAVE_CONFIG
if (plugin == "klipper" && data.type == "reload" && data.subtype == "config") {
self.klipperViewModel.consoleMessage("debug", "onDataUpdaterPluginMessage klipper reload baseconfig");
self.ConfigChangedAfterSave_Config();
}
};
//set externally changed config flag if the current file is the base config
self.ConfigChangedAfterSave_Config = function () {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
if (self.CfgFilename() == self.settings.settings.plugins.klipper.configuration.baseconfig()) {
self.CfgChangedExtern = true;
self.checkExternChange();
}
};
//check if the config was externally changed and ask for a reload
self.checkExternChange = function() {
var baseconfig = self.settings.settings.plugins.klipper.configuration.baseconfig();
if (self.CfgChangedExtern && self.CfgFilename() == baseconfig) {
if (editordialog.is(":visible")) {
var perform = function () {
self.reloadFromFile();
}
var html = "<p>" + gettext("Reload Configfile after SAVE_CONFIG?") + "</p>";
showConfirmationDialog({
title: gettext("Externally changed config") + " " + baseconfig,
html: html,
proceed: gettext("Proceed"),
onproceed: perform,
});
}
}
};
self.askSaveFaulty = function () {
return new Promise(function (resolve) {
var html = "<h5>" +
gettext("Your configuration seems to be faulty.") +
"</h5>";
showConfirmationDialog({
title: gettext("Save faulty Configuration?"),
html: html,
cancel: gettext("Do not save!"),
proceed: [gettext("Save anyway!"), gettext("Save anyway and don't ask this again.")],
onproceed: function (idx) {
if (idx == 0) {
resolve(true);
} else {
self.klipperViewModel.saveOption("configuration", "parse_check", false);
resolve(true);
}
},
oncancel: function () {
resolve(false);
}
});
});
};
self.checkSyntax = function () {
return new Promise((resolve, reject) => {
if (editor.session) {
self.klipperViewModel.consoleMessage("debug", "checkSyntax started");
OctoPrint.plugins.klipper.checkCfg(editor.session.getValue())
.done(function (response) {
if (response.is_syntax_ok == true) {
self.klipperViewModel.showPopUp("success", gettext("SyntaxCheck"), gettext("SyntaxCheck OK"));
self.editorFocusDelay(1000);
resolve(true);
} else {
self.editorFocusDelay(1000);
resolve(false);
}
})
.fail(function () {
reject(false);
});
} else { reject(false); }
});
};
self.saveCfg = function (options) {
var options = options || {};
var closing = options.closing || false;
if (self.CfgFilename() != "") {
if (editor.session) {
if (self.settings.settings.plugins.klipper.configuration.parse_check() == true) {
// check Syntax and wait for response
self.checkSyntax().then((syntaxOK) => {
if (syntaxOK === false) {
// Ask if we should save a faulty config anyway
self.askSaveFaulty().then((areWeSaving) => {
if (areWeSaving === false) {
// Not saving
showMessageDialog(
gettext('Faulty config not saved!'),
{
title: gettext("Save Config"),
onclose: function () { self.editorFocusDelay(1000); }
}
);
} else {
// Save anyway
self.saveRequest(closing);
}
});
} else {
// Syntax is ok
self.saveRequest(closing);
}
});
} else {
self.saveRequest(closing);
}
}
} else {
showMessageDialog(
gettext("No filename set"),
{
title: gettext("Save Config")
}
);
}
};
self.minusFontsize = function () {
self.settings.settings.plugins.klipper.configuration.fontsize(
self.settings.settings.plugins.klipper.configuration.fontsize() - 1
);
if (self.settings.settings.plugins.klipper.configuration.fontsize() < 9) {
self.settings.settings.plugins.klipper.configuration.fontsize(9);
}
var fontsize = self.settings.settings.plugins.klipper.configuration.fontsize();
if (editor) {
editor.setFontSize(fontsize);
editor.resize();
}
self.klipperViewModel.saveOption("configuration", "fontsize", fontsize);
};
self.plusFontsize = function () {
self.settings.settings.plugins.klipper.configuration.fontsize(
self.settings.settings.plugins.klipper.configuration.fontsize() + 1
);
if (self.settings.settings.plugins.klipper.configuration.fontsize() > 20) {
self.settings.settings.plugins.klipper.configuration.fontsize(20);
}
var fontsize = self.settings.settings.plugins.klipper.configuration.fontsize();
if (editor) {
editor.setFontSize(fontsize);
editor.resize();
}
self.klipperViewModel.saveOption("configuration", "fontsize", fontsize);
};
self.reloadFromFile = function () {
if (self.CfgFilename() != "") {
OctoPrint.plugins.klipper.getCfg(self.CfgFilename())
.done(function (response) {
self.klipperViewModel.consoleMessage("debug", "reloadFromFile done");
if (response.response.text != "") {
showMessageDialog(
response.response.text,
{
title: gettext("Reload File")
}
);
} else {
self.klipperViewModel.showPopUp("success", gettext("Reload Config"), gettext("File reloaded."));
self.CfgChangedExtern = false;
if (editor) {
editor.session.setValue(response.response.config);
self.loadedConfig = response.response.config;
editor.clearSelection();
editor.focus();
}
}
})
.fail(function (response) {
showMessageDialog(
response,
{
title: gettext("Reload File")
}
);
});
} else {
showMessageDialog(
gettext("No filename set"),
{
title: gettext("Reload File")
}
);
}
};
self.onStartup = function () {
ace.config.set("basePath", "plugin/klipper/static/js/lib/ace/");
editor = ace.edit("plugin-klipper-config");
editor.setTheme("ace/theme/monokai");
editor.session.setMode("ace/mode/klipper_config");
editor.clearSelection();
editor.setOptions({
hScrollBarAlwaysVisible: false,
vScrollBarAlwaysVisible: false,
autoScrollEditorIntoView: true,
showPrintMargin: false,
//maxLines: "Infinity"
});
editor.session.on('change', function (delta) {
self.CfgContent(editor.getValue());
editor.resize();
});
};
self.editorFocusDelay = function (delay) {
self.klipperViewModel.sleep(delay).then(
function () {
editor.focus();
}
);
};
self.saveRequest = function (closing) {
self.klipperViewModel.consoleMessage("debug", "SaveCfg start");
OctoPrint.plugins.klipper.saveCfg(editor.session.getValue(), self.CfgFilename())
.done(function (response) {
if (response.saved === true) {
self.klipperViewModel.showPopUp("success", gettext("Save Config"), gettext("File saved."));
self.loadedConfig = editor.session.getValue(); //set loaded config to current for resetting dirtyEditor
if (closing) {
editordialog.modal('hide');
}
if (self.settings.settings.plugins.klipper.configuration.restart_onsave() == true) {
self.klipperViewModel.requestRestart();
}
} else {
showMessageDialog(
gettext('File not saved!'),
{
title: gettext("Save Config"),
onclose: function () { self.editorFocusDelay(1000); }
}
);
}
});
};
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperEditorViewModel,
dependencies: ["settingsViewModel", "klipperViewModel"],
elements: ["#klipper_editor"],
});
});

View File

@ -13,220 +13,320 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
$(function() {
$('#klipper-settings a:first').tab('show');
function KlipperSettingsViewModel(parameters) {
var self = this;
var obKlipperConfig = null;
var editor = null;
$(function () {
$("#klipper-settings a:first").tab("show");
function KlipperSettingsViewModel(parameters) {
var self = this;
self.settings = parameters[0];
self.klipperViewModel = parameters[1];
self.settings = parameters[0];
self.klipperViewModel = parameters[1];
self.klipperEditorViewModel = parameters[2];
self.klipperBackupViewModel = parameters[3];
self.access = parameters[4];
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache"
});
self.header = OctoPrint.getRequestHeaders({
"content-type": "application/json",
"cache-control": "no-cache",
});
self.apiUrl = OctoPrint.getSimpleApiUrl("klipper");
self.markedForFileRemove = ko.observableArray([]);
self.PathToConfigs = ko.observable("");
self.onSettingsBeforeSave = function () {
if (editor.session && self.settings.settings.plugins.klipper.configuration.parse_check() === true) {
self.klipperViewModel.consoleMessage("debug", "onSettingsBeforeSave:")
var settings = {
"crossDomain": true,
"url": self.apiUrl,
"method": "POST",
"headers": self.header,
"processData": false,
"dataType": "json",
"data": JSON.stringify({command: "checkConfig",
config: editor.session.getValue()})
}
$(document).on('shown.bs.modal','#klipper_editor', function () {
self.klipperEditorViewModel.onShown();
});
$.ajax(settings).done(function (response) {
});
}
}
self.addMacro = function() {
self.settings.settings.plugins.klipper.macros.push({
name: 'Macro',
macro: '',
sidebar: true,
tab: true
});
}
self.removeMacro = function(macro) {
self.settings.settings.plugins.klipper.macros.remove(macro);
}
self.moveMacroUp = function(macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.macros, macro)
}
self.moveMacroDown = function(macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.macros, macro)
}
self.addProbePoint = function() {
self.settings.settings.plugins.klipper.probe.points.push(
{
name: 'point-#',
x:0, y:0, z:0
}
);
}
self.removeProbePoint = function(point) {
self.settings.settings.plugins.klipper.probe.points.remove(point);
}
self.moveProbePointUp = function(macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.probe.points, macro)
}
self.moveProbePointDown = function(macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.probe.points, macro)
}
self.moveItemDown = function(list, item) {
var i = list().indexOf(item);
if (i < list().length - 1) {
var rawList = list();
list.splice(i, 2, rawList[i + 1], rawList[i]);
}
}
self.moveItemUp = function(list, item) {
var i = list().indexOf(item);
if (i > 0) {
var rawList = list();
list.splice(i-1, 2, rawList[i], rawList[i-1]);
}
}
self.minusFontsize = function () {
self.settings.settings.plugins.klipper.configuration.fontsize(self.settings.settings.plugins.klipper.configuration.fontsize() - 1);
if (self.settings.settings.plugins.klipper.configuration.fontsize() < 9) {
self.settings.settings.plugins.klipper.configuration.fontsize(9);
}
if (editor) {
editor.setFontSize(self.settings.settings.plugins.klipper.configuration.fontsize());
editor.resize();
}
}
self.plusFontsize = function () {
self.settings.settings.plugins.klipper.configuration.fontsize(self.settings.settings.plugins.klipper.configuration.fontsize() + 1);
if (self.settings.settings.plugins.klipper.configuration.fontsize() > 20) {
self.settings.settings.plugins.klipper.configuration.fontsize(20);
}
if (editor) {
editor.setFontSize(self.settings.settings.plugins.klipper.configuration.fontsize());
editor.resize();
}
}
self.loadLastSession = function () {
if (self.settings.settings.plugins.klipper.configuration.old_config() != "") {
self.klipperViewModel.consoleMessage("info","lastSession:" + self.settings.settings.plugins.klipper.configuration.old_config())
if (editor.session) {
editor.session.setValue(self.settings.settings.plugins.klipper.configuration.old_config());
editor.clearSelection();
}
}
}
self.reloadFromFile = function () {
if (editor.session) {
var settings = {
"crossDomain": true,
"url": self.apiUrl,
"method": "POST",
"headers": self.header,
"processData": false,
"dataType": "json",
"data": JSON.stringify({command: "reloadConfig"})
}
$.ajax(settings).done(function (response) {
editor.session.setValue(response["data"]);
editor.clearSelection();
});
}
}
self.configBound = function (config) {
config.withSilence = function() {
this.notifySubscribers = function() {
if (!this.isSilent) {
ko.subscribable.fn.notifySubscribers.apply(this, arguments);
}
}
this.silentUpdate = function(newValue) {
this.isSilent = true;
this(newValue);
this.isSilent = false;
};
return this;
}
obKlipperConfig = config.withSilence();
if (editor) {
editor.setValue(obKlipperConfig());
editor.setFontSize(self.settings.settings.plugins.klipper.configuration.fontsize());
editor.resize();
editor.clearSelection();
}
return obKlipperConfig;
}
ace.config.set("basePath", "plugin/klipper/static/js/lib/ace/");
editor = ace.edit("plugin-klipper-config");
editor.setTheme("ace/theme/monokai");
editor.session.setMode("ace/mode/klipper_config");
editor.setOptions({
hScrollBarAlwaysVisible: false,
vScrollBarAlwaysVisible: false,
autoScrollEditorIntoView: true,
showPrintMargin: false,
maxLines: "Infinity"
})
editor.session.on('change', function(delta) {
if (obKlipperConfig) {
obKlipperConfig.silentUpdate(editor.getValue());
editor.resize();
}
});
// Uncomment this if not using maxLines: "Infinity"...
// setInterval(function(){ editor.resize(); }, 500);
self.onDataUpdaterPluginMessage = function(plugin, data) {
if(plugin == "klipper") {
if ("reload" == data.type){
if ("config" == data.subtype){
if (editor.session) {
editor.session.setValue(data.payload);
editor.clearSelection();
}
}
return
}
}
}
self.checkFontsize = function () {
if (self.settings.settings.plugins.klipper.configuration.fontsize() > 20) {
self.settings.settings.plugins.klipper.configuration.fontsize(20)
} else if (self.settings.settings.plugins.klipper.configuration.fontsize()< 9) {
self.settings.settings.plugins.klipper.configuration.fontsize(9)
}
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperSettingsViewModel,
dependencies: [
"settingsViewModel",
"klipperViewModel"
],
elements: ["#settings_plugin_klipper"]
});
// initialize list helper
self.configs = new ItemListHelper(
"klipperCfgFiles",
{
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;
},
date: function (a, b) {
// sorts descending
if (a["date"] > b["date"]) return -1;
if (a["date"] < b["date"]) return 1;
return 0;
},
size: function (a, b) {
// sorts descending
if (a["bytes"] > b["bytes"]) return -1;
if (a["bytes"] < b["bytes"]) return 1;
return 0;
},
},
{},
"name",
[],
[],
15
);
self.onStartupComplete = function () {
self.listCfgFiles();
self.loadBaseConfig();
};
self.listCfgFiles = function () {
self.klipperViewModel.consoleMessage("debug", "listCfgFiles started");
OctoPrint.plugins.klipper.listCfg().done(function (response) {
self.klipperViewModel.consoleMessage("debug", "listCfgFiles done");
self.configs.updateItems(response.files);
self.PathToConfigs(gettext("Path: ") + response.path);
self.configs.resetPage();
});
};
self.loadBaseConfig = function () {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
var baseconfig = self.settings.settings.plugins.klipper.configuration.baseconfig();
if (baseconfig != "") {
self.klipperViewModel.consoleMessage("debug", "loadBaseConfig:" + baseconfig);
OctoPrint.plugins.klipper.getCfg(baseconfig).done(function (response) {
var config = {
content: response.response.config,
file: baseconfig,
};
self.klipperEditorViewModel.process(config).then();
});
}
};
self.removeCfg = function (config) {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
var perform = function () {
OctoPrint.plugins.klipper
.deleteCfg(config)
.done(function () {
self.listCfgFiles();
})
.fail(function (response) {
var html = "<p>" + _.sprintf(gettext("Failed to remove config %(name)s.</p><p>Please consult octoprint.log for details.</p>"), { name: _.escape(config) });
html += pnotifyAdditionalInfo('<pre style="overflow: auto">' + _.escape(response.responseText) + "</pre>");
new PNotify({
title: gettext("Could not remove config"),
text: html,
type: "error",
hide: false,
});
});
};
showConfirmationDialog(
_.sprintf(gettext('You are about to delete config file "%(name)s".'), {
name: _.escape(config),
}),
perform
);
};
self.markFilesOnPage = function () {
self.markedForFileRemove(_.uniq(self.markedForFileRemove().concat(_.map(self.configs.paginatedItems(), "file"))));
};
self.markAllFiles = function () {
self.markedForFileRemove(_.map(self.configs.allItems, "file"));
};
self.clearMarkedFiles = function () {
self.markedForFileRemove.removeAll();
};
self.removeMarkedFiles = function () {
var perform = function () {
self._bulkRemove(self.markedForFileRemove()).done(function () {
self.markedForFileRemove.removeAll();
});
};
showConfirmationDialog(
_.sprintf(gettext("You are about to delete %(count)d config files."), {
count: self.markedForFileRemove().length,
}),
perform
);
};
self._bulkRemove = function (files) {
var title, message, handler;
title = gettext("Deleting config files");
message = _.sprintf(gettext("Deleting %(count)d config files..."), {
count: files.length,
});
handler = function (filename) {
return OctoPrint.plugins.klipper
.deleteCfg(filename)
.done(function () {
deferred.notify(
_.sprintf(gettext("Deleted %(filename)s..."), {
filename: _.escape(filename),
}),
true
);
self.markedForFileRemove.remove(function (item) {
return item.name == filename;
});
})
.fail(function () {
deferred.notify(_.sprintf(gettext("Deleting of %(filename)s failed, continuing..."), { filename: _.escape(filename) }), false);
});
};
var deferred = $.Deferred();
var promise = deferred.promise();
var options = {
title: title,
message: message,
max: files.length,
output: true,
};
showProgressModal(options, promise);
var requests = [];
_.each(files, function (filename) {
var request = handler(filename);
requests.push(request);
});
$.when.apply($, _.map(requests, wrapPromiseWithAlways)).done(function () {
deferred.resolve();
self.listCfgFiles();
});
return promise;
};
self.showBackupsDialog = function () {
self.klipperViewModel.consoleMessage("debug", "showBackupsDialog");
self.klipperBackupViewModel.listBakFiles();
var dialog = $("#klipper_backups_dialog");
dialog.modal({
show: "true",
});
};
self.showEditor = function () {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
var editorDialog = $("#klipper_editor");
editorDialog.modal({
show: "true",
width: "90%",
backdrop: "static",
});
}
self.newFile = function () {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
var config = {
content: "",
file: "Change Filename",
};
self.klipperEditorViewModel.process(config).then(
function() { self.showEditor(); }
);
};
self.openConfig = function (file) {
if (!self.klipperViewModel.hasRight("CONFIG")) return;
OctoPrint.plugins.klipper.getCfg(file).done(function (response) {
var config = {
content: response.response.config,
file: file,
};
self.klipperEditorViewModel.process(config).then(
function() { self.showEditor(); }
);
});
};
self.addMacro = function () {
self.settings.settings.plugins.klipper.macros.push({
name: "Macro",
macro: "",
sidebar: true,
tab: true,
});
};
self.removeMacro = function (macro) {
self.settings.settings.plugins.klipper.macros.remove(macro);
};
self.moveMacroUp = function (macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.macros, macro);
};
self.moveMacroDown = function (macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.macros, macro);
};
self.addProbePoint = function () {
self.settings.settings.plugins.klipper.probe.points.push({
name: "point-#",
x: 0,
y: 0,
z: 0,
});
};
self.removeProbePoint = function (point) {
self.settings.settings.plugins.klipper.probe.points.remove(point);
};
self.moveProbePointUp = function (macro) {
self.moveItemUp(self.settings.settings.plugins.klipper.probe.points, macro);
};
self.moveProbePointDown = function (macro) {
self.moveItemDown(self.settings.settings.plugins.klipper.probe.points, macro);
};
self.moveItemDown = function (list, item) {
var i = list().indexOf(item);
if (i < list().length - 1) {
var rawList = list();
list.splice(i, 2, rawList[i + 1], rawList[i]);
}
};
self.moveItemUp = function (list, item) {
var i = list().indexOf(item);
if (i > 0) {
var rawList = list();
list.splice(i - 1, 2, rawList[i], rawList[i - 1]);
}
};
self.onDataUpdaterPluginMessage = function (plugin, data) {
if (plugin == "klipper" && data.type == "reload" && data.subtype == "configlist") {
self.klipperViewModel.consoleMessage("debug", "onDataUpdaterPluginMessage klipper reload configlist");
self.listCfgFiles();
}
};
}
OCTOPRINT_VIEWMODELS.push({
construct: KlipperSettingsViewModel,
dependencies: ["settingsViewModel", "klipperViewModel", "klipperEditorViewModel", "klipperBackupViewModel", "accessViewModel"],
elements: ["#settings_plugin_klipper"],
});
});

View File

@ -160,7 +160,7 @@ ace.define("ace/mode/klipper_config_highlight_rules",[], function(require, expor
caseInsensitive: true
}, {
token: "support.type",
regex: /[\^~!]*(?:z:)?[a-zA-Z]{1,2}\d{1,2}(?:\.\d{1,2})?/,
regex: /[\^~!]*(?:z:)?[a-zA-Z]{1,4}(?:gpio)?\d{1,2}(?:\.\d{1,2})?/,
caseInsensitive: true
}],
"#config_line_start_gcode": [{
@ -192,8 +192,8 @@ ace.define("ace/mode/klipper_config_highlight_rules",[], function(require, expor
}]
}],
"#config_line": [{
token: ["variable.name", "variable.name"],
regex: /(?!gcode|sensor_type|rpi:)(\w+)(\s*[:]\s*)/,
token: ["variable.name", "variable.name", "variable.name", "variable.name"],
regex: /(?!gcode|sensor_type|rpi:)(\w+)(\s*[:]\s*\w+[:])|(?!gcode|sensor_type|rpi:)(\w+)(\s*[:]\s*)/,
push: [{
token: "text",
regex: /$/,

View File

@ -0,0 +1,110 @@
<div id="klipper_backups_dialog" class="modal hide fade large" tabindex="-1" role="dialog" aria-labelledby="klipper_backups_dialog_label"
aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_dialog_label">{{ _('Backups') }}</h3>
</div>
<div class="modal-body">
<div class="editor-controls">
<div class="klipper-btn-group klipper-fluid-item-1" data-bind="visible: $root.klipperViewModel.hasRightKo('CONFIG')">
<div class="btn-group">
<button class="btn btn-small dropdown-toggle" data-toggle="dropdown">
<i class="far fa-square"></i>
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<a href="javascript:void(0)" data-bind="click: markFilesOnPage">{{ _('Select all on this page') }}</a>
</li>
<li>
<a href="javascript:void(0)" data-bind="click: markAllFiles">{{ _('Select all') }}</a>
</li>
<li class="divider">
</li>
<li>
<a href="javascript:void(0)" data-bind="click: clearMarkedFiles">{{ _('Clear selection') }}</a>
</li>
</ul>
</div>
<button class="btn btn-small" data-bind="click: listBakFiles" title="{{ _('Refresh file list') }}">
<i class="icon-refresh"></i> {{ _('Refresh Files') }}
</button>
<button class="btn btn-small"
data-bind="click: restoreMarkedFiles, enable: markedForFileRestore().length > 0">{{ _('Restore selected') }}</button>
<button class="btn btn-small"
data-bind="click: removeMarkedFiles, enable: markedForFileRestore().length > 0">{{ _('Delete selected') }}</button>
</div>
<div class="pull-right">
<div class="btn-group">
<button class="btn btn-small dropdown-toggle" data-toggle="dropdown">
<i class="fas fa-wrench"></i>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="javascript:void(0)" data-bind="click: function() { backups.changeSorting('name'); }"><i class="fas fa-check"
data-bind="style: {visibility: backups.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> {{ _('Sort by name') }}
({{ _('ascending') }})</a>
</li>
<li><a href="javascript:void(0)" data-bind="click: function() { backups.changeSorting('date'); }"><i class="fas fa-check"
data-bind="style: {visibility: backups.currentSorting() == 'date' ? 'visible' : 'hidden'}"></i> {{ _('Sort by date') }}
({{ _('descending') }})</a>
</li>
<li><a href="javascript:void(0)" data-bind="click: function() { backups.changeSorting('size'); }"><i class="fas fa-check"
data-bind="style: {visibility: backups.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> {{ _('Sort by file size') }}
({{ _('descending') }})</a>
</li>
</ul>
</div>
</div>
</div>
<table class="table table-striped table-hover table-condensed table-hover" id="klipper_bak_files">
<thead>
<tr>
<th class="klipper_baks_checkbox"></th>
<th class="klipper_baks_name">{{ _('Name') }}</th>
<th class="klipper_baks_size">{{ _('Size') }}</th>
<th class="klipper_baks_action">{{ _('Action') }}</th>
</tr>
</thead>
<tbody data-bind="foreach: backups.paginatedItems">
<tr data-bind="attr: {title: name}">
<td class="klipper_baks_checkbox"><input type="checkbox"
data-bind="value: file, checked: $root.markedForFileRestore, invisible: !$root.klipperViewModel.hasRightKo('CONFIG')" />
</td>
<td class="klipper_baks_name" data-bind="text: name"></td>
<td class="klipper_baks_size" data-bind="text: size"></td>
<td class="klipper_baks_action">
<a href="javascript:void(0)" class="far fa-trash-alt" title="{{ _('Delete') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.removeCfg($data.name); }"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-undo" title="{{ _('Restore') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.restoreBak($data.name); }"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-download" title="{{ _('Download') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, attr: { href: ($root.klipperViewModel.hasRightKo('CONFIG')()) ? $data.url : 'javascript:void(0)'}"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-eye" title="{{ _('Preview') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.showCfg($data.name); }"></a>
</td>
</tr>
</tbody>
</table>
<div class="pagination pagination-mini pagination-centered">
<ul>
<li data-bind="css: {disabled: backups.currentPage() === 0}"><a href="javascript:void(0)" data-bind="click: backups.prevPage">«</a></li>
</ul>
<ul data-bind="foreach: backups.pages">
<li data-bind="css: { active: $data.number === $root.backups.currentPage(), disabled: $data.number === -1 }"><a href="javascript:void(0)"
data-bind="text: $data.text, click: function() { $root.backups.changePage($data.number); }"></a></li>
</ul>
<ul>
<li data-bind="css: {disabled: backups.currentPage() === backups.lastPage()}"><a href="javascript:void(0)"
data-bind="click: backups.nextPage">»</a></li>
</ul>
</div>
<div class="textarea">
<textarea rows="21" readonly data-bind="value: CfgContent" id="klipper_bak_text"></textarea>
</div>
</div>
</div>

View File

@ -0,0 +1,46 @@
<div id="klipper_editor" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="klipper_editor_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-bind="click: closeEditor">&times;</button>
<h3 id="klipper_dialog_label">Editor
<div class="pull-right">
<button data-bind="click: minusFontsize" title="{{ _('Decrease Fontsize') }}" class="btn">
<i class="fas fa-search-minus"></i>
</button>
<button data-bind="click: plusFontsize" title="{{ _('Increase Fontsize') }}" class="btn">
<i class="fas fa-search-plus"></i>
</button>
</div>
</h3>
</div>
<div class="modal-body">
<script src="plugin/klipper/static/js/lib/ace/ace.min.js" type="text/javascript" charset="utf-8"></script>
<script src="plugin/klipper/static/js/lib/ace/theme-monokai.min.js" type="text/javascript" charset="utf-8"></script>
<script src="plugin/klipper/static/js/lib/ace/mode-klipper_config.js" type="text/javascript"></script>
<div class="full-sized-box">
<div class="conf-editor" id="conf_editor">
<input id="hdnLoadKlipperConfig" type="hidden" data-bind="value: CfgContent">
<div id="plugin-klipper-config"></div>
</div>
</div>
</div>
<div class="modal-footer">
<div class="editor-controls">
<span class="control-label">{{ _('Filename') }}:</span>
<input type="text" data-bind="value: CfgFilename">
<div class="klipper-btn-group klipper-fluid-item-2">
<button class="btn btn-small" data-bind="click: reloadFromFile" title="{{ _('Reload from file') }}">
<i class="fas fa-upload"></i> {{ _('Reload from file') }}
</button>
<button class="btn btn-small" data-bind="click: checkSyntax" title="{{ _('Check Syntax') }}">
<i class="fas fa-spell-check"></i> {{ _('Check Syntax') }}
</button>
<button class="btn btn-small" data-bind="click: function() { $root.saveCfg({closing: true}) }" title="{{ _('Save Config and Close') }}">
<i class="fas fa-save"></i> {{ _('Save & Close') }}
</button>
<button class="btn btn-small" data-bind="click: saveCfg" title="{{ _('Save Config') }}">
<i class="fas fa-save"></i> {{ _('Save') }}
</button>
</div>
</div>
</div>
</div>

View File

@ -1,12 +1,12 @@
<div id="klipper_graph_dialog" class="modal hide fade large" tabindex="-1" role="dialog" aria-labelledby="klipper_graph_dialog_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_pid_tuning_dialog_label">{{ _('Performance Graph') }}</h3>
<h3 id="klipper_dialog_label">{{ _('Performance Graph') }}</h3>
</div>
<div class="modal-body">
<div class="full-sized-box">
<span class="help-inline" style="display:block; position: absolute">
<em>Click labels to hide/show dataset</em>
<em>{{ _('Click labels to hide/show dataset') }}</em>
</span>
<script src="plugin/klipper/static/js/lib/Chart.bundle.min.js" type="text/javascript" defer></script>
<canvas id="klipper_graph_canvas"></canvas>
@ -32,6 +32,6 @@
<div id="klipper_graph_spinner" class="modal hide fade small" tabindex="-1" role="dialog" aria-hidden="true">
<img src="plugin/klipper/static/img/spinner.gif" width="100" height="100">
<span class="help-inline">
Depending on the size of the log file this might take a while.
{{ _('Depending on the size of the log file this might take a while.') }}
</span>
</div>

View File

@ -1,3 +1,3 @@
<!-- ko if: settings.settings.plugins.klipper.configuration.shortStatus_navbar -->
<a title="Go to OctoKlipper Tab" data-bind="text: shortStatus_navbar, click: navbarClicked"></a>
<a data-bind="attr: { title: shortStatus_navbar_hover }, text: shortStatus_navbar, click: navbarClicked"></a>
<!-- /ko -->

View File

@ -1,7 +1,7 @@
<div id="klipper_offset_dialog" class="modal hide fade small" tabindex="-1" role="dialog" aria-labelledby="klipper_offset_dialog_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_pid_tuning_dialog_label">{{ _('Coordinate Offset') }}</h3>
<h3 id="klipper_dialog_label">{{ _('Coordinate Offset') }}</h3>
</div>
<div class="modal-body">
<div class="row-fluid" style="margin-bottom: 15px">
@ -10,15 +10,15 @@
<div class="row-fluid" style="margin-bottom: 15px">
<div class="input-append span3">
<input type="text" class="input-block-level span6" data-bind="value: offsetX">
<span class="add-on">{{ _('X') }}</span>
<span class="add-on">X</span>
</div>
<div class="input-append span3">
<input type="text" class="input-block-level span6" data-bind="value: offsetY">
<span class="add-on">{{ _('Y') }}</span>
<span class="add-on">Y</span>
</div>
<div class="input-append span3">
<input type="text" class="input-block-level span6" data-bind="value: offsetZ">
<span class="add-on">{{ _('Z') }}</span>
<span class="add-on">Z</span>
</div>
</div>
<div class="row-fluid" style="margin-bottom: 15px">

View File

@ -1,7 +1,7 @@
<div id="klipper_macro_dialog" class="modal hide fade small" tabindex="-1" role="dialog" aria-labelledby="klipper_macro_dialog_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_pid_tuning_dialog_label">{{ _('Run - ') }}<span data-bind="text: macroName"></span></h3>
<h3 id="klipper_dialog_label">{{ _('Run - ') }}<span data-bind="text: macroName"></span></h3>
</div>
<div class="modal-body">
<div class="control-group" data-bind="foreach: parameters">

View File

@ -1,7 +1,7 @@
<div id="klipper_pid_tuning_dialog" class="modal hide fade small" tabindex="-1" role="dialog" aria-labelledby="klipper_pid_tuning_dialog_label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 id="klipper_pid_tuning_dialog_label">{{ _('PID Tuning') }}</h3>
<h3 id="klipper_dialog_label">{{ _('PID Tuning') }}</h3>
</div>
<div class="modal-body">
<div class="control-group">

View File

@ -1,231 +1,347 @@
<form class="form-horizontal">
<ul class="nav nav-pills" id="klipper-settings">
<li><a href="#basic" data-toggle="tab" data-profile-type="klipper-basic">Basic</a></li>
<li><a href="#macros" data-toggle="tab" data-profile-type="klipper-macros">Macros</a></li>
<li><a href="#level" data-toggle="tab" data-profile-type="klipper-bed">Bed Leveling</a></li>
<li><a href="#conf" data-toggle="tab" data-profile-type="klipper-config">Klipper Configuration</a></li>
</ul>
<div class="tab-content">
<!-- Basics -->
<div class="tab-pane active" id="basic">
<div class="control-group">
<ul class="nav nav-pills" id="klipper-settings">
<li><a href="#basic" data-toggle="tab" data-profile-type="klipper-basic">{{ _('Basic') }}</a></li>
<li><a href="#macros" data-toggle="tab" data-profile-type="klipper-macros">{{ _('Macros') }}</a></li>
<li><a href="#level" data-toggle="tab" data-profile-type="klipper-bed">{{ _('Bed Leveling') }}</a></li>
<li><a href="#conf" data-toggle="tab" data-profile-type="klipper-config">{{ _('Klipper Configuration') }}</a></li>
</ul>
<div class="tab-content">
<!-- Basics -->
<div class="tab-pane active" id="basic">
<div class="control-group">
<label class="control-label">{{ _('Serial Port') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.connection.port">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.connection.port" />
</div>
</div>
<div class="control-group">
</div>
<div class="control-group">
<label class="control-label">{{ _('Replace Connection Panel') }}</label>
<div class="controls">
<input class="controls-checkbox" title="{{ _('Replace Connection Panel') }}" type="checkbox" data-bind="checked: settings.settings.plugins.klipper.connection.replace_connection_panel">
<input class="controls-checkbox" title="{{ _('Replace Connection Panel') }}" type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.connection.replace_connection_panel">
</div>
</div>
<div class="control-group">
</div>
<div class="control-group border">
<label class="control-label">{{ _('Show Short Messages') }}</label>
<div class="controls">
<label class="checkbox" title="{{ _('on NavBar') }}"><input type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_navbar"> {{ _('on NavBar') }}</label>
<label class="checkbox" title="{{ _('on SideBar') }}"><input type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_sidebar"> {{ _('on SideBar') }}</label>
<label class="checkbox" title="{{ _('on NavBar') }}"><input type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_navbar" /> {{ _('on NavBar') }}</label>
<label class="checkbox" title="{{ _('on SideBar') }}"><input type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.shortStatus_sidebar" /> {{ _('on SideBar') }}</label>
</div>
</div>
<div class="control-group">
</div>
<div class="control-group">
<label class="control-label">{{ _('Enable debug logging') }}</label>
<div class="controls">
<input class="controls-checkbox" title="{{ _('Enable debug logging') }}" type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.debug_logging">
<input class="controls-checkbox" title="{{ _('Enable debug logging') }}" type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.debug_logging" />
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Klipper Config File') }}</label>
</div>
<div class="control-group border">
<label class="control-label">{{ _('Config Editor') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.configpath">
<label class="checkbox" title="{{ _('Check parsing on save') }}"><input type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.parse_check" /> {{ _('Check parsing on save') }}
</label>
<label title="{{ _('Fontsize') }}"><input type="text"
class="input-block-level span1" data-bind="value: settings.settings.plugins.klipper.configuration.fontsize,
event: { change: checkFontsize}" /> {{ _('Fontsize') }}
</label>
</div>
</div>
<div class="control-group">
</div>
<div class="control-group">
<label class="control-label">{{ _('Klipper Config Directory') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.config_path" />
</div>
</div>
<div class="control-group">
<label class="control-label" title="{{ _('The filename of the base config that Klipper is loading. (default is printer.cfg)') }}">{{ _('Klipper Base Config Filename') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.baseconfig" />
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Klipper Log File') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.logpath">
<input type="text" class="input-block-level" data-bind="value: settings.settings.plugins.klipper.configuration.logpath" />
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Configuration Reload Command') }}</label>
</div>
<div class="control-group border">
<label class="control-label">{{ _('Configuration Restart Command') }}</label>
<div class="controls">
<select data-bind="value: settings.settings.plugins.klipper.configuration.reload_command">
<option value="RESTART">RESTART</option>
<option value="FIRMWARE_RESTART">FIRMWARE_RESTART</option>
<option value="manually">Manually</option>
</select>
<span class="help-block">
The command that is executed when the Klipper configuration changed and needs to be reloaded.<br>
Set this to "Manually" if you don't want to immediately restart klipper.
</span>
<select data-bind="value: settings.settings.plugins.klipper.configuration.reload_command">
<option value="RESTART">RESTART</option>
<option value="FIRMWARE_RESTART">FIRMWARE_RESTART</option>
</select>
<span class="help-block" style="margin-top:5px;">
{{ _('The command that is executed if you want to restart klipper.') }}
</span>
<label class="checkbox" title="{{ _('Restart klipper on editor save?') }}"><input type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.restart_onsave" /> {{ _('Restart Klipper on editor save?') }}
</label>
<label class="checkbox" title="{{ _('Show Confirmation before restarting Klipper?') }}"><input type="checkbox"
data-bind="checked: settings.settings.plugins.klipper.configuration.confirm_reload" /> {{ _('Confirmation before restarting Klipper?') }}
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Config Backup') }}</label>
<div class="controls">
<button class="btn btn-small" data-bind='click: showBackupsDialog' title="{{ _('Show Backups') }}">
<i class="fas fa-upload"></i> {{ _('Show Backups') }}
</button>
</div>
</div>
</div>
</div>
<!-- Macros -->
<div class="tab-pane" id="macros">
<div class="control-group" style="margin-bottom: 0px;">
<div class="controls" style="margin-left: 82px;">
<!-- Macros -->
<div class="tab-pane" id="macros">
<div class="control-group">
<span class="help-block">
{{ _('These macros are only meant to be used in OctoPrint.') }}
{{ _('They are not the ones that can be defined in the printer.cfg.') }}<br />
</span>
</div>
<div class="control-group" style="margin-bottom: 0px;">
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span8" style="text-align: right"><small>{{ _('Add macro button to:') }}</small></div>
<div class="span1" style="margin: auto;text-align: center"><small>{{ _('Klipper Tab') }}</small></div>
<div class="span2" style="margin: auto;"><small>{{ _('Sidebar') }}</small></div>
</div>
</div>
</div>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
<div class="control-group" id="item">
<label class="control-label">{{ _('Name') }}</label>
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span8">
<input type="text" class="input-block-level" data-bind="value: name"/>
</div>
<div class="span1" style="margin: auto; text-align: center;">
<input title="{{ _('Klipper Tab') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: tab"/>
</div>
<div class="span1" style="margin: auto; text-align: center;">
<input title="{{ _('Sidebar') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: sidebar"/>
</div>
<div class="span2" style="margin: auto; text-align: center;">
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroUp' class="fa fa-chevron-up"></a>
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroDown' class="fa fa-chevron-down"></a>
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.removeMacro' class="fa fa-trash-o"></a>
</div>
</div>
</div>
<label class="control-label">{{ _('Command') }}</label>
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span12" style="margin-top:2px;">
<textarea rows="2" class="block" data-bind="value: macro">
</textarea>
</div>
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind='click: addMacro' title="{{ _('Add Macro') }}" class="fa fa-plus-circle"></a> {{ _('Add Macro') }}
</div>
</div>
<div class="control-group">
<span class="help-block">
To show a dialog that asks for parameters you can write your macro like in the following example:<br>
</span>
</div>
<div class="control-group">
<pre>
PID_CALIBRATE
HEATER={label:Heater, default:extruder, options:extruder|extruder1}
TARGET={label:Target Temperature, unit:°C, default:190}
WRITE_FILE={label:Write to File, default:0, options:0|1}
</pre>
</div>
</div>
<!-- Leveling -->
<div class="tab-pane" id="level">
<div class="control-group">
<span class="help-block">
This feature assists in manually leveling you print bed by moving the head to the defined points in sequence.<br>
If you use a piece of paper for leveling, set "Probe Height" to the paper thickness eg. "0.1".
</span>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Height') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.height">
<span class="add-on">mm</span>
</div>
<span class="help-inline">{{ _('Z-height to probe at') }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Lift') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.lift">
<span class="add-on">mm</span>
</div>
<span class="help-inline">{{ _('Lift Head by this amount before moving.') }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Feedrate Z') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.speed_z">
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Feedrate X/Y') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.speed_xy">
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<h5>{{ _('Probe Points') }}</h5>
<div class="controls">
<div class="row-fluid">
<div class="span3">name</div>
<div class="span3">x(mm)</div>
<div class="span3">y(mm)</div>
<div class="span3"> </div>
</div>
</div>
</div>
<div data-bind="foreach: settings.settings.plugins.klipper.probe.points" class="control-group">
<label class="control-label" data-bind="text: $index"></label>
<div class="controls">
<div class="row-fluid">
<div class="span3"><input type="text" class="input-block-level" data-bind="value: name"></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: x"></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: y"></div>
<div class="span3">
<a href='#' data-bind='click: $parent.moveProbePointUp' class="fa fa-chevron-up"></a>
<a href='#' data-bind='click: $parent.moveProbePointDown' class="fa fa-chevron-down"></a>
<a href='#' data-bind='click: $parent.removeProbePoint' class="fa fa-trash-o"></a>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
<div class="control-group" id="item">
<label class="control-label">{{ _('Name') }}</label>
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span8">
<input type="text" class="input-block-level" data-bind="value: name" />
</div>
<div class="span1" style="margin: auto; text-align: center;">
<input title="{{ _('Klipper Tab') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: tab" />
</div>
<div class="span1" style="margin: auto; text-align: center;">
<input title="{{ _('Sidebar') }}" style="margin: auto;" type="checkbox" class="input-block-level" data-bind="checked: sidebar" />
</div>
<div class="span2" style="margin: auto; text-align: center;">
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroUp' class="fa fa-chevron-up"></a>
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.moveMacroDown' class="fa fa-chevron-down"></a>
<a href='#' style="vertical-align: bottom;" data-bind='click: $parent.removeMacro' class="fa fa-trash-o"></a>
</div>
</div>
</div>
<label class="control-label">{{ _('Command') }}</label>
<div class="controls" style="margin-left: 82px;">
<div class="row-fluid">
<div class="span12" style="margin-top:2px;">
<textarea rows="2" class="block" data-bind="value: macro">
</textarea>
</div>
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind='click: addMacro' title="{{ _('Add Macro') }}" class="fa fa-plus-circle"></a> {{ _('Add Macro') }}
</div>
</div>
<div class="control-group">
<span class="help-block">
{{ _('To show a dialog that asks for parameters you can write your macro like in the following example:') }}<br />
</span>
</div>
<div class="control-group">
<pre>
PID_CALIBRATE
HEATER={label:Heater, default:extruder, options:extruder|extruder1}
TARGET={label:Target Temperature, unit:°C, default:190}
WRITE_FILE={label:Write to File, default:0, options:0|1}
</pre>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind="click: addProbePoint" title="{{ _('Add Point') }}" class="fa fa-plus-circle"></a> {{ _('Add Point') }}
<!-- Leveling -->
<div class="tab-pane" id="level">
<div class="control-group">
<span class="help-block">
{{ _('This feature assists in manually leveling your print bed by moving the head to the defined points in sequence.<br />If you use a piece of paper for leveling, set "Probe Height" to the paper thickness eg. "0.1".') }}
</span>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Height') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.height" />
<span class="add-on">mm</span>
</div>
<span class="help-inline">{{ _('Z-height to probe at') }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Lift') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.lift" />
<span class="add-on">mm</span>
</div>
<span class="help-inline">{{ _('Lift Head by this amount before moving.') }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Probe Feedrate Z') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.speed_z" />
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Feedrate X/Y') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" class="input-block-level span3" data-bind="value: settings.settings.plugins.klipper.probe.speed_xy" />
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<h5>{{ _('Probe Points') }}</h5>
<div class="controls">
<div class="row-fluid">
<div class="span3">name</div>
<div class="span3">x(mm)</div>
<div class="span3">y(mm)</div>
<div class="span3"> </div>
</div>
</div>
</div>
<div data-bind="foreach: settings.settings.plugins.klipper.probe.points" class="control-group">
<label class="control-label" data-bind="text: $index"></label>
<div class="controls">
<div class="row-fluid">
<div class="span3"><input type="text" class="input-block-level" data-bind="value: name" /></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: x" /></div>
<div class="span3"><input type="text" class="input-block-level" data-bind="value: y" /></div>
<div class="span3">
<a href='#' data-bind='click: $parent.moveProbePointUp' class="fa fa-chevron-up"></a>
<a href='#' data-bind='click: $parent.moveProbePointDown' class="fa fa-chevron-down"></a>
<a href='#' data-bind='click: $parent.removeProbePoint' class="fa fa-trash-o"></a>
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<a href='#' data-bind="click: addProbePoint" title="{{ _('Add Point') }}" class="fa fa-plus-circle"></a> {{_ ('Add Point') }}
</div>
</div>
</div>
<!-- Klipper Conf -->
<div class="tab-pane" id="conf">
<div class="klipper-column-fluid klipper-settings-tab">
<h3 class="text-center m-0">{{ _('Config Files') }}</h3>
<div class="klipper-row-fluid">
<div class="klipper-fluid-item-2" data-bind="visible: $root.klipperViewModel.hasRightKo('CONFIG')">
<div class="btn-group">
<button class="btn btn-small dropdown-toggle" data-toggle="dropdown"><i class="far fa-square"></i>
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="javascript:void(0)" data-bind="click: markFilesOnPage">{{ _('Select all on this page') }}</a></li>
<li><a href="javascript:void(0)" data-bind="click: markAllFiles">{{ _('Select all') }}</a></li>
<li class="divider"></li>
<li><a href="javascript:void(0)" data-bind="click: clearMarkedFiles">{{ _('Clear selection') }}</a></li>
</ul>
</div>
<button class="btn btn-small" data-bind="click: newFile" title="{{ _('Add new File') }}">
<i class="far fa-file"></i> {{ _('New File') }}
</button>
<button class="btn btn-small" data-bind="click: showEditor" title="{{ _('Open last config') }}">
<i class="fas fa-file-code"></i> {{ _('Open last Editor') }}
</button>
<button class="btn btn-small" data-bind="click: listCfgFiles" title="{{ _('Refresh file list') }}">
<i class="icon-refresh"></i> {{ _('Refresh Files') }}
</button>
<button class="btn btn-small"
data-bind="click: removeMarkedFiles, enable: markedForFileRemove().length > 0">{{ _('Delete selected') }}
</button>
</div>
<div class="pull-right">
<div class="btn-group">
<button class="btn btn-small dropdown-toggle" data-toggle="dropdown"><i class="fas fa-wrench"></i> <span class="caret"></span></button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="javascript:void(0)" data-bind="click: function() { configs.changeSorting('name'); }"><i class="fas fa-check"
data-bind="style: {visibility: configs.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> {{ _('Sort by name') }}
({{ _('ascending') }})</a></li>
<li><a href="javascript:void(0)" data-bind="click: function() { configs.changeSorting('date'); }"><i class="fas fa-check"
data-bind="style: {visibility: configs.currentSorting() == 'date' ? 'visible' : 'hidden'}"></i> {{ _('Sort by date') }}
({{ _('descending') }})</a></li>
<li><a href="javascript:void(0)" data-bind="click: function() { configs.changeSorting('size'); }"><i class="fas fa-check"
data-bind="style: {visibility: configs.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> {{ _('Sort by file size') }}
({{ _('descending') }})</a></li>
</ul>
</div>
</div>
</div>
<p class="klipper-inline" data-bind="text: PathToConfigs" title="{{ _('Path to the config files.')}}"></p>
<div class="scroll-y">
<table class="table table-striped table-hover table-condensed table-hover table-fixed" id="klipper_cfg_files">
<thead>
<tr>
<th class="klipper_cfgs_checkbox span1"></th>
<th class="klipper_cfgs_name">{{ _('Name') }}</th>
<th class="klipper_cfgs_size">{{ _('Size') }}</th>
<th class="klipper_cfgs_action">{{ _('Action') }}</th>
</tr>
</thead>
<tbody data-bind="foreach: configs.paginatedItems">
<tr data-bind="attr: {title: name}">
<td class="klipper_cfgs_checkbox">
<input type="checkbox"
data-bind="value: name, checked: $root.markedForFileRemove, invisible: !$root.klipperViewModel.hasRightKo('CONFIG')">
</td>
<td class="klipper_cfgs_name" data-bind="text: name"></td>
<td class="klipper_cfgs_size" data-bind="text: size"></td>
<td class="klipper_cfgs_action">
<a href="javascript:void(0)" class="far fa-trash-alt" title="{{ _('Delete') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.removeCfg($data.name);}"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-download" title="{{ _('Download') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, attr: { href: ($root.klipperViewModel.hasRightKo('CONFIG')()) ? $data.url : 'javascript:void(0)'}"></a>
&nbsp;|&nbsp;
<a href="javascript:void(0)" class="fas fa-pencil-alt" title="{{ _('Edit') }}"
data-bind="css: {disabled: !$root.klipperViewModel.hasRightKo('CONFIG')()}, click: function() { $parent.openConfig($data.name);}"></a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination pagination-mini pagination-centered">
<ul>
<li data-bind="css: {disabled: configs.currentPage() === 0}"><a href="javascript:void(0)" data-bind="click: configs.prevPage">«</a></li>
</ul>
<ul data-bind="foreach: configs.pages">
<li data-bind="css: {active: $data.number === $root.configs.currentPage(), disabled: $data.number === -1 }">
<a href="javascript:void(0)" data-bind="text: $data.text, click: function() { $root.configs.changePage($data.number); }"></a>
</li>
</ul>
<ul>
<li data-bind="css: {disabled: configs.currentPage() === configs.lastPage()}">
<a href="javascript:void(0)"
data-bind="click: configs.nextPage">»
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Klipper Conf -->
<div class="tab-pane" id="conf">
<div class="control-group">
<script src="plugin/klipper/static/js/lib/ace/ace.min.js" type="text/javascript" charset="utf-8"></script>
<script src="plugin/klipper/static/js/lib/ace/theme-monokai.min.js" type="text/javascript" charset="utf-8"></script>
<script src="plugin/klipper/static/js/lib/ace/mode-klipper_config.js" type="text/javascript"></script>
<div class="editor-controls">
<button class="btn btn-small" data-bind="click: loadLastSession"
title="{{ _('Reload last changes') }}">
<i class="fas fa-redo"></i> {{ _('Reload last changes') }}
</button>
<button class="btn btn-small" data-bind='click: reloadFromFile'
title="{{ _('Reload from file') }}">
<i class="fas fa-upload"></i> {{ _('Reload from file') }}
</button>
<label class="inline"><input class="inline-checkbox" type="checkbox" data-bind="checked: settings.settings.plugins.klipper.configuration.parse_check"> {{ _('Check parsing on save') }}</label>
&nbsp;&nbsp;
<a href='#' data-bind="click: minusFontsize" title="{{ _('Decrease Fontsize') }}" class="fas fa-search-minus"></a>
<a href='#' data-bind="click: plusFontsize" title="{{ _('Increase Fontsize') }}" class="fas fa-search-plus"></a>
</div>
<div class="conf-editor">
<input id="hdnLoadKlipperConfig" type="hidden" data-bind="value: configBound(settings.settings.plugins.klipper.config)" />
<div id="plugin-klipper-config"></div>
</div>
</div>
<div class="tab-footer">
<a href="https://www.paypal.com/donate/?business=7P63W664NF8LA&item_name=OctoKlipper" class="btn btn-mini" target="_blank">
<i class="fab fa-paypal"> {{ _('Donate') }}</i>
</a>
</div>
</div>
</form>

View File

@ -3,15 +3,17 @@
<label for="connection_printers" data-bind="css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()">{{ _('Printer Profile') }}</label>
<select id="connection_printers" data-bind="options: connectionState.printerOptions, optionsText: 'name', optionsValue: 'id', value: connectionState.selectedPrinter, css: {disabled: !connectionState.isErrorOrClosed()}, enable: connectionState.isErrorOrClosed() && loginState.isUser()"></select>
<button class="btn btn-block" data-bind="click: connectionState.connect, text: connectionState.buttonText(), enable: loginState.isUser()">{{ _('Connect') }}</button>
<button class="btn btn-block" data-bind="visible: hasRight('CONFIG', 'Ko'), click: function() {openOctoKlipperSettings('klipper-config');}">{{ _('Open Klipper config') }}</button>
<button class="btn btn-block" title="{{ _('Open Editor') }}" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: showEditorDialog">{{ _('Open Editor') }}</button>
</div>
</div>
<!-- ko if: settings.settings.plugins.klipper.configuration.shortStatus_sidebar -->
<div class="plugin-klipper-sidebar">
<a title="Go to OctoKlipper Tab" data-bind="text: shortStatus_sidebar, click: navbarClicked"></a>
<div id="shortStatus_SideBar" class="plugin-klipper-sidebar">
<a title="{{ _('Go to OctoKlipper Tab') }}" data-bind="click: navbarClicked">
<div data-bind="html: shortStatus_sidebar" class="msg"></div>
</a>
</div>
<!-- /ko -->
<div class="control-group" data-bind="visible: hasRight('MACRO', 'Ko')">
<div class="control-group" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_MACRO)">
<div class="controls">
<label class="control-label small"><i class="icon-list-alt"></i> {{ _('Macros') }}</label>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">

View File

@ -1,6 +1,11 @@
<div class="row-fluid">
<div id="left-side">
<label> <i class="icon-tasks"></i> {{ _("Messages") }} </label>
<label class="klipper-inline"> <i class="icon-tasks"></i> {{ _("Messages") }} </label>
<button class="btn btn-small pull-right"
data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: openOctoKlipperSettings('klipper-basic')"
title="{{ _('Open the OctoKlipper Settings') }}">
<i class="fa icon-black fa-wrench"></i>
</button>
<div class="plugin-klipper-log" data-bind="foreach: logMessages">
<div class="log-item" data-bind="css: type">
<div data-bind="text: time" class="ts"></div>
@ -8,12 +13,8 @@
</div>
</div>
&nbsp;
<button
class="btn btn-mini pull-right clear-btn"
data-bind="click: onClearLog"
title="Clear Log"
>
<i class="fa fa-trash"></i> {{ _("Clear") }}
<button class="btn btn-mini pull-right clear-btn" data-bind="click: onClearLog" title="{{ _('Clear Log') }}">
<i class="fa fa-trash"></i> {{ _('Clear Log') }}
</button>
</div>
<div id="right-side">
@ -21,89 +22,58 @@
<div class="control-group">
<div class="controls">
<label class="control-label"></label>
<button
class="btn btn-block btn-small"
data-bind="click: onGetStatus, enable: isActive()"
title="Query Klipper for its current status"
>
<button class="btn btn-block btn-small" data-bind="click: onGetStatus, enable: isActive()"
title="{{ _('Query Klipper for its current status') }}">
<i class="fa icon-black fa-info-circle"></i> {{ _("Get Status") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="visible: hasRight('CONFIG', 'Ko'), click: function() {openOctoKlipperSettings('klipper-config');}"
title="Open the Klipper configurationfile"
>
<button class="btn btn-block btn-small"
data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_CONFIG), click: showEditorDialog"
title="{{ _('Show the Editor') }}">
<i class="fa icon-black fa-file-code-o"></i>
{{ _("Open Klipper config") }}
{{ _("Show Editor") }}
</button>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="control-label small"
><i class="icon-refresh"></i> {{ _("Restart") }}</label
>
<button
class="btn btn-block btn-small"
data-bind="click: onRestartHost, enable: isActive()"
title="This will cause the host software to reload its config and perform an internal reset"
>
<label class="control-label small"><i class="icon-refresh"></i> {{ _("Restart") }}</label>
<button class="btn btn-block btn-small" data-bind="click: onRestartHost, enable: isActive()"
title="{{ _('This will cause the host software to reload its config and perform an internal reset') }}">
{{ _("Host") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="click: onRestartFirmware, enable: isActive()"
title="Similar to a host restart, but also clears any error state from the micro-controller"
>
<button class="btn btn-block btn-small" data-bind="click: onRestartFirmware, enable: isActive()"
title="{{ _('Similar to a host restart, but also clears any error state from the micro-controller') }}">
{{ _("Firmware") }}
</button>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="control-label"
><i class="icon-wrench"></i> {{ _("Tools") }}</label
>
<button
class="btn btn-block btn-small"
data-bind="click: showLevelingDialog, enable: isActive()"
title="Assists in manually leveling your printbed by moving the head to a configurable set of positions in sequence."
>
<label class="control-label"><i class="icon-wrench"></i> {{ _("Tools") }}</label>
<button class="btn btn-block btn-small" data-bind="click: showLevelingDialog, enable: isActive()"
title="{{ _('Assists in manually leveling your printbed by moving the head to a configurable set of positions in sequence.') }}">
{{ _("Assisted Bed Leveling") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="click: showPidTuningDialog, enable: isActive()"
title="Determines optimal PID parameters by heat cycling the hotend/bed."
>
<button class="btn btn-block btn-small" data-bind="click: showPidTuningDialog, enable: isActive()"
title="{{ _('Determines optimal PID parameters by heat cycling the hotend/bed.') }}">
{{ _("PID Tuning") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="click: showOffsetDialog, enable: isActive()"
title="Sets a offset for subsequent GCODE coordinates."
>
<button class="btn btn-block btn-small" data-bind="click: showOffsetDialog, enable: isActive()"
title="{{ _('Sets a offset for subsequent GCODE coordinates.') }}">
{{ _("Coordinate Offset") }}
</button>
<button
class="btn btn-block btn-small"
data-bind="click: showGraphDialog"
title="Assists in debugging performance issues by analyzing the Klipper log files."
>
<button class="btn btn-block btn-small" data-bind="click: showGraphDialog"
title="{{ _('Assists in debugging performance issues by analyzing the Klipper log files.') }}">
{{ _("Analyze Klipper Log") }}
</button>
</div>
</div>
<div class="controls" data-bind="visible: hasRight('MACRO', 'Ko')">
<label class="control-label"
><i class="icon-list-alt"></i> {{ _("Macros") }}</label
>
<div class="controls" data-bind="visible: $root.loginState.hasPermissionKo($root.access.permissions.PLUGIN_KLIPPER_MACRO)">
<label class="control-label"><i class="icon-list-alt"></i> {{ _("Macros") }}</label>
<div data-bind="foreach: settings.settings.plugins.klipper.macros">
<!-- ko if: tab -->
<button
class="btn btn-block btn-small"
data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()"
></button>
<button class="btn btn-block btn-small" data-bind="text: name, click: $parent.executeMacro, enable: $parent.isActive()">
</button>
<!-- /ko -->
</div>
</div>

View File

@ -0,0 +1,854 @@
# German translations for OctoKlipper.
# Copyright (C) 2021 The OctoPrint Project
# This file is distributed under the same license as the OctoKlipper
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: OctoKlipper 0.3.8.2\n"
"Report-Msgid-Bugs-To: i18n@octoprint.org\n"
"POT-Creation-Date: 2021-11-12 17:52+0100\n"
"PO-Revision-Date: 2021-05-13 17:32+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.1\n"
#: octoprint_klipper/__init__.py:93
msgid "Allows to config klipper"
msgstr "Erlaubt Klipper zu konfigurieren"
#: octoprint_klipper/__init__.py:101
msgid "Allows to use klipper macros"
msgstr "Erlaubt Makros zu benutzen"
#: octoprint_klipper/__init__.py:597
msgid "Restart Klipper"
msgstr "Klipper Neustart"
#: octoprint_klipper/__init__.py:598
msgid "You are about to restart Klipper!"
msgstr "Sie sind dabei Klipper neu zu starten!"
#: octoprint_klipper/__init__.py:598
msgid "This will stop ongoing prints!"
msgstr "Dies wird laufende Aufträge abbrechen!"
#: octoprint_klipper/cfgUtils.py:86
msgid "Error: Klipper config file not found at:"
msgstr "Fehler: Klipper Konfigurationsdatei nicht gefunden:"
#: octoprint_klipper/cfgUtils.py:89
msgid "IOError:"
msgstr "IOFehler"
#: octoprint_klipper/cfgUtils.py:96
msgid "Decode Error:"
msgstr "Dekodierungsfehler:"
#: octoprint_klipper/cfgUtils.py:100
msgid "Please convert your config files to utf-8!"
msgstr "Bitte die Konfigurationsdateien nach utf-8 konvertieren!"
#: octoprint_klipper/cfgUtils.py:102
msgid ""
"Or you can also paste your config \n"
"into the Editor and save it."
msgstr ""
"Sie können auch die Konfiguration \n"
"in den Editor einfügen und dann speichern."
#: octoprint_klipper/cfgUtils.py:109
msgid "File not found!"
msgstr "Datei nicht gefunden!"
#: octoprint_klipper/static/js/klipper.js:172
#: octoprint_klipper/templates/klipper_sidebar.jinja2:11
msgid "Go to OctoKlipper Tab"
msgstr "Gehe zum OctoKlipper Reiter"
#: octoprint_klipper/static/js/klipper.js:290
msgid "Restarted Klipper"
msgstr "Klipper neu gestartet"
#: octoprint_klipper/static/js/klipper.js:298
msgid "All ongoing Prints will be stopped!"
msgstr "Alle laufende Drucke werden gestoppt!"
#: octoprint_klipper/static/js/klipper.js:303
msgid "Restart Klipper?"
msgstr "Klipper neu starten?"
#: octoprint_klipper/static/js/klipper.js:305
#: octoprint_klipper/templates/klipper_tab_main.jinja2:39
msgid "Restart"
msgstr "Neustart"
#: octoprint_klipper/static/js/klipper.js:305
msgid "Restart and don't ask this again."
msgstr "Neu starten und dies nicht wieder nachfragen."
#: octoprint_klipper/static/js/klipper_backup.js:110
#: octoprint_klipper/static/js/klipper_settings.js:119
#, python-format
msgid ""
"Failed to remove config %(name)s.</p><p>Please consult octoprint.log for "
"details.</p>"
msgstr ""
"Konnte config %(name)s nicht löschen. </p><p> Bitte im octoprint.log "
"nachsehen für weitere Details.</p>"
#: octoprint_klipper/static/js/klipper_backup.js:113
#: octoprint_klipper/static/js/klipper_settings.js:122
msgid "Could not remove config"
msgstr "Konnte Konfiguration nicht löschen"
#: octoprint_klipper/static/js/klipper_backup.js:122
#, python-format
msgid "You are about to delete backed config file \"%(name)s\"."
msgstr "Sie sind dabei die gesicherte Konfigurationsdatei \"%(name)s\" zu löschen."
#: octoprint_klipper/static/js/klipper_backup.js:138
msgid "This will overwrite any file with the same name on the configpath."
msgstr ""
"Dies wird jede Datei mit dem gleichen Namen im Konfigurationsordner "
"überschreiben."
#: octoprint_klipper/static/js/klipper_backup.js:141
msgid "Are you sure you want to restore now?"
msgstr "Sind sie sicher jetzt wiederherzustellen?"
#: octoprint_klipper/static/js/klipper_backup.js:143
#: octoprint_klipper/static/js/klipper_editor.js:158
msgid "Proceed"
msgstr "Weiter"
#: octoprint_klipper/static/js/klipper_backup.js:168
#, python-format
msgid "You are about to restore %(count)d backed config files."
msgstr ""
"Sie sind dabei %(count)d gesicherte Konfigurationsdateien "
"wiederherzustellen."
#: octoprint_klipper/static/js/klipper_backup.js:183
#, python-format
msgid "You are about to delete %(count)d backed config files."
msgstr "Sie sind dabei %(count)d gesicherte Konfigurationsdateien zu löschen."
#: octoprint_klipper/static/js/klipper_backup.js:193
msgid "Restoring klipper config files"
msgstr "Stelle Klipper Konfigdatei wieder her"
#: octoprint_klipper/static/js/klipper_backup.js:195
#, python-format
msgid "Restoring %(count)d backed config files..."
msgstr "Stelle %(count)d gesicherte Konfigurationsdateien wieder her."
#: octoprint_klipper/static/js/klipper_backup.js:204
#, python-format
msgid "Restored %(filename)s..."
msgstr "%(filename)s wiederhergestellt..."
#: octoprint_klipper/static/js/klipper_backup.js:215
#, python-format
msgid "Restoring of %(filename)s failed, continuing..."
msgstr "Wiederherstellung von Datei %(filename)s gescheitert, setze fort..."
#: octoprint_klipper/static/js/klipper_backup.js:248
msgid "Deleting backup files"
msgstr "Lösche gesicherte Dateien"
#: octoprint_klipper/static/js/klipper_backup.js:249
#, python-format
msgid "Deleting %(count)d backed files..."
msgstr "Lösche %(count)d gesicherte Konfigurationsdateien..."
#: octoprint_klipper/static/js/klipper_backup.js:257
#: octoprint_klipper/static/js/klipper_settings.js:178
#, python-format
msgid "Deleted %(filename)s..."
msgstr "%(filename)s gelöscht..."
#: octoprint_klipper/static/js/klipper_backup.js:263
#: octoprint_klipper/static/js/klipper_settings.js:188
#, python-format
msgid "Deleting of %(filename)s failed, continuing..."
msgstr "Löschung von Datei %(filename)s gescheitert, setze fort... "
#: octoprint_klipper/static/js/klipper_editor.js:68
msgid "Closing without saving"
msgstr "Schließen ohne zu Speichern?"
#: octoprint_klipper/static/js/klipper_editor.js:69
msgid "Your file seems to have changed."
msgstr "Datei scheint geändert worden zu sein."
#: octoprint_klipper/static/js/klipper_editor.js:71
msgid "Do you really want to close it?"
msgstr "Wollen sie wirklich schließen?"
#: octoprint_klipper/static/js/klipper_editor.js:72
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:27
msgid "Close"
msgstr "Schließen"
#: octoprint_klipper/static/js/klipper_editor.js:72
msgid "Do not close"
msgstr "Nicht schließen"
#: octoprint_klipper/static/js/klipper_editor.js:72
#: octoprint_klipper/templates/klipper_editor.jinja2:38
msgid "Save & Close"
msgstr "Speichern & Schließen"
#: octoprint_klipper/static/js/klipper_editor.js:153
msgid "Reload Configfile after SAVE_CONFIG?"
msgstr "Konfiguration neuladen nach SAVE_CONFIG?"
#: octoprint_klipper/static/js/klipper_editor.js:156
msgid "Externally changed config"
msgstr "Extern geänderte Konfiguration"
#: octoprint_klipper/static/js/klipper_editor.js:168
msgid "Your configuration seems to be faulty."
msgstr "Ihre Konfiguration sieht fehlerhaft aus."
#: octoprint_klipper/static/js/klipper_editor.js:172
msgid "Save faulty Configuration?"
msgstr "Speichere fehlerhafte Konfiguration?"
#: octoprint_klipper/static/js/klipper_editor.js:174
msgid "Do not save!"
msgstr "Datei nicht speichern!"
#: octoprint_klipper/static/js/klipper_editor.js:175
msgid "Save anyway!"
msgstr "Speichere trotzdem!"
#: octoprint_klipper/static/js/klipper_editor.js:175
msgid "Save anyway and don't ask this again."
msgstr "Speichere trotzdem und dies nicht wieder nachfragen."
#: octoprint_klipper/static/js/klipper_editor.js:199
msgid "SyntaxCheck"
msgstr "Syntaxprüfung"
#: octoprint_klipper/static/js/klipper_editor.js:199
msgid "SyntaxCheck OK"
msgstr "Syntaxprüfung OK"
#: octoprint_klipper/static/js/klipper_editor.js:231
msgid "Faulty config not saved!"
msgstr "Fehlerhafte Datei nicht gespeichert!"
#: octoprint_klipper/static/js/klipper_editor.js:233
#: octoprint_klipper/static/js/klipper_editor.js:255
#: octoprint_klipper/static/js/klipper_editor.js:372
#: octoprint_klipper/static/js/klipper_editor.js:384
#: octoprint_klipper/templates/klipper_editor.jinja2:40
msgid "Save Config"
msgstr "Speichere Konfig"
#: octoprint_klipper/static/js/klipper_editor.js:253
#: octoprint_klipper/static/js/klipper_editor.js:329
msgid "No filename set"
msgstr "Dateiname nicht angegeben"
#: octoprint_klipper/static/js/klipper_editor.js:305
#: octoprint_klipper/static/js/klipper_editor.js:323
#: octoprint_klipper/static/js/klipper_editor.js:331
msgid "Reload File"
msgstr "Datei neuladen"
#: octoprint_klipper/static/js/klipper_editor.js:309
msgid "Reload Config"
msgstr "Datei neuladen"
#: octoprint_klipper/static/js/klipper_editor.js:309
msgid "File reloaded."
msgstr "Datei neugeladen."
#: octoprint_klipper/static/js/klipper_editor.js:372
msgid "File saved."
msgstr "Datei gespeichert."
#: octoprint_klipper/static/js/klipper_editor.js:382
msgid "File not saved!"
msgstr "Datei nicht gespeichert."
#: octoprint_klipper/static/js/klipper_settings.js:88
msgid "Path: "
msgstr "Pfad: "
#: octoprint_klipper/static/js/klipper_settings.js:131
#, python-format
msgid "You are about to delete config file \"%(name)s\"."
msgstr "Sie sind dabei Konfigurationsdatei \"%(name)s\" zu löschen."
#: octoprint_klipper/static/js/klipper_settings.js:158
#, python-format
msgid "You are about to delete %(count)d config files."
msgstr "Sie sind dabei %(count)d Konfigurationsdateien zu löschen."
#: octoprint_klipper/static/js/klipper_settings.js:168
msgid "Deleting config files"
msgstr "Lösche Konfigurationsdateien"
#: octoprint_klipper/static/js/klipper_settings.js:169
#, python-format
msgid "Deleting %(count)d config files..."
msgstr "Lösche %(count)d Konfigurationsdateien..."
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:5
msgid "Backups"
msgstr "Sicherungen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:17
#: octoprint_klipper/templates/klipper_settings.jinja2:253
msgid "Select all on this page"
msgstr "Wähle alle auf dieser Seite aus"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:20
#: octoprint_klipper/templates/klipper_settings.jinja2:254
msgid "Select all"
msgstr "Alles Auswählen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:25
#: octoprint_klipper/templates/klipper_settings.jinja2:256
msgid "Clear selection"
msgstr "Auswahl zurücksetzen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:265
msgid "Refresh file list"
msgstr "Aktualisiere Dateiliste"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:30
#: octoprint_klipper/templates/klipper_settings.jinja2:266
msgid "Refresh Files"
msgstr "Aktualisieren"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:34
msgid "Restore selected"
msgstr "Ausgewählte wiederherstellen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:36
#: octoprint_klipper/templates/klipper_settings.jinja2:269
msgid "Delete selected"
msgstr "Ausgewählte löschen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:277
msgid "Sort by name"
msgstr "Sortiere nach Name"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:47
#: octoprint_klipper/templates/klipper_settings.jinja2:278
msgid "ascending"
msgstr "aufsteigend"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:50
#: octoprint_klipper/templates/klipper_settings.jinja2:280
msgid "Sort by date"
msgstr "Sortiere nach Datum"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:51
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:55
#: octoprint_klipper/templates/klipper_settings.jinja2:281
#: octoprint_klipper/templates/klipper_settings.jinja2:284
msgid "descending"
msgstr "absteigend"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:54
#: octoprint_klipper/templates/klipper_settings.jinja2:283
msgid "Sort by file size"
msgstr "Sortiere nach Dateigröße"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:65
#: octoprint_klipper/templates/klipper_settings.jinja2:116
#: octoprint_klipper/templates/klipper_settings.jinja2:295
msgid "Name"
msgstr "Name"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:66
#: octoprint_klipper/templates/klipper_settings.jinja2:296
msgid "Size"
msgstr "Größe"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:67
#: octoprint_klipper/templates/klipper_settings.jinja2:297
msgid "Action"
msgstr "Aktion"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:78
#: octoprint_klipper/templates/klipper_settings.jinja2:309
msgid "Delete"
msgstr "Löschen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:81
msgid "Restore"
msgstr "Wiederherstellen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:84
#: octoprint_klipper/templates/klipper_settings.jinja2:312
msgid "Download"
msgstr "Runterladen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:87
msgid "Preview"
msgstr "Vorschau"
#: octoprint_klipper/templates/klipper_editor.jinja2:6
msgid "Decrease Fontsize"
msgstr "Verringere Schriftgröße"
#: octoprint_klipper/templates/klipper_editor.jinja2:9
msgid "Increase Fontsize"
msgstr "Vergrößere Schriftgröße"
#: octoprint_klipper/templates/klipper_editor.jinja2:28
msgid "Filename"
msgstr "Dateiname"
#: octoprint_klipper/templates/klipper_editor.jinja2:31
#: octoprint_klipper/templates/klipper_editor.jinja2:32
msgid "Reload from file"
msgstr "Datei neuladen"
#: octoprint_klipper/templates/klipper_editor.jinja2:34
#: octoprint_klipper/templates/klipper_editor.jinja2:35
msgid "Check Syntax"
msgstr "Syntax prüfen"
#: octoprint_klipper/templates/klipper_editor.jinja2:37
msgid "Save Config and Close"
msgstr "Speichern und schließen"
#: octoprint_klipper/templates/klipper_editor.jinja2:41
msgid "Save"
msgstr "Speichern"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:4
msgid "Performance Graph"
msgstr "Performancediagramm"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:9
msgid "Click labels to hide/show dataset"
msgstr "Auf Labels klicken zum verstecken/anzeigen"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:15
msgid "Fill Datasets"
msgstr "Fülle Flächen"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:22
msgid "Select"
msgstr "Auswählen"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:26
msgid "Analyze Log"
msgstr "Log analysieren"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:35
msgid "Depending on the size of the log file this might take a while."
msgstr "Abhängig von der Größe der Logdatei kann es etwas dauern."
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:55
msgid "Assisted Bed Leveling"
msgstr "Unterstützte Druckbett-Nivellierung"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:14
msgid "Home"
msgstr "Grundstellung"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:42
msgid "Start"
msgstr "Start"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:43
msgid "Previous"
msgstr "Vorheriger"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:44
msgid "Next"
msgstr "Nächster"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:45
msgid "Stop"
msgstr "Stop"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:63
msgid "Coordinate Offset"
msgstr "Koordinaten Versatz"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:8
msgid "Set an offset for all future GCODE move commands in mm."
msgstr "Setze einen Versatz in mm für alle zukünftigen GCODE Bewegungskommandos"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:26
msgid "Add to existing offset"
msgstr "Hinzufügen zum existierenden Versatz"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:31
msgid "Set Offset"
msgstr "Setze Versatz"
#: octoprint_klipper/templates/klipper_param_macro_dialog.jinja2:4
msgid "Run - "
msgstr "Ausführen - "
#: octoprint_klipper/templates/klipper_param_macro_dialog.jinja2:27
msgid "OK"
msgstr "OK"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:59
msgid "PID Tuning"
msgstr "PID Abstimmung"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:8
msgid "Heater / Extruder Name (from config file)"
msgstr "Heizer / Extruder Name (von Konfigdatei)"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:12
msgid "name"
msgstr "Name"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:17
msgid "Target Temperature"
msgstr "Zieltemperatur"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:25
msgid "The result of the tuning cycle is reported in the message log."
msgstr "Die Ergebnisse der Abstimmung werden im Nachrichtenlog ausgegeben."
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:28
msgid "Start Tuning"
msgstr "Starte Abstimmung"
#: octoprint_klipper/templates/klipper_settings.jinja2:3
msgid "Basic"
msgstr "Haupteinstellungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:4
#: octoprint_klipper/templates/klipper_sidebar.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:72
msgid "Macros"
msgstr "Makros"
#: octoprint_klipper/templates/klipper_settings.jinja2:5
msgid "Bed Leveling"
msgstr "Druckbett-Nivellierung"
#: octoprint_klipper/templates/klipper_settings.jinja2:6
msgid "Klipper Configuration"
msgstr "Klipper Konfiguration"
#: octoprint_klipper/templates/klipper_settings.jinja2:12
msgid "Serial Port"
msgstr "Serieller Port"
#: octoprint_klipper/templates/klipper_settings.jinja2:18
#: octoprint_klipper/templates/klipper_settings.jinja2:20
msgid "Replace Connection Panel"
msgstr "Ersetze Verbindungsmenu"
#: octoprint_klipper/templates/klipper_settings.jinja2:25
msgid "Show Short Messages"
msgstr "Zeige Kurzmeldungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:27
#: octoprint_klipper/templates/klipper_settings.jinja2:28
msgid "on NavBar"
msgstr "auf Nav-leiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:30
msgid "on SideBar"
msgstr "auf Seitenleiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:34
#: octoprint_klipper/templates/klipper_settings.jinja2:36
msgid "Enable debug logging"
msgstr "Aktiviere Debugloging"
#: octoprint_klipper/templates/klipper_settings.jinja2:41
msgid "Config Editor"
msgstr "Konfig Editor"
#: octoprint_klipper/templates/klipper_settings.jinja2:43
#: octoprint_klipper/templates/klipper_settings.jinja2:44
msgid "Check parsing on save"
msgstr "Prüfe Syntax beim speichern"
#: octoprint_klipper/templates/klipper_settings.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:48
msgid "Fontsize"
msgstr "Fontgröße"
#: octoprint_klipper/templates/klipper_settings.jinja2:53
msgid "Klipper Config Directory"
msgstr "Klipper Konfig Pfad"
#: octoprint_klipper/templates/klipper_settings.jinja2:59
msgid ""
"The filename of the base config that Klipper is loading. (default is "
"printer.cfg)"
msgstr ""
"Der Dateiname der Haupt-Konfigurationsdatei, die Klipper läd.(Standard "
"ist printer.cfg)"
#: octoprint_klipper/templates/klipper_settings.jinja2:59
msgid "Klipper Base Config Filename"
msgstr "Klipper Haupt-Konfigurationsdatei"
#: octoprint_klipper/templates/klipper_settings.jinja2:65
msgid "Klipper Log File"
msgstr "Klipper Logdatei"
#: octoprint_klipper/templates/klipper_settings.jinja2:71
msgid "Configuration Restart Command"
msgstr "Konfiguration Neustart Befehl"
#: octoprint_klipper/templates/klipper_settings.jinja2:78
msgid "The command that is executed if you want to restart klipper."
msgstr "Der Befehl, der ausgeführt wird, um Klipper neu zu starten."
#: octoprint_klipper/templates/klipper_settings.jinja2:80
msgid "Restart klipper on editor save?"
msgstr "Klipper neu starten nach speichern im Editor?"
#: octoprint_klipper/templates/klipper_settings.jinja2:81
msgid "Restart Klipper on editor save?"
msgstr "Klipper neu starten nach speichern im Editor?"
#: octoprint_klipper/templates/klipper_settings.jinja2:83
msgid "Show Confirmation before restarting Klipper?"
msgstr "Zeige Bestätigung vor Klipper Neustart?"
#: octoprint_klipper/templates/klipper_settings.jinja2:84
msgid "Confirmation before restarting Klipper?"
msgstr "Bestätigung vor Klipper Neustart?"
#: octoprint_klipper/templates/klipper_settings.jinja2:89
msgid "Config Backup"
msgstr "Konfig Sicherungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:91
#: octoprint_klipper/templates/klipper_settings.jinja2:92
msgid "Show Backups"
msgstr "Zeige Sicherungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:101
msgid "These macros are only meant to be used in OctoPrint."
msgstr "Diese Makros sind nur für OctoPrint gedacht."
#: octoprint_klipper/templates/klipper_settings.jinja2:102
msgid "They are not the ones that can be defined in the printer.cfg."
msgstr "Es sind nicht die, die in der printer.cfg definiert werden können."
#: octoprint_klipper/templates/klipper_settings.jinja2:108
msgid "Add macro button to:"
msgstr "Zeige Makroschaltfläche auf:"
#: octoprint_klipper/templates/klipper_settings.jinja2:109
#: octoprint_klipper/templates/klipper_settings.jinja2:123
msgid "Klipper Tab"
msgstr "Klipper Reiter"
#: octoprint_klipper/templates/klipper_settings.jinja2:110
#: octoprint_klipper/templates/klipper_settings.jinja2:126
msgid "Sidebar"
msgstr "Seitenleiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:135
msgid "Command"
msgstr "Befehl"
#: octoprint_klipper/templates/klipper_settings.jinja2:148
msgid "Add Macro"
msgstr "Makro hinzufügen"
#: octoprint_klipper/templates/klipper_settings.jinja2:153
msgid ""
"To show a dialog that asks for parameters you can write your macro like "
"in the following example:"
msgstr ""
"Um ein Dialog anzeigen zu lassen, welches nach Parametern fragt, kann man"
" ein Makro wie im nächsten Beispiel schreiben:"
#: octoprint_klipper/templates/klipper_settings.jinja2:169
msgid ""
"This feature assists in manually leveling your print bed by moving the "
"head to the defined points in sequence.<br />If you use a piece of paper "
"for leveling, set \"Probe Height\" to the paper thickness eg. \"0.1\"."
msgstr ""
"Diese Funktion hilft beim Einstellen des Druckbettes, da "
"es die angegebenen Punkte automatisch nacheinander anfährt. <br />"
"Wenn man ein Papier zum messen nimmt, setzt man \"Messhöhe\" "
"auf die Papierstärke zBsp.: \"0.1\"."
#: octoprint_klipper/templates/klipper_settings.jinja2:173
msgid "Probe Height"
msgstr "Messhöhe"
#: octoprint_klipper/templates/klipper_settings.jinja2:179
msgid "Z-height to probe at"
msgstr "Höhe bei der gemessen wird"
#: octoprint_klipper/templates/klipper_settings.jinja2:183
msgid "Probe Lift"
msgstr "Höhe für Seitenbewegung"
#: octoprint_klipper/templates/klipper_settings.jinja2:189
msgid "Lift Head by this amount before moving."
msgstr "Hebe Druckkopf auf diese Höhe vor einer Seitenbewegung"
#: octoprint_klipper/templates/klipper_settings.jinja2:193
msgid "Probe Feedrate Z"
msgstr "Geschwindigkeit Z"
#: octoprint_klipper/templates/klipper_settings.jinja2:202
msgid "Feedrate X/Y"
msgstr "Geschwindigkeit X/Y"
#: octoprint_klipper/templates/klipper_settings.jinja2:211
msgid "Probe Points"
msgstr "Messpunkte"
#: octoprint_klipper/templates/klipper_settings.jinja2:238
msgid "Add Point"
msgstr "Füge Messpunkt hinzu"
#: octoprint_klipper/templates/klipper_settings.jinja2:245
msgid "Config Files"
msgstr "Konfig Dateien"
#: octoprint_klipper/templates/klipper_settings.jinja2:259
msgid "Add new File"
msgstr "Erstelle neue Datei"
#: octoprint_klipper/templates/klipper_settings.jinja2:260
msgid "New File"
msgstr "Neue Datei"
#: octoprint_klipper/templates/klipper_settings.jinja2:262
msgid "Open last config"
msgstr "Öffne letzte Konfig"
#: octoprint_klipper/templates/klipper_settings.jinja2:263
msgid "Open last Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_settings.jinja2:289
msgid "Path to the config files."
msgstr "Pfad zu den Konfigurationsdateien"
#: octoprint_klipper/templates/klipper_settings.jinja2:315
msgid "Edit"
msgstr "Bearbeiten"
#: octoprint_klipper/templates/klipper_settings.jinja2:344
msgid "Donate"
msgstr "Spenden"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:3
msgid "Printer Profile"
msgstr "Druckerprofil"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:5
msgid "Connect"
msgstr "Verbinde"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:6
msgid "Open Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:3
msgid "Messages"
msgstr "Nachricht"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:6
msgid "Open the OctoKlipper Settings"
msgstr "Öffne die OctoKlipper Einstellungen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:17
msgid "Clear Log"
msgstr "Log löschen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:26
msgid "Query Klipper for its current status"
msgstr "Aktuellen Status von Klipper anfordern"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:27
msgid "Get Status"
msgstr "Statusabfrage"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:31
msgid "Show the Editor"
msgstr "Öffne den Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:33
msgid "Show Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:41
msgid ""
"This will cause the host software to reload its config and perform an "
"internal reset"
msgstr "Das wird die Konfigurationsdatei neuladen und die Hostsoftware neu starten"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:42
msgid "Host"
msgstr "Host"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:45
msgid ""
"Similar to a host restart, but also clears any error state from the "
"micro-controller"
msgstr "Wie der Host Neustart, aber zusätzlich wird die Firmware neu gestartet"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:46
msgid "Firmware"
msgstr "Firmware"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:52
msgid "Tools"
msgstr "Tools"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:54
msgid ""
"Assists in manually leveling your printbed by moving the head to a "
"configurable set of positions in sequence."
msgstr ""
"Unterstützt beim manuellem Bettnivellieren, <br>\n"
"da der Druckkopf nacheinander einstellbare Punkte anfährt."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:58
msgid "Determines optimal PID parameters by heat cycling the hotend/bed."
msgstr ""
"Findet optimale PID Parameter beim zyklischen Heizen des "
"Druckkopfes/-bettes."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:62
msgid "Sets a offset for subsequent GCODE coordinates."
msgstr "Setze einen Versatz für alle weiteren GCODE Koordinaten"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:66
msgid ""
"Assists in debugging performance issues by analyzing the Klipper log "
"files."
msgstr ""
"Unterstützt, mit dem analysieren des Klipperlogs, beim Suchen von "
"Performanceproblemen."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:67
msgid "Analyze Klipper Log"
msgstr "Analysiere Klipperlog"

104
octoprint_klipper/util.py Normal file
View File

@ -0,0 +1,104 @@
from . import logger
def migrate_old_settings(self, settings):
'''
For Old settings
'''
migrate_settings(settings, "serialport", "connection", "port")
migrate_settings(settings, "replace_connection_panel", "connection", "replace_connection_panel")
migrate_settings(settings, "probeHeight", "probe", "height")
migrate_settings(settings, "probeLift", "probe", "lift")
migrate_settings(settings, "probeSpeedXy", "probe", "speed_xy")
migrate_settings(settings, "probeSpeedZ", "probe", "speed_z")
migrate_settings(settings, "configPath", "configuration", "configpath")
if settings.has(["probePoints"]):
points = settings.get(["probePoints"])
points_new = [dict(name="", x=int(p["x"]), y=int(p["y"]), z=0) for p in points]
settings.set(["probe", "points"], points_new)
settings.remove(["probePoints"])
def migrate_settings(self, settings, old, new, new2=""):
"""migrate setting to setting with an additional path
Args:
settings (any): instance of self._settings
old (str): the old setting to migrate
new (str): group or only new setting if there is no new2
new2 (str, optional): the new setting to migrate to. Defaults to "".
""" ''''''
if settings.has(old):
if new2 != "":
logger.log_info(self, "migrate setting for '" + old + "' -> '" + new + "/" + new2 + "'")
settings.set([new, new2], settings.get(old))
else:
logger.log_info(self, "migrate setting for '" + old + "' -> '" + new + "'")
settings.set([new], settings.get(old))
settings.remove(old)
def migrate_settings_configuration(self, settings, new, old):
"""migrate setting in path configuration to new name
:param settings: the class of the mixin
:type settings: class
:param new: new name
:type new: str
:param old: the old name
:type old: str
"""
if settings.has(["configuration", old]):
logger.log_info(self, "migrate setting for 'configuration/" + old + "' -> 'configuration/" + new + "'")
settings.set(["configuration", new], settings.get(["configuration", old]))
settings.remove(["configuration", old])
def poll_status(self):
self._printer.commands("STATUS")
def update_status(self, subtype, status):
send_message(
self,
type = "status",
subtype = subtype,
payload = status)
def file_exist(self, filepath):
'''
Returns if a file exists and shows PopUp if not
'''
from os import path
if not path.isfile(filepath):
logger.log_debug(self, "File: <br />" + filepath + "<br /> does not exist!")
send_message(
self,
type = "PopUp",
subtype = "warning",
title = "OctoKlipper Settings",
payload = "File: <br />" + filepath + "<br /> does not exist!")
return False
else:
return True
def key_exist(dict, key1, key2):
try:
dict[key1][key2]
except KeyError:
return False
else:
return True
def send_message(self, type, subtype, title = "", payload = ""):
"""
Send Message over API to FrontEnd
"""
import datetime
self._plugin_manager.send_plugin_message(
self._identifier,
dict(
time = datetime.datetime.now().strftime("%H:%M:%S"),
type = type,
subtype = subtype,
title = title,
payload = payload
)
)

View File

@ -19,7 +19,7 @@ plugin_package = "octoprint_klipper"
plugin_name = "OctoKlipper"
plugin_version = "0.3.8.4"
plugin_version = "0.3.9"
plugin_description = """A plugin for OctoPrint to configure,control and monitor the Klipper 3D printer software."""

Binary file not shown.

View File

@ -0,0 +1,854 @@
# German translations for OctoKlipper.
# Copyright (C) 2021 The OctoPrint Project
# This file is distributed under the same license as the OctoKlipper
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: OctoKlipper 0.3.8.2\n"
"Report-Msgid-Bugs-To: i18n@octoprint.org\n"
"POT-Creation-Date: 2021-11-12 17:52+0100\n"
"PO-Revision-Date: 2021-05-13 17:32+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.1\n"
#: octoprint_klipper/__init__.py:93
msgid "Allows to config klipper"
msgstr "Erlaubt Klipper zu konfigurieren"
#: octoprint_klipper/__init__.py:101
msgid "Allows to use klipper macros"
msgstr "Erlaubt Makros zu benutzen"
#: octoprint_klipper/__init__.py:597
msgid "Restart Klipper"
msgstr "Klipper Neustart"
#: octoprint_klipper/__init__.py:598
msgid "You are about to restart Klipper!"
msgstr "Sie sind dabei Klipper neu zu starten!"
#: octoprint_klipper/__init__.py:598
msgid "This will stop ongoing prints!"
msgstr "Dies wird laufende Aufträge abbrechen!"
#: octoprint_klipper/cfgUtils.py:86
msgid "Error: Klipper config file not found at:"
msgstr "Fehler: Klipper Konfigurationsdatei nicht gefunden:"
#: octoprint_klipper/cfgUtils.py:89
msgid "IOError:"
msgstr "IOFehler"
#: octoprint_klipper/cfgUtils.py:96
msgid "Decode Error:"
msgstr "Dekodierungsfehler:"
#: octoprint_klipper/cfgUtils.py:100
msgid "Please convert your config files to utf-8!"
msgstr "Bitte die Konfigurationsdateien nach utf-8 konvertieren!"
#: octoprint_klipper/cfgUtils.py:102
msgid ""
"Or you can also paste your config \n"
"into the Editor and save it."
msgstr ""
"Sie können auch die Konfiguration \n"
"in den Editor einfügen und dann speichern."
#: octoprint_klipper/cfgUtils.py:109
msgid "File not found!"
msgstr "Datei nicht gefunden!"
#: octoprint_klipper/static/js/klipper.js:172
#: octoprint_klipper/templates/klipper_sidebar.jinja2:11
msgid "Go to OctoKlipper Tab"
msgstr "Gehe zum OctoKlipper Reiter"
#: octoprint_klipper/static/js/klipper.js:290
msgid "Restarted Klipper"
msgstr "Klipper neu gestartet"
#: octoprint_klipper/static/js/klipper.js:298
msgid "All ongoing Prints will be stopped!"
msgstr "Alle laufende Drucke werden gestoppt!"
#: octoprint_klipper/static/js/klipper.js:303
msgid "Restart Klipper?"
msgstr "Klipper neu starten?"
#: octoprint_klipper/static/js/klipper.js:305
#: octoprint_klipper/templates/klipper_tab_main.jinja2:39
msgid "Restart"
msgstr "Neustart"
#: octoprint_klipper/static/js/klipper.js:305
msgid "Restart and don't ask this again."
msgstr "Neu starten und dies nicht wieder nachfragen."
#: octoprint_klipper/static/js/klipper_backup.js:110
#: octoprint_klipper/static/js/klipper_settings.js:119
#, python-format
msgid ""
"Failed to remove config %(name)s.</p><p>Please consult octoprint.log for "
"details.</p>"
msgstr ""
"Konnte config %(name)s nicht löschen. </p><p> Bitte im octoprint.log "
"nachsehen für weitere Details.</p>"
#: octoprint_klipper/static/js/klipper_backup.js:113
#: octoprint_klipper/static/js/klipper_settings.js:122
msgid "Could not remove config"
msgstr "Konnte Konfiguration nicht löschen"
#: octoprint_klipper/static/js/klipper_backup.js:122
#, python-format
msgid "You are about to delete backed config file \"%(name)s\"."
msgstr "Sie sind dabei die gesicherte Konfigurationsdatei \"%(name)s\" zu löschen."
#: octoprint_klipper/static/js/klipper_backup.js:138
msgid "This will overwrite any file with the same name on the configpath."
msgstr ""
"Dies wird jede Datei mit dem gleichen Namen im Konfigurationsordner "
"überschreiben."
#: octoprint_klipper/static/js/klipper_backup.js:141
msgid "Are you sure you want to restore now?"
msgstr "Sind sie sicher jetzt wiederherzustellen?"
#: octoprint_klipper/static/js/klipper_backup.js:143
#: octoprint_klipper/static/js/klipper_editor.js:158
msgid "Proceed"
msgstr "Weiter"
#: octoprint_klipper/static/js/klipper_backup.js:168
#, python-format
msgid "You are about to restore %(count)d backed config files."
msgstr ""
"Sie sind dabei %(count)d gesicherte Konfigurationsdateien "
"wiederherzustellen."
#: octoprint_klipper/static/js/klipper_backup.js:183
#, python-format
msgid "You are about to delete %(count)d backed config files."
msgstr "Sie sind dabei %(count)d gesicherte Konfigurationsdateien zu löschen."
#: octoprint_klipper/static/js/klipper_backup.js:193
msgid "Restoring klipper config files"
msgstr "Stelle Klipper Konfigdatei wieder her"
#: octoprint_klipper/static/js/klipper_backup.js:195
#, python-format
msgid "Restoring %(count)d backed config files..."
msgstr "Stelle %(count)d gesicherte Konfigurationsdateien wieder her."
#: octoprint_klipper/static/js/klipper_backup.js:204
#, python-format
msgid "Restored %(filename)s..."
msgstr "%(filename)s wiederhergestellt..."
#: octoprint_klipper/static/js/klipper_backup.js:215
#, python-format
msgid "Restoring of %(filename)s failed, continuing..."
msgstr "Wiederherstellung von Datei %(filename)s gescheitert, setze fort..."
#: octoprint_klipper/static/js/klipper_backup.js:248
msgid "Deleting backup files"
msgstr "Lösche gesicherte Dateien"
#: octoprint_klipper/static/js/klipper_backup.js:249
#, python-format
msgid "Deleting %(count)d backed files..."
msgstr "Lösche %(count)d gesicherte Konfigurationsdateien..."
#: octoprint_klipper/static/js/klipper_backup.js:257
#: octoprint_klipper/static/js/klipper_settings.js:178
#, python-format
msgid "Deleted %(filename)s..."
msgstr "%(filename)s gelöscht..."
#: octoprint_klipper/static/js/klipper_backup.js:263
#: octoprint_klipper/static/js/klipper_settings.js:188
#, python-format
msgid "Deleting of %(filename)s failed, continuing..."
msgstr "Löschung von Datei %(filename)s gescheitert, setze fort... "
#: octoprint_klipper/static/js/klipper_editor.js:68
msgid "Closing without saving"
msgstr "Schließen ohne zu Speichern?"
#: octoprint_klipper/static/js/klipper_editor.js:69
msgid "Your file seems to have changed."
msgstr "Datei scheint geändert worden zu sein."
#: octoprint_klipper/static/js/klipper_editor.js:71
msgid "Do you really want to close it?"
msgstr "Wollen sie wirklich schließen?"
#: octoprint_klipper/static/js/klipper_editor.js:72
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:27
msgid "Close"
msgstr "Schließen"
#: octoprint_klipper/static/js/klipper_editor.js:72
msgid "Do not close"
msgstr "Nicht schließen"
#: octoprint_klipper/static/js/klipper_editor.js:72
#: octoprint_klipper/templates/klipper_editor.jinja2:38
msgid "Save & Close"
msgstr "Speichern & Schließen"
#: octoprint_klipper/static/js/klipper_editor.js:153
msgid "Reload Configfile after SAVE_CONFIG?"
msgstr "Konfiguration neuladen nach SAVE_CONFIG?"
#: octoprint_klipper/static/js/klipper_editor.js:156
msgid "Externally changed config"
msgstr "Extern geänderte Konfiguration"
#: octoprint_klipper/static/js/klipper_editor.js:168
msgid "Your configuration seems to be faulty."
msgstr "Ihre Konfiguration sieht fehlerhaft aus."
#: octoprint_klipper/static/js/klipper_editor.js:172
msgid "Save faulty Configuration?"
msgstr "Speichere fehlerhafte Konfiguration?"
#: octoprint_klipper/static/js/klipper_editor.js:174
msgid "Do not save!"
msgstr "Datei nicht speichern!"
#: octoprint_klipper/static/js/klipper_editor.js:175
msgid "Save anyway!"
msgstr "Speichere trotzdem!"
#: octoprint_klipper/static/js/klipper_editor.js:175
msgid "Save anyway and don't ask this again."
msgstr "Speichere trotzdem und dies nicht wieder nachfragen."
#: octoprint_klipper/static/js/klipper_editor.js:199
msgid "SyntaxCheck"
msgstr "Syntaxprüfung"
#: octoprint_klipper/static/js/klipper_editor.js:199
msgid "SyntaxCheck OK"
msgstr "Syntaxprüfung OK"
#: octoprint_klipper/static/js/klipper_editor.js:231
msgid "Faulty config not saved!"
msgstr "Fehlerhafte Datei nicht gespeichert!"
#: octoprint_klipper/static/js/klipper_editor.js:233
#: octoprint_klipper/static/js/klipper_editor.js:255
#: octoprint_klipper/static/js/klipper_editor.js:372
#: octoprint_klipper/static/js/klipper_editor.js:384
#: octoprint_klipper/templates/klipper_editor.jinja2:40
msgid "Save Config"
msgstr "Speichere Konfig"
#: octoprint_klipper/static/js/klipper_editor.js:253
#: octoprint_klipper/static/js/klipper_editor.js:329
msgid "No filename set"
msgstr "Dateiname nicht angegeben"
#: octoprint_klipper/static/js/klipper_editor.js:305
#: octoprint_klipper/static/js/klipper_editor.js:323
#: octoprint_klipper/static/js/klipper_editor.js:331
msgid "Reload File"
msgstr "Datei neuladen"
#: octoprint_klipper/static/js/klipper_editor.js:309
msgid "Reload Config"
msgstr "Datei neuladen"
#: octoprint_klipper/static/js/klipper_editor.js:309
msgid "File reloaded."
msgstr "Datei neugeladen."
#: octoprint_klipper/static/js/klipper_editor.js:372
msgid "File saved."
msgstr "Datei gespeichert."
#: octoprint_klipper/static/js/klipper_editor.js:382
msgid "File not saved!"
msgstr "Datei nicht gespeichert."
#: octoprint_klipper/static/js/klipper_settings.js:88
msgid "Path: "
msgstr "Pfad: "
#: octoprint_klipper/static/js/klipper_settings.js:131
#, python-format
msgid "You are about to delete config file \"%(name)s\"."
msgstr "Sie sind dabei Konfigurationsdatei \"%(name)s\" zu löschen."
#: octoprint_klipper/static/js/klipper_settings.js:158
#, python-format
msgid "You are about to delete %(count)d config files."
msgstr "Sie sind dabei %(count)d Konfigurationsdateien zu löschen."
#: octoprint_klipper/static/js/klipper_settings.js:168
msgid "Deleting config files"
msgstr "Lösche Konfigurationsdateien"
#: octoprint_klipper/static/js/klipper_settings.js:169
#, python-format
msgid "Deleting %(count)d config files..."
msgstr "Lösche %(count)d Konfigurationsdateien..."
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:5
msgid "Backups"
msgstr "Sicherungen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:17
#: octoprint_klipper/templates/klipper_settings.jinja2:253
msgid "Select all on this page"
msgstr "Wähle alle auf dieser Seite aus"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:20
#: octoprint_klipper/templates/klipper_settings.jinja2:254
msgid "Select all"
msgstr "Alles Auswählen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:25
#: octoprint_klipper/templates/klipper_settings.jinja2:256
msgid "Clear selection"
msgstr "Auswahl zurücksetzen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:265
msgid "Refresh file list"
msgstr "Aktualisiere Dateiliste"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:30
#: octoprint_klipper/templates/klipper_settings.jinja2:266
msgid "Refresh Files"
msgstr "Aktualisieren"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:34
msgid "Restore selected"
msgstr "Ausgewählte wiederherstellen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:36
#: octoprint_klipper/templates/klipper_settings.jinja2:269
msgid "Delete selected"
msgstr "Ausgewählte löschen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:277
msgid "Sort by name"
msgstr "Sortiere nach Name"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:47
#: octoprint_klipper/templates/klipper_settings.jinja2:278
msgid "ascending"
msgstr "aufsteigend"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:50
#: octoprint_klipper/templates/klipper_settings.jinja2:280
msgid "Sort by date"
msgstr "Sortiere nach Datum"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:51
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:55
#: octoprint_klipper/templates/klipper_settings.jinja2:281
#: octoprint_klipper/templates/klipper_settings.jinja2:284
msgid "descending"
msgstr "absteigend"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:54
#: octoprint_klipper/templates/klipper_settings.jinja2:283
msgid "Sort by file size"
msgstr "Sortiere nach Dateigröße"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:65
#: octoprint_klipper/templates/klipper_settings.jinja2:116
#: octoprint_klipper/templates/klipper_settings.jinja2:295
msgid "Name"
msgstr "Name"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:66
#: octoprint_klipper/templates/klipper_settings.jinja2:296
msgid "Size"
msgstr "Größe"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:67
#: octoprint_klipper/templates/klipper_settings.jinja2:297
msgid "Action"
msgstr "Aktion"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:78
#: octoprint_klipper/templates/klipper_settings.jinja2:309
msgid "Delete"
msgstr "Löschen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:81
msgid "Restore"
msgstr "Wiederherstellen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:84
#: octoprint_klipper/templates/klipper_settings.jinja2:312
msgid "Download"
msgstr "Runterladen"
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:87
msgid "Preview"
msgstr "Vorschau"
#: octoprint_klipper/templates/klipper_editor.jinja2:6
msgid "Decrease Fontsize"
msgstr "Verringere Schriftgröße"
#: octoprint_klipper/templates/klipper_editor.jinja2:9
msgid "Increase Fontsize"
msgstr "Vergrößere Schriftgröße"
#: octoprint_klipper/templates/klipper_editor.jinja2:28
msgid "Filename"
msgstr "Dateiname"
#: octoprint_klipper/templates/klipper_editor.jinja2:31
#: octoprint_klipper/templates/klipper_editor.jinja2:32
msgid "Reload from file"
msgstr "Datei neuladen"
#: octoprint_klipper/templates/klipper_editor.jinja2:34
#: octoprint_klipper/templates/klipper_editor.jinja2:35
msgid "Check Syntax"
msgstr "Syntax prüfen"
#: octoprint_klipper/templates/klipper_editor.jinja2:37
msgid "Save Config and Close"
msgstr "Speichern und schließen"
#: octoprint_klipper/templates/klipper_editor.jinja2:41
msgid "Save"
msgstr "Speichern"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:4
msgid "Performance Graph"
msgstr "Performancediagramm"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:9
msgid "Click labels to hide/show dataset"
msgstr "Auf Labels klicken zum verstecken/anzeigen"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:15
msgid "Fill Datasets"
msgstr "Fülle Flächen"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:22
msgid "Select"
msgstr "Auswählen"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:26
msgid "Analyze Log"
msgstr "Log analysieren"
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:35
msgid "Depending on the size of the log file this might take a while."
msgstr "Abhängig von der Größe der Logdatei kann es etwas dauern."
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:55
msgid "Assisted Bed Leveling"
msgstr "Unterstützte Druckbett-Nivellierung"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:14
msgid "Home"
msgstr "Grundstellung"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:42
msgid "Start"
msgstr "Start"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:43
msgid "Previous"
msgstr "Vorheriger"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:44
msgid "Next"
msgstr "Nächster"
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:45
msgid "Stop"
msgstr "Stop"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:63
msgid "Coordinate Offset"
msgstr "Koordinaten Versatz"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:8
msgid "Set an offset for all future GCODE move commands in mm."
msgstr "Setze einen Versatz in mm für alle zukünftigen GCODE Bewegungskommandos"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:26
msgid "Add to existing offset"
msgstr "Hinzufügen zum existierenden Versatz"
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:31
msgid "Set Offset"
msgstr "Setze Versatz"
#: octoprint_klipper/templates/klipper_param_macro_dialog.jinja2:4
msgid "Run - "
msgstr "Ausführen - "
#: octoprint_klipper/templates/klipper_param_macro_dialog.jinja2:27
msgid "OK"
msgstr "OK"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:59
msgid "PID Tuning"
msgstr "PID Abstimmung"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:8
msgid "Heater / Extruder Name (from config file)"
msgstr "Heizer / Extruder Name (von Konfigdatei)"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:12
msgid "name"
msgstr "Name"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:17
msgid "Target Temperature"
msgstr "Zieltemperatur"
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:25
msgid "The result of the tuning cycle is reported in the message log."
msgstr "Die Ergebnisse der Abstimmung werden im Nachrichtenlog ausgegeben."
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:28
msgid "Start Tuning"
msgstr "Starte Abstimmung"
#: octoprint_klipper/templates/klipper_settings.jinja2:3
msgid "Basic"
msgstr "Haupteinstellungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:4
#: octoprint_klipper/templates/klipper_sidebar.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:72
msgid "Macros"
msgstr "Makros"
#: octoprint_klipper/templates/klipper_settings.jinja2:5
msgid "Bed Leveling"
msgstr "Druckbett-Nivellierung"
#: octoprint_klipper/templates/klipper_settings.jinja2:6
msgid "Klipper Configuration"
msgstr "Klipper Konfiguration"
#: octoprint_klipper/templates/klipper_settings.jinja2:12
msgid "Serial Port"
msgstr "Serieller Port"
#: octoprint_klipper/templates/klipper_settings.jinja2:18
#: octoprint_klipper/templates/klipper_settings.jinja2:20
msgid "Replace Connection Panel"
msgstr "Ersetze Verbindungsmenu"
#: octoprint_klipper/templates/klipper_settings.jinja2:25
msgid "Show Short Messages"
msgstr "Zeige Kurzmeldungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:27
#: octoprint_klipper/templates/klipper_settings.jinja2:28
msgid "on NavBar"
msgstr "auf Nav-leiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:30
msgid "on SideBar"
msgstr "auf Seitenleiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:34
#: octoprint_klipper/templates/klipper_settings.jinja2:36
msgid "Enable debug logging"
msgstr "Aktiviere Debugloging"
#: octoprint_klipper/templates/klipper_settings.jinja2:41
msgid "Config Editor"
msgstr "Konfig Editor"
#: octoprint_klipper/templates/klipper_settings.jinja2:43
#: octoprint_klipper/templates/klipper_settings.jinja2:44
msgid "Check parsing on save"
msgstr "Prüfe Syntax beim speichern"
#: octoprint_klipper/templates/klipper_settings.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:48
msgid "Fontsize"
msgstr "Fontgröße"
#: octoprint_klipper/templates/klipper_settings.jinja2:53
msgid "Klipper Config Directory"
msgstr "Klipper Konfig Pfad"
#: octoprint_klipper/templates/klipper_settings.jinja2:59
msgid ""
"The filename of the base config that Klipper is loading. (default is "
"printer.cfg)"
msgstr ""
"Der Dateiname der Haupt-Konfigurationsdatei, die Klipper läd.(Standard "
"ist printer.cfg)"
#: octoprint_klipper/templates/klipper_settings.jinja2:59
msgid "Klipper Base Config Filename"
msgstr "Klipper Haupt-Konfigurationsdatei"
#: octoprint_klipper/templates/klipper_settings.jinja2:65
msgid "Klipper Log File"
msgstr "Klipper Logdatei"
#: octoprint_klipper/templates/klipper_settings.jinja2:71
msgid "Configuration Restart Command"
msgstr "Konfiguration Neustart Befehl"
#: octoprint_klipper/templates/klipper_settings.jinja2:78
msgid "The command that is executed if you want to restart klipper."
msgstr "Der Befehl, der ausgeführt wird, um Klipper neu zu starten."
#: octoprint_klipper/templates/klipper_settings.jinja2:80
msgid "Restart klipper on editor save?"
msgstr "Klipper neu starten nach speichern im Editor?"
#: octoprint_klipper/templates/klipper_settings.jinja2:81
msgid "Restart Klipper on editor save?"
msgstr "Klipper neu starten nach speichern im Editor?"
#: octoprint_klipper/templates/klipper_settings.jinja2:83
msgid "Show Confirmation before restarting Klipper?"
msgstr "Zeige Bestätigung vor Klipper Neustart?"
#: octoprint_klipper/templates/klipper_settings.jinja2:84
msgid "Confirmation before restarting Klipper?"
msgstr "Bestätigung vor Klipper Neustart?"
#: octoprint_klipper/templates/klipper_settings.jinja2:89
msgid "Config Backup"
msgstr "Konfig Sicherungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:91
#: octoprint_klipper/templates/klipper_settings.jinja2:92
msgid "Show Backups"
msgstr "Zeige Sicherungen"
#: octoprint_klipper/templates/klipper_settings.jinja2:101
msgid "These macros are only meant to be used in OctoPrint."
msgstr "Diese Makros sind nur für OctoPrint gedacht."
#: octoprint_klipper/templates/klipper_settings.jinja2:102
msgid "They are not the ones that can be defined in the printer.cfg."
msgstr "Es sind nicht die, die in der printer.cfg definiert werden können."
#: octoprint_klipper/templates/klipper_settings.jinja2:108
msgid "Add macro button to:"
msgstr "Zeige Makroschaltfläche auf:"
#: octoprint_klipper/templates/klipper_settings.jinja2:109
#: octoprint_klipper/templates/klipper_settings.jinja2:123
msgid "Klipper Tab"
msgstr "Klipper Reiter"
#: octoprint_klipper/templates/klipper_settings.jinja2:110
#: octoprint_klipper/templates/klipper_settings.jinja2:126
msgid "Sidebar"
msgstr "Seitenleiste"
#: octoprint_klipper/templates/klipper_settings.jinja2:135
msgid "Command"
msgstr "Befehl"
#: octoprint_klipper/templates/klipper_settings.jinja2:148
msgid "Add Macro"
msgstr "Makro hinzufügen"
#: octoprint_klipper/templates/klipper_settings.jinja2:153
msgid ""
"To show a dialog that asks for parameters you can write your macro like "
"in the following example:"
msgstr ""
"Um ein Dialog anzeigen zu lassen, welches nach Parametern fragt, kann man"
" ein Makro wie im nächsten Beispiel schreiben:"
#: octoprint_klipper/templates/klipper_settings.jinja2:169
msgid ""
"This feature assists in manually leveling your print bed by moving the "
"head to the defined points in sequence.<br />If you use a piece of paper "
"for leveling, set \"Probe Height\" to the paper thickness eg. \"0.1\"."
msgstr ""
"Diese Funktion hilft beim Einstellen des Druckbettes, da "
"es die angegebenen Punkte automatisch nacheinander anfährt. <br />"
"Wenn man ein Papier zum messen nimmt, setzt man \"Messhöhe\" "
"auf die Papierstärke zBsp.: \"0.1\"."
#: octoprint_klipper/templates/klipper_settings.jinja2:173
msgid "Probe Height"
msgstr "Messhöhe"
#: octoprint_klipper/templates/klipper_settings.jinja2:179
msgid "Z-height to probe at"
msgstr "Höhe bei der gemessen wird"
#: octoprint_klipper/templates/klipper_settings.jinja2:183
msgid "Probe Lift"
msgstr "Höhe für Seitenbewegung"
#: octoprint_klipper/templates/klipper_settings.jinja2:189
msgid "Lift Head by this amount before moving."
msgstr "Hebe Druckkopf auf diese Höhe vor einer Seitenbewegung"
#: octoprint_klipper/templates/klipper_settings.jinja2:193
msgid "Probe Feedrate Z"
msgstr "Geschwindigkeit Z"
#: octoprint_klipper/templates/klipper_settings.jinja2:202
msgid "Feedrate X/Y"
msgstr "Geschwindigkeit X/Y"
#: octoprint_klipper/templates/klipper_settings.jinja2:211
msgid "Probe Points"
msgstr "Messpunkte"
#: octoprint_klipper/templates/klipper_settings.jinja2:238
msgid "Add Point"
msgstr "Füge Messpunkt hinzu"
#: octoprint_klipper/templates/klipper_settings.jinja2:245
msgid "Config Files"
msgstr "Konfig Dateien"
#: octoprint_klipper/templates/klipper_settings.jinja2:259
msgid "Add new File"
msgstr "Erstelle neue Datei"
#: octoprint_klipper/templates/klipper_settings.jinja2:260
msgid "New File"
msgstr "Neue Datei"
#: octoprint_klipper/templates/klipper_settings.jinja2:262
msgid "Open last config"
msgstr "Öffne letzte Konfig"
#: octoprint_klipper/templates/klipper_settings.jinja2:263
msgid "Open last Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_settings.jinja2:289
msgid "Path to the config files."
msgstr "Pfad zu den Konfigurationsdateien"
#: octoprint_klipper/templates/klipper_settings.jinja2:315
msgid "Edit"
msgstr "Bearbeiten"
#: octoprint_klipper/templates/klipper_settings.jinja2:344
msgid "Donate"
msgstr "Spenden"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:3
msgid "Printer Profile"
msgstr "Druckerprofil"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:5
msgid "Connect"
msgstr "Verbinde"
#: octoprint_klipper/templates/klipper_sidebar.jinja2:6
msgid "Open Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:3
msgid "Messages"
msgstr "Nachricht"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:6
msgid "Open the OctoKlipper Settings"
msgstr "Öffne die OctoKlipper Einstellungen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:17
msgid "Clear Log"
msgstr "Log löschen"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:26
msgid "Query Klipper for its current status"
msgstr "Aktuellen Status von Klipper anfordern"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:27
msgid "Get Status"
msgstr "Statusabfrage"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:31
msgid "Show the Editor"
msgstr "Öffne den Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:33
msgid "Show Editor"
msgstr "Öffne Editor"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:41
msgid ""
"This will cause the host software to reload its config and perform an "
"internal reset"
msgstr "Das wird die Konfigurationsdatei neuladen und die Hostsoftware neu starten"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:42
msgid "Host"
msgstr "Host"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:45
msgid ""
"Similar to a host restart, but also clears any error state from the "
"micro-controller"
msgstr "Wie der Host Neustart, aber zusätzlich wird die Firmware neu gestartet"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:46
msgid "Firmware"
msgstr "Firmware"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:52
msgid "Tools"
msgstr "Tools"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:54
msgid ""
"Assists in manually leveling your printbed by moving the head to a "
"configurable set of positions in sequence."
msgstr ""
"Unterstützt beim manuellem Bettnivellieren, <br>\n"
"da der Druckkopf nacheinander einstellbare Punkte anfährt."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:58
msgid "Determines optimal PID parameters by heat cycling the hotend/bed."
msgstr ""
"Findet optimale PID Parameter beim zyklischen Heizen des "
"Druckkopfes/-bettes."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:62
msgid "Sets a offset for subsequent GCODE coordinates."
msgstr "Setze einen Versatz für alle weiteren GCODE Koordinaten"
#: octoprint_klipper/templates/klipper_tab_main.jinja2:66
msgid ""
"Assists in debugging performance issues by analyzing the Klipper log "
"files."
msgstr ""
"Unterstützt, mit dem analysieren des Klipperlogs, beim Suchen von "
"Performanceproblemen."
#: octoprint_klipper/templates/klipper_tab_main.jinja2:67
msgid "Analyze Klipper Log"
msgstr "Analysiere Klipperlog"

832
translations/messages.pot Normal file
View File

@ -0,0 +1,832 @@
# Translations template for OctoKlipper.
# Copyright (C) 2021 The OctoPrint Project
# This file is distributed under the same license as the OctoKlipper
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: OctoKlipper 0.3.9rc6\n"
"Report-Msgid-Bugs-To: i18n@octoprint.org\n"
"POT-Creation-Date: 2021-11-12 17:52+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.1\n"
#: octoprint_klipper/__init__.py:93
msgid "Allows to config klipper"
msgstr ""
#: octoprint_klipper/__init__.py:101
msgid "Allows to use klipper macros"
msgstr ""
#: octoprint_klipper/__init__.py:597
msgid "Restart Klipper"
msgstr ""
#: octoprint_klipper/__init__.py:598
msgid "You are about to restart Klipper!"
msgstr ""
#: octoprint_klipper/__init__.py:598
msgid "This will stop ongoing prints!"
msgstr ""
#: octoprint_klipper/cfgUtils.py:86
msgid "Error: Klipper config file not found at:"
msgstr ""
#: octoprint_klipper/cfgUtils.py:89
msgid "IOError:"
msgstr ""
#: octoprint_klipper/cfgUtils.py:96
msgid "Decode Error:"
msgstr ""
#: octoprint_klipper/cfgUtils.py:100
msgid "Please convert your config files to utf-8!"
msgstr ""
#: octoprint_klipper/cfgUtils.py:102
msgid ""
"Or you can also paste your config \n"
"into the Editor and save it."
msgstr ""
#: octoprint_klipper/cfgUtils.py:109
msgid "File not found!"
msgstr ""
#: octoprint_klipper/static/js/klipper.js:172
#: octoprint_klipper/templates/klipper_sidebar.jinja2:11
msgid "Go to OctoKlipper Tab"
msgstr ""
#: octoprint_klipper/static/js/klipper.js:290
msgid "Restarted Klipper"
msgstr ""
#: octoprint_klipper/static/js/klipper.js:298
msgid "All ongoing Prints will be stopped!"
msgstr ""
#: octoprint_klipper/static/js/klipper.js:303
msgid "Restart Klipper?"
msgstr ""
#: octoprint_klipper/static/js/klipper.js:305
#: octoprint_klipper/templates/klipper_tab_main.jinja2:39
msgid "Restart"
msgstr ""
#: octoprint_klipper/static/js/klipper.js:305
msgid "Restart and don't ask this again."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:110
#: octoprint_klipper/static/js/klipper_settings.js:119
#, python-format
msgid ""
"Failed to remove config %(name)s.</p><p>Please consult octoprint.log for "
"details.</p>"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:113
#: octoprint_klipper/static/js/klipper_settings.js:122
msgid "Could not remove config"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:122
#, python-format
msgid "You are about to delete backed config file \"%(name)s\"."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:138
msgid "This will overwrite any file with the same name on the configpath."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:141
msgid "Are you sure you want to restore now?"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:143
#: octoprint_klipper/static/js/klipper_editor.js:158
msgid "Proceed"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:168
#, python-format
msgid "You are about to restore %(count)d backed config files."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:183
#, python-format
msgid "You are about to delete %(count)d backed config files."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:193
msgid "Restoring klipper config files"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:195
#, python-format
msgid "Restoring %(count)d backed config files..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:204
#, python-format
msgid "Restored %(filename)s..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:215
#, python-format
msgid "Restoring of %(filename)s failed, continuing..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:248
msgid "Deleting backup files"
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:249
#, python-format
msgid "Deleting %(count)d backed files..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:257
#: octoprint_klipper/static/js/klipper_settings.js:178
#, python-format
msgid "Deleted %(filename)s..."
msgstr ""
#: octoprint_klipper/static/js/klipper_backup.js:263
#: octoprint_klipper/static/js/klipper_settings.js:188
#, python-format
msgid "Deleting of %(filename)s failed, continuing..."
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:68
msgid "Closing without saving"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:69
msgid "Your file seems to have changed."
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:71
msgid "Do you really want to close it?"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:72
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:27
msgid "Close"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:72
msgid "Do not close"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:72
#: octoprint_klipper/templates/klipper_editor.jinja2:38
msgid "Save & Close"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:153
msgid "Reload Configfile after SAVE_CONFIG?"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:156
msgid "Externally changed config"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:168
msgid "Your configuration seems to be faulty."
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:172
msgid "Save faulty Configuration?"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:174
msgid "Do not save!"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:175
msgid "Save anyway!"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:175
msgid "Save anyway and don't ask this again."
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:199
msgid "SyntaxCheck"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:199
msgid "SyntaxCheck OK"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:231
msgid "Faulty config not saved!"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:233
#: octoprint_klipper/static/js/klipper_editor.js:255
#: octoprint_klipper/static/js/klipper_editor.js:372
#: octoprint_klipper/static/js/klipper_editor.js:384
#: octoprint_klipper/templates/klipper_editor.jinja2:40
msgid "Save Config"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:253
#: octoprint_klipper/static/js/klipper_editor.js:329
msgid "No filename set"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:305
#: octoprint_klipper/static/js/klipper_editor.js:323
#: octoprint_klipper/static/js/klipper_editor.js:331
msgid "Reload File"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:309
msgid "Reload Config"
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:309
msgid "File reloaded."
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:372
msgid "File saved."
msgstr ""
#: octoprint_klipper/static/js/klipper_editor.js:382
msgid "File not saved!"
msgstr ""
#: octoprint_klipper/static/js/klipper_settings.js:88
msgid "Path: "
msgstr ""
#: octoprint_klipper/static/js/klipper_settings.js:131
#, python-format
msgid "You are about to delete config file \"%(name)s\"."
msgstr ""
#: octoprint_klipper/static/js/klipper_settings.js:158
#, python-format
msgid "You are about to delete %(count)d config files."
msgstr ""
#: octoprint_klipper/static/js/klipper_settings.js:168
msgid "Deleting config files"
msgstr ""
#: octoprint_klipper/static/js/klipper_settings.js:169
#, python-format
msgid "Deleting %(count)d config files..."
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:5
msgid "Backups"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:17
#: octoprint_klipper/templates/klipper_settings.jinja2:253
msgid "Select all on this page"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:20
#: octoprint_klipper/templates/klipper_settings.jinja2:254
msgid "Select all"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:25
#: octoprint_klipper/templates/klipper_settings.jinja2:256
msgid "Clear selection"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:265
msgid "Refresh file list"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:30
#: octoprint_klipper/templates/klipper_settings.jinja2:266
msgid "Refresh Files"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:34
msgid "Restore selected"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:36
#: octoprint_klipper/templates/klipper_settings.jinja2:269
msgid "Delete selected"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:277
msgid "Sort by name"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:47
#: octoprint_klipper/templates/klipper_settings.jinja2:278
msgid "ascending"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:50
#: octoprint_klipper/templates/klipper_settings.jinja2:280
msgid "Sort by date"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:51
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:55
#: octoprint_klipper/templates/klipper_settings.jinja2:281
#: octoprint_klipper/templates/klipper_settings.jinja2:284
msgid "descending"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:54
#: octoprint_klipper/templates/klipper_settings.jinja2:283
msgid "Sort by file size"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:65
#: octoprint_klipper/templates/klipper_settings.jinja2:116
#: octoprint_klipper/templates/klipper_settings.jinja2:295
msgid "Name"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:66
#: octoprint_klipper/templates/klipper_settings.jinja2:296
msgid "Size"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:67
#: octoprint_klipper/templates/klipper_settings.jinja2:297
msgid "Action"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:78
#: octoprint_klipper/templates/klipper_settings.jinja2:309
msgid "Delete"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:81
msgid "Restore"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:84
#: octoprint_klipper/templates/klipper_settings.jinja2:312
msgid "Download"
msgstr ""
#: octoprint_klipper/templates/klipper_backups_dialog.jinja2:87
msgid "Preview"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:6
msgid "Decrease Fontsize"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:9
msgid "Increase Fontsize"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:28
msgid "Filename"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:31
#: octoprint_klipper/templates/klipper_editor.jinja2:32
msgid "Reload from file"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:34
#: octoprint_klipper/templates/klipper_editor.jinja2:35
msgid "Check Syntax"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:37
msgid "Save Config and Close"
msgstr ""
#: octoprint_klipper/templates/klipper_editor.jinja2:41
msgid "Save"
msgstr ""
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:4
msgid "Performance Graph"
msgstr ""
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:9
msgid "Click labels to hide/show dataset"
msgstr ""
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:15
msgid "Fill Datasets"
msgstr ""
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:22
msgid "Select"
msgstr ""
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:26
msgid "Analyze Log"
msgstr ""
#: octoprint_klipper/templates/klipper_graph_dialog.jinja2:35
msgid "Depending on the size of the log file this might take a while."
msgstr ""
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:55
msgid "Assisted Bed Leveling"
msgstr ""
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:14
msgid "Home"
msgstr ""
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:42
msgid "Start"
msgstr ""
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:43
msgid "Previous"
msgstr ""
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:44
msgid "Next"
msgstr ""
#: octoprint_klipper/templates/klipper_leveling_dialog.jinja2:45
msgid "Stop"
msgstr ""
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:63
msgid "Coordinate Offset"
msgstr ""
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:8
msgid "Set an offset for all future GCODE move commands in mm."
msgstr ""
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:26
msgid "Add to existing offset"
msgstr ""
#: octoprint_klipper/templates/klipper_offset_dialog.jinja2:31
msgid "Set Offset"
msgstr ""
#: octoprint_klipper/templates/klipper_param_macro_dialog.jinja2:4
msgid "Run - "
msgstr ""
#: octoprint_klipper/templates/klipper_param_macro_dialog.jinja2:27
msgid "OK"
msgstr ""
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:4
#: octoprint_klipper/templates/klipper_tab_main.jinja2:59
msgid "PID Tuning"
msgstr ""
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:8
msgid "Heater / Extruder Name (from config file)"
msgstr ""
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:12
msgid "name"
msgstr ""
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:17
msgid "Target Temperature"
msgstr ""
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:25
msgid "The result of the tuning cycle is reported in the message log."
msgstr ""
#: octoprint_klipper/templates/klipper_pid_tuning_dialog.jinja2:28
msgid "Start Tuning"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:3
msgid "Basic"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:4
#: octoprint_klipper/templates/klipper_sidebar.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:72
msgid "Macros"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:5
msgid "Bed Leveling"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:6
msgid "Klipper Configuration"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:12
msgid "Serial Port"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:18
#: octoprint_klipper/templates/klipper_settings.jinja2:20
msgid "Replace Connection Panel"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:25
msgid "Show Short Messages"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:27
#: octoprint_klipper/templates/klipper_settings.jinja2:28
msgid "on NavBar"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:29
#: octoprint_klipper/templates/klipper_settings.jinja2:30
msgid "on SideBar"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:34
#: octoprint_klipper/templates/klipper_settings.jinja2:36
msgid "Enable debug logging"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:41
msgid "Config Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:43
#: octoprint_klipper/templates/klipper_settings.jinja2:44
msgid "Check parsing on save"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:46
#: octoprint_klipper/templates/klipper_settings.jinja2:48
msgid "Fontsize"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:53
msgid "Klipper Config Directory"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:59
msgid ""
"The filename of the base config that Klipper is loading. (default is "
"printer.cfg)"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:59
msgid "Klipper Base Config Filename"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:65
msgid "Klipper Log File"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:71
msgid "Configuration Restart Command"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:78
msgid "The command that is executed if you want to restart klipper."
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:80
msgid "Restart klipper on editor save?"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:81
msgid "Restart Klipper on editor save?"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:83
msgid "Show Confirmation before restarting Klipper?"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:84
msgid "Confirmation before restarting Klipper?"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:89
msgid "Config Backup"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:91
#: octoprint_klipper/templates/klipper_settings.jinja2:92
msgid "Show Backups"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:101
msgid "These macros are only meant to be used in OctoPrint."
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:102
msgid "They are not the ones that can be defined in the printer.cfg."
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:108
msgid "Add macro button to:"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:109
#: octoprint_klipper/templates/klipper_settings.jinja2:123
msgid "Klipper Tab"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:110
#: octoprint_klipper/templates/klipper_settings.jinja2:126
msgid "Sidebar"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:135
msgid "Command"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:148
msgid "Add Macro"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:153
msgid ""
"To show a dialog that asks for parameters you can write your macro like "
"in the following example:"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:169
msgid ""
"This feature assists in manually leveling your print bed by moving the "
"head to the defined points in sequence.<br />If you use a piece of paper "
"for leveling, set \"Probe Height\" to the paper thickness eg. \"0.1\"."
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:173
msgid "Probe Height"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:179
msgid "Z-height to probe at"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:183
msgid "Probe Lift"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:189
msgid "Lift Head by this amount before moving."
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:193
msgid "Probe Feedrate Z"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:202
msgid "Feedrate X/Y"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:211
msgid "Probe Points"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:238
msgid "Add Point"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:245
msgid "Config Files"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:259
msgid "Add new File"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:260
msgid "New File"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:262
msgid "Open last config"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:263
msgid "Open last Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:289
msgid "Path to the config files."
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:315
msgid "Edit"
msgstr ""
#: octoprint_klipper/templates/klipper_settings.jinja2:344
msgid "Donate"
msgstr ""
#: octoprint_klipper/templates/klipper_sidebar.jinja2:3
msgid "Printer Profile"
msgstr ""
#: octoprint_klipper/templates/klipper_sidebar.jinja2:5
msgid "Connect"
msgstr ""
#: octoprint_klipper/templates/klipper_sidebar.jinja2:6
msgid "Open Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:3
msgid "Messages"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:6
msgid "Open the OctoKlipper Settings"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:16
#: octoprint_klipper/templates/klipper_tab_main.jinja2:17
msgid "Clear Log"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:26
msgid "Query Klipper for its current status"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:27
msgid "Get Status"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:31
msgid "Show the Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:33
msgid "Show Editor"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:41
msgid ""
"This will cause the host software to reload its config and perform an "
"internal reset"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:42
msgid "Host"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:45
msgid ""
"Similar to a host restart, but also clears any error state from the "
"micro-controller"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:46
msgid "Firmware"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:52
msgid "Tools"
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:54
msgid ""
"Assists in manually leveling your printbed by moving the head to a "
"configurable set of positions in sequence."
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:58
msgid "Determines optimal PID parameters by heat cycling the hotend/bed."
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:62
msgid "Sets a offset for subsequent GCODE coordinates."
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:66
msgid ""
"Assists in debugging performance issues by analyzing the Klipper log "
"files."
msgstr ""
#: octoprint_klipper/templates/klipper_tab_main.jinja2:67
msgid "Analyze Klipper Log"
msgstr ""