├── .github ├── FUNDING.yml └── workflows │ ├── create_tagged_release_with_packages.yaml │ ├── package.json │ └── translation.yaml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── DiscoverOctoPrintAction.py ├── LICENSE ├── NetworkMJPGImage.py ├── NetworkReplyTimeout.py ├── OctoPrintOutputController.py ├── OctoPrintOutputDevice.py ├── OctoPrintOutputDevicePlugin.py ├── PowerPlugins.py ├── README.md ├── UploadOptions.py ├── WebcamsModel.py ├── __init__.py ├── i18n ├── cs_CZ │ ├── LC_MESSAGES │ │ └── octoprint.mo │ └── octoprint.po ├── de_DE │ ├── LC_MESSAGES │ │ └── octoprint.mo │ └── octoprint.po ├── octoprint.pot └── ru_RU │ ├── LC_MESSAGES │ └── octoprint.mo │ └── octoprint.po ├── plugin.json ├── qml ├── DiscoverOctoPrintAction.qml ├── ManualInstanceDialog.qml ├── MonitorItem.qml ├── OctoPrintComponents.qml └── UploadOptions.qml └── qml_qt5 ├── DiscoverOctoPrintAction.qml ├── ManualInstanceDialog.qml ├── MonitorItem3x.qml ├── MonitorItem4x.qml ├── OctoPrintComponents.qml └── UploadOptions.qml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: fieldofview 2 | custom: "https://www.paypal.me/fieldofview" 3 | -------------------------------------------------------------------------------- /.github/workflows/create_tagged_release_with_packages.yaml: -------------------------------------------------------------------------------- 1 | name: "Cura-plugin release" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | create-curapackages: 10 | name: "Tagged Release" 11 | runs-on: "ubuntu-latest" 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | path: "build" 17 | submodules: "recursive" 18 | - uses: fieldOfView/cura-plugin-packager-action@main 19 | with: 20 | source_folder: "build" 21 | package_info_path: "build/.github/workflows/package.json" 22 | - uses: "marvinpinto/action-automatic-releases@latest" 23 | with: 24 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 25 | prerelease: false 26 | files: | 27 | *.curapackage 28 | -------------------------------------------------------------------------------- /.github/workflows/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "author_id": "fieldofview", 4 | "display_name": "fieldOfView", 5 | "email": "aldo@fieldofview.com", 6 | "website": "http://fieldofview.com" 7 | }, 8 | "description": "Enables networked printing and monitoring with OctoPrint.\n\nOctoPrint is a registered trademark. For more information about OctoPrint, see octoprint.org\n\nThe development of this plugin can be sponsored via Github Sponsors (https://github.com/sponsors/fieldofview) or Paypal (https://www.paypal.me/fieldofview)", 9 | "display_name": "OctoPrint Connection", 10 | "package_id": "OctoPrintPlugin", 11 | "package_type": "plugin", 12 | "package_version": "0.0.0", 13 | "sdk_version": 0, 14 | "sdk_version_semver": "0.0.0", 15 | "website": "https://github.com/fieldOfView/Cura-OctoPrintPlugin" 16 | } -------------------------------------------------------------------------------- /.github/workflows/translation.yaml: -------------------------------------------------------------------------------- 1 | name: "Auto translation with the action" 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'release' 7 | - '**.**' 8 | 9 | jobs: 10 | update_translation: 11 | name: "Update plugin translation" 12 | runs-on: "ubuntu-latest" 13 | 14 | steps: 15 | - name: "Translation" 16 | id: translation 17 | uses: Elkin-Vasily/cura-plugin-translation@v1 18 | with: 19 | translation_folder: 'i18n' 20 | translation_name: 'octoprint' 21 | plugin_name: 'OctoPrint Connection Plugin' 22 | 23 | - if: ${{ steps.translation.outputs.template_updated || steps.translation.outputs.locales_updated }} 24 | name: 'Create Pull Request' 25 | uses: peter-evans/create-pull-request@v4 26 | with: 27 | branch: ${{github.ref_name}}-translation 28 | delete-branch: true 29 | title: '[CI] Translation update' 30 | body: 'Translation was automatically updated' 31 | commit-message: '[CI] Translation update' 32 | labels: 'translation' 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.qmlc 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | 58 | #Ipython Notebook 59 | .ipynb_checkpoints 60 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "python-zeroconf"] 2 | path = python-zeroconf 3 | url = https://github.com/jstasiak/python-zeroconf 4 | [submodule "ifaddr"] 5 | path = ifaddr 6 | url = https://github.com/pydron/ifaddr 7 | [submodule "async-timeout"] 8 | path = async-timeout 9 | url = https://github.com/aio-libs/async-timeout 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(OctoPrintPlugin NONE) 2 | cmake_minimum_required(VERSION 2.8.12) 3 | 4 | install(FILES 5 | __init__.py 6 | plugin.json 7 | DiscoverOctoPrintAction.py 8 | OctoPrintOutputDevicePlugin.py 9 | OctoPrintOutputDevice.py 10 | OctoPrintOutputController.py 11 | NetworkReplyTimeout.py 12 | NetworkMJPGImage.py 13 | PowerPlugins.py 14 | UploadOptions.py 15 | WebcamsModel.py 16 | LICENSE 17 | README.md 18 | DESTINATION lib/cura/plugins/OctoPrintPlugin 19 | ) 20 | 21 | install(DIRECTORY qml 22 | DESTINATION lib/cura/plugins/OctoPrintPlugin/qml 23 | ) 24 | 25 | install(DIRECTORY qml_qt5 26 | DESTINATION lib/cura/plugins/OctoPrintPlugin/qml_qt5 27 | ) 28 | 29 | install(DIRECTORY python-zeroconf 30 | DESTINATION lib/cura/plugins/OctoPrintPlugin/python-zeroconf 31 | ) 32 | 33 | install(DIRECTORY ifaddr 34 | DESTINATION lib/cura/plugins/OctoPrintPlugin/ifaddr 35 | ) 36 | 37 | install(DIRECTORY async-timeout 38 | DESTINATION lib/cura/plugins/OctoPrintPlugin/async-timeout 39 | ) 40 | 41 | install(DIRECTORY i18n 42 | DESTINATION lib/cura/plugins/OctoPrintPlugin/i18n 43 | ) 44 | -------------------------------------------------------------------------------- /NetworkMJPGImage.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | # NetworkMJPGImage is released under the terms of the LGPLv3 or higher. 3 | 4 | try: 5 | from cura.ApplicationMetadata import CuraSDKVersion 6 | except ImportError: # Cura <= 3.6 7 | CuraSDKVersion = "6.0.0" 8 | if CuraSDKVersion >= "8.0.0": 9 | from PyQt6.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray 10 | from PyQt6.QtGui import QImage, QPainter 11 | from PyQt6.QtQuick import QQuickPaintedItem 12 | from PyQt6.QtNetwork import ( 13 | QNetworkRequest, 14 | QNetworkReply, 15 | QNetworkAccessManager, 16 | QSslConfiguration, 17 | QSslSocket, 18 | ) 19 | QNetworkRequestAttributes = QNetworkRequest.Attribute 20 | QSslSocketPeerVerifyModes = QSslSocket.PeerVerifyMode 21 | else: 22 | from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray 23 | from PyQt5.QtGui import QImage, QPainter 24 | from PyQt5.QtQuick import QQuickPaintedItem 25 | from PyQt5.QtNetwork import ( 26 | QNetworkRequest, 27 | QNetworkReply, 28 | QNetworkAccessManager, 29 | QSslConfiguration, 30 | QSslSocket, 31 | ) 32 | QNetworkRequestAttributes = QNetworkRequest.Attribute 33 | QSslSocketPeerVerifyModes = QSslSocket 34 | 35 | from UM.Logger import Logger 36 | 37 | import base64 38 | 39 | # 40 | # A QQuickPaintedItem that progressively downloads a network mjpeg stream, 41 | # picks it apart in individual jpeg frames, and paints it. 42 | # 43 | class NetworkMJPGImage(QQuickPaintedItem): 44 | def __init__(self, *args, **kwargs) -> None: 45 | super().__init__(*args, **kwargs) 46 | 47 | self._stream_buffer = QByteArray() 48 | self._stream_buffer_start_index = -1 49 | self._network_manager = None # type: QNetworkAccessManager 50 | self._image_request = None # type: QNetworkRequest 51 | self._image_reply = None # type: QNetworkReply 52 | self._image = QImage() 53 | self._image_rect = QRect() 54 | 55 | self._source_url = QUrl() 56 | self._started = False 57 | 58 | self._mirror = False 59 | 60 | self.setAntialiasing(True) 61 | 62 | ## Ensure that close gets called when object is destroyed 63 | def __del__(self) -> None: 64 | self.stop() 65 | 66 | def paint(self, painter: "QPainter") -> None: 67 | if self._mirror: 68 | painter.drawImage(self.contentsBoundingRect(), self._image.mirrored()) 69 | return 70 | 71 | painter.drawImage(self.contentsBoundingRect(), self._image) 72 | 73 | def setSourceURL(self, source_url: "QUrl") -> None: 74 | self._source_url = source_url 75 | self.sourceURLChanged.emit() 76 | if self._started: 77 | self.start() 78 | 79 | def getSourceURL(self) -> "QUrl": 80 | return self._source_url 81 | 82 | sourceURLChanged = pyqtSignal() 83 | source = pyqtProperty( 84 | QUrl, fget=getSourceURL, fset=setSourceURL, notify=sourceURLChanged 85 | ) 86 | 87 | def setMirror(self, mirror: bool) -> None: 88 | if mirror == self._mirror: 89 | return 90 | self._mirror = mirror 91 | self.mirrorChanged.emit() 92 | self.update() 93 | 94 | def getMirror(self) -> bool: 95 | return self._mirror 96 | 97 | mirrorChanged = pyqtSignal() 98 | mirror = pyqtProperty(bool, fget=getMirror, fset=setMirror, notify=mirrorChanged) 99 | 100 | imageSizeChanged = pyqtSignal() 101 | 102 | @pyqtProperty(int, notify=imageSizeChanged) 103 | def imageWidth(self) -> int: 104 | return self._image.width() 105 | 106 | @pyqtProperty(int, notify=imageSizeChanged) 107 | def imageHeight(self) -> int: 108 | return self._image.height() 109 | 110 | @pyqtSlot() 111 | def start(self) -> None: 112 | self.stop() # Ensure that previous requests (if any) are stopped. 113 | 114 | if not self._source_url: 115 | Logger.log("w", "Unable to start camera stream without target!") 116 | return 117 | 118 | auth_data = "" 119 | if self._source_url.userInfo(): 120 | # move auth data to basic authorization header 121 | auth_data = base64.b64encode(self._source_url.userInfo().encode()).decode( 122 | "utf-8" 123 | ) 124 | authority = self._source_url.authority() 125 | self._source_url.setAuthority(authority.rsplit("@", 1)[1]) 126 | 127 | self._image_request = QNetworkRequest(self._source_url) 128 | try: 129 | self._image_request.setAttribute(QNetworkRequestAttributes.FollowRedirectsAttribute, True) 130 | except AttributeError: 131 | # in Qt6, this is no longer possible (or required), see https://doc.qt.io/qt-6/network-changes-qt6.html#redirect-policies 132 | pass 133 | 134 | if auth_data: 135 | self._image_request.setRawHeader( 136 | b"Authorization", ("basic %s" % auth_data).encode() 137 | ) 138 | 139 | if self._source_url.scheme().lower() == "https": 140 | # ignore SSL errors (eg for self-signed certificates) 141 | ssl_configuration = QSslConfiguration.defaultConfiguration() 142 | ssl_configuration.setPeerVerifyMode(QSslSocketPeerVerifyModes.VerifyNone) 143 | self._image_request.setSslConfiguration(ssl_configuration) 144 | 145 | if self._network_manager is None: 146 | self._network_manager = QNetworkAccessManager() 147 | 148 | self._image_reply = self._network_manager.get(self._image_request) 149 | self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress) 150 | 151 | self._started = True 152 | 153 | @pyqtSlot() 154 | def stop(self) -> None: 155 | self._stream_buffer = QByteArray() 156 | self._stream_buffer_start_index = -1 157 | 158 | if self._image_reply: 159 | try: 160 | try: 161 | self._image_reply.downloadProgress.disconnect( 162 | self._onStreamDownloadProgress 163 | ) 164 | except Exception: 165 | pass 166 | 167 | if not self._image_reply.isFinished(): 168 | self._image_reply.close() 169 | except Exception as e: # RuntimeError 170 | pass # It can happen that the wrapped c++ object is already deleted. 171 | 172 | self._image_reply = None 173 | self._image_request = None 174 | 175 | self._network_manager = None 176 | 177 | self._started = False 178 | 179 | def _onStreamDownloadProgress(self, bytes_received: int, bytes_total: int) -> None: 180 | # An MJPG stream is (for our purpose) a stream of concatenated JPG images. 181 | # JPG images start with the marker 0xFFD8, and end with 0xFFD9 182 | if self._image_reply is None: 183 | return 184 | self._stream_buffer += self._image_reply.readAll() 185 | 186 | if ( 187 | len(self._stream_buffer) > 5000000 188 | ): # No single camera frame should be 5 MB or larger 189 | Logger.log( 190 | "w", "MJPEG buffer exceeds reasonable size. Restarting stream..." 191 | ) 192 | self.stop() # resets stream buffer and start index 193 | self.start() 194 | return 195 | 196 | if self._stream_buffer_start_index == -1: 197 | self._stream_buffer_start_index = self._stream_buffer.indexOf(b"\xff\xd8") 198 | stream_buffer_end_index = self._stream_buffer.lastIndexOf(b"\xff\xd9") 199 | # If this happens to be more than a single frame, then so be it; the JPG decoder will 200 | # ignore the extra data. We do it like this in order not to get a buildup of frames 201 | 202 | if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1: 203 | jpg_data = self._stream_buffer[ 204 | self._stream_buffer_start_index : stream_buffer_end_index + 2 205 | ] 206 | self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2 :] 207 | self._stream_buffer_start_index = -1 208 | self._image.loadFromData(jpg_data) 209 | 210 | if self._image.rect() != self._image_rect: 211 | self.imageSizeChanged.emit() 212 | 213 | self.update() 214 | -------------------------------------------------------------------------------- /NetworkReplyTimeout.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | # NetworkReplyTimeout is released under the terms of the AGPLv3 or higher. 3 | 4 | try: 5 | from cura.ApplicationMetadata import CuraSDKVersion 6 | except ImportError: # Cura <= 3.6 7 | CuraSDKVersion = "6.0.0" 8 | if CuraSDKVersion >= "8.0.0": 9 | from PyQt6.QtCore import QObject, QTimer 10 | from PyQt6.QtNetwork import QNetworkReply 11 | else: 12 | from PyQt5.QtCore import QObject, QTimer 13 | from PyQt5.QtNetwork import QNetworkReply 14 | 15 | from UM.Signal import Signal 16 | 17 | from typing import Optional, Callable 18 | 19 | # 20 | # A timer that is started when a QNetworkRequest returns a QNetworkReply, which closes the 21 | # QNetworkReply does not reply in a timely manner 22 | # 23 | class NetworkReplyTimeout(QObject): 24 | timeout = Signal() 25 | 26 | def __init__( 27 | self, 28 | reply: QNetworkReply, 29 | timeout: int, 30 | callback: Optional[Callable[[QNetworkReply], None]] = None, 31 | ) -> None: 32 | super().__init__() 33 | 34 | self._reply = reply 35 | self._callback = callback 36 | 37 | self._timer = QTimer() 38 | self._timer.setInterval(timeout) 39 | self._timer.setSingleShot(True) 40 | self._timer.timeout.connect(self._onTimeout) 41 | 42 | self._timer.start() 43 | 44 | def _onTimeout(self): 45 | if self._reply.isRunning(): 46 | self._reply.abort() 47 | if self._callback: 48 | self._callback(self._reply) 49 | self.timeout.emit(self._reply) 50 | -------------------------------------------------------------------------------- /OctoPrintOutputController.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | # OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | from cura.PrinterOutput.GenericOutputController import GenericOutputController 5 | 6 | try: 7 | # Cura 4.1 and newer 8 | from cura.PrinterOutput.PrinterOutputDevice import ( 9 | PrinterOutputDevice, 10 | ConnectionState, 11 | ) 12 | from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel 13 | except ImportError: 14 | # Cura 3.5 - Cura 4.0 15 | from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState 16 | from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel 17 | 18 | 19 | class OctoPrintOutputController(GenericOutputController): 20 | def __init__(self, output_device: "PrinterOutputDevice") -> None: 21 | super().__init__(output_device) 22 | 23 | def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None: 24 | axis_information = self._output_device.getAxisInformation() 25 | if axis_information["x"].inverted: 26 | x = -x 27 | if axis_information["y"].inverted: 28 | y = -y 29 | if axis_information["z"].inverted: 30 | z = -z 31 | 32 | self._output_device.sendCommand("G91") 33 | self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed)) 34 | self._output_device.sendCommand("G90") 35 | -------------------------------------------------------------------------------- /OctoPrintOutputDevicePlugin.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | # OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin 5 | from .OctoPrintOutputDevice import OctoPrintOutputDevice 6 | 7 | from UM.Signal import Signal, signalemitter 8 | from UM.Application import Application 9 | from UM.Logger import Logger 10 | from UM.Util import parseBool 11 | from UM.Settings.ContainerStack import ContainerStack 12 | 13 | try: 14 | from cura.ApplicationMetadata import CuraSDKVersion 15 | except ImportError: # Cura <= 3.6 16 | CuraSDKVersion = "6.0.0" 17 | if CuraSDKVersion >= "8.0.0": 18 | from PyQt6.QtCore import QTimer 19 | else: 20 | from PyQt5.QtCore import QTimer 21 | 22 | import time 23 | import json 24 | import re 25 | import base64 26 | import os.path 27 | import ipaddress 28 | 29 | from typing import Any, Dict, List, Union, Optional, TYPE_CHECKING 30 | 31 | try: 32 | ìmport_exceptions = (SyntaxError, FileNotFoundError, ModuleNotFoundError, ImportError) 33 | except NameError: 34 | # Python 3.5 does not know the ModuleNotFoundError 35 | ìmport_exceptions = (SyntaxError, FileNotFoundError, ImportError) 36 | 37 | if TYPE_CHECKING: 38 | # for MYPY, fall back to the system-installed version 39 | from zeroconf import ( 40 | Zeroconf, 41 | ServiceBrowser, 42 | ServiceStateChange, 43 | ServiceInfo, 44 | DNSAddress, 45 | ) 46 | else: 47 | try: 48 | # import the included version of python-zeroconf 49 | # expand search path so local copies of zeroconf, ifaddr and async-timeout can be imported 50 | import sys 51 | import importlib.util 52 | 53 | original_path = list(sys.path) 54 | original_zeroconf_module = None 55 | 56 | if "zeroconf" in sys.modules: 57 | Logger.log( 58 | "d", 59 | "The zeroconf module is already imported; flush it to use a newer version", 60 | ) 61 | original_zeroconf_module = sys.modules.pop("zeroconf") 62 | 63 | plugin_path = os.path.dirname(os.path.abspath(__file__)) 64 | sys.path.insert(0, os.path.join(plugin_path, "ifaddr")) 65 | sys.path.insert(0, os.path.join(plugin_path, "async-timeout")) 66 | 67 | zeroconf_spec = importlib.util.spec_from_file_location( 68 | "zeroconf", 69 | os.path.join(plugin_path, "python-zeroconf", "src", "zeroconf", "__init__.py"), 70 | ) 71 | zeroconf_module = importlib.util.module_from_spec(zeroconf_spec) 72 | sys.modules["zeroconf"] = zeroconf_module 73 | 74 | zeroconf_spec.loader.exec_module( 75 | zeroconf_module 76 | ) # must be called after adding zeroconf to sys.modules 77 | 78 | from zeroconf import ( 79 | Zeroconf, 80 | ServiceBrowser, 81 | ServiceStateChange, 82 | ServiceInfo, 83 | DNSAddress, 84 | __version__ as zeroconf_version, 85 | ) 86 | 87 | # restore original path 88 | sys.path = original_path 89 | 90 | Logger.log("d", "Using included Zeroconf module version %s" % zeroconf_version) 91 | except ìmport_exceptions as exception: 92 | # fall back to the system-installed version, or what comes with Cura 93 | Logger.logException("e", "Failed to load included version of Zeroconf module") 94 | 95 | # restore original path 96 | sys.path = original_path 97 | if original_zeroconf_module: 98 | sys.modules["zeroconf"] = original_zeroconf_module 99 | 100 | try: 101 | from zeroconf import ( 102 | Zeroconf, 103 | ServiceBrowser, 104 | ServiceStateChange, 105 | ServiceInfo, 106 | DNSAddress, 107 | __version__ as zeroconf_version, 108 | ) 109 | 110 | Logger.log( 111 | "w", "Falling back to default Zeroconf module version %s" % zeroconf_version 112 | ) 113 | except ImportError: 114 | Zeroconf = None 115 | ServiceBrowser = None 116 | ServiceStateChange = None 117 | ServiceInfo = None 118 | DNSAddress = None 119 | 120 | Logger.log( 121 | "w", "Zeroconf could not be loaded; Auto-discovery is not available" 122 | ) 123 | 124 | 125 | if TYPE_CHECKING: 126 | from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel 127 | 128 | ## This plugin handles the connection detection & creation of output device objects for OctoPrint-connected printers. 129 | # Zero-Conf is used to detect printers, which are saved in a dict. 130 | # If we discover an instance that has the same key as the active machine instance a connection is made. 131 | @signalemitter 132 | class OctoPrintOutputDevicePlugin(OutputDevicePlugin): 133 | def __init__(self) -> None: 134 | super().__init__() 135 | self._zeroconf = None # type: Optional[Zeroconf] 136 | self._browser = None # type: Optional[ServiceBrowser] 137 | self._instances = {} # type: Dict[str, OctoPrintOutputDevice] 138 | 139 | # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. 140 | self.addInstanceSignal.connect(self.addInstance) 141 | self.removeInstanceSignal.connect(self.removeInstance) 142 | Application.getInstance().globalContainerStackChanged.connect( 143 | self.reCheckConnections 144 | ) 145 | 146 | # Load custom instances from preferences 147 | self._preferences = Application.getInstance().getPreferences() 148 | self._preferences.addPreference("octoprint/manual_instances", "{}") 149 | 150 | self._preferences.addPreference("octoprint/use_zeroconf", True) 151 | 152 | try: 153 | self._manual_instances = json.loads( 154 | self._preferences.getValue("octoprint/manual_instances") 155 | ) 156 | except ValueError: 157 | self._manual_instances = {} # type: Dict[str, Any] 158 | if not isinstance(self._manual_instances, dict): 159 | self._manual_instances = {} # type: Dict[str, Any] 160 | 161 | self._name_regex = re.compile(r"OctoPrint instance (\".*\"\.|on )(.*)\.") 162 | 163 | self._keep_alive_timer = QTimer() 164 | self._keep_alive_timer.setInterval(2000) 165 | self._keep_alive_timer.setSingleShot(True) 166 | self._keep_alive_timer.timeout.connect(self._keepDiscoveryAlive) 167 | self._consecutive_zeroconf_restarts = 0 168 | 169 | addInstanceSignal = Signal() 170 | removeInstanceSignal = Signal() 171 | instanceListChanged = Signal() 172 | 173 | ## Start looking for devices on network. 174 | def start(self) -> None: 175 | self.startDiscovery() 176 | 177 | def startDiscovery(self) -> None: 178 | # Clean up previous discovery components and results 179 | if self._zeroconf: 180 | self._zeroconf.close() 181 | self._zeroconf = None # type: Optional[Zeroconf] 182 | 183 | if self._browser: 184 | self._browser.cancel() 185 | self._browser = None # type: Optional[ServiceBrowser] 186 | self._printers = [] # type: List[PrinterOutputModel] 187 | 188 | instance_keys = list(self._instances.keys()) 189 | for key in instance_keys: 190 | self.removeInstance(key) 191 | 192 | # Add manual instances from preference 193 | for name, properties in self._manual_instances.items(): 194 | additional_properties = { 195 | b"path": properties["path"].encode("utf-8"), 196 | b"useHttps": b"true" if properties.get("useHttps", False) else b"false", 197 | b"userName": properties.get("userName", "").encode("utf-8"), 198 | b"password": properties.get("password", "").encode("utf-8"), 199 | b"manual": b"true", 200 | } # These additional properties use bytearrays to mimick the output of zeroconf 201 | self.addInstance( 202 | name, properties["address"], properties["port"], additional_properties 203 | ) 204 | 205 | self.instanceListChanged.emit() 206 | 207 | # Don't start zeroconf discovery if it is disabled 208 | if not self._preferences.getValue("octoprint/use_zeroconf"): 209 | self._keep_alive_timer.stop() 210 | return 211 | 212 | try: 213 | self._zeroconf = Zeroconf() 214 | except Exception: 215 | self._zeroconf = None # type: Optional[Zeroconf] 216 | self._keep_alive_timer.stop() 217 | Logger.logException( 218 | "e", "Failed to create Zeroconf instance. Auto-discovery will not work." 219 | ) 220 | 221 | if self._zeroconf: 222 | self._browser = ServiceBrowser( 223 | self._zeroconf, "_octoprint._tcp.local.", [self._onServiceChanged] 224 | ) 225 | if self._browser and self._browser.is_alive(): 226 | self._keep_alive_timer.start() 227 | else: 228 | Logger.log( 229 | "w", 230 | "Failed to create Zeroconf browser. Auto-discovery will not work.", 231 | ) 232 | self._keep_alive_timer.stop() 233 | 234 | def _keepDiscoveryAlive(self) -> None: 235 | if not self._browser or not self._browser.is_alive(): 236 | if self._consecutive_zeroconf_restarts < 5: 237 | Logger.log( 238 | "w", 239 | "Zeroconf discovery has died, restarting discovery of OctoPrint instances.", 240 | ) 241 | self._consecutive_zeroconf_restarts += 1 242 | self.startDiscovery() 243 | else: 244 | if self._zeroconf: 245 | self._zeroconf.close() 246 | self._zeroconf = None # type: Optional[Zeroconf] 247 | Logger.log( 248 | "e", 249 | "Giving up restarting Zeroconf browser after 5 consecutive attempts. Auto-discovery will not work.", 250 | ) 251 | else: 252 | # ZeroConf has been alive and well for the past 2 seconds 253 | self._consecutive_zeroconf_restarts = 0 254 | self._keep_alive_timer.start() 255 | 256 | def addManualInstance( 257 | self, 258 | name: str, 259 | address: str, 260 | port: int, 261 | path: str, 262 | useHttps: bool = False, 263 | userName: str = "", 264 | password: str = "", 265 | ) -> None: 266 | self._manual_instances[name] = { 267 | "address": address, 268 | "port": port, 269 | "path": path, 270 | "useHttps": useHttps, 271 | "userName": userName, 272 | "password": password, 273 | } 274 | self._preferences.setValue( 275 | "octoprint/manual_instances", json.dumps(self._manual_instances) 276 | ) 277 | 278 | properties = { 279 | b"path": path.encode("utf-8"), 280 | b"useHttps": b"true" if useHttps else b"false", 281 | b"userName": userName.encode("utf-8"), 282 | b"password": password.encode("utf-8"), 283 | b"manual": b"true", 284 | } 285 | 286 | if name in self._instances: 287 | self.removeInstance(name) 288 | 289 | self.addInstance(name, address, port, properties) 290 | self.instanceListChanged.emit() 291 | 292 | def removeManualInstance(self, name: str) -> None: 293 | if name in self._instances: 294 | self.removeInstance(name) 295 | self.instanceListChanged.emit() 296 | 297 | if name in self._manual_instances: 298 | self._manual_instances.pop(name, None) 299 | self._preferences.setValue( 300 | "octoprint/manual_instances", json.dumps(self._manual_instances) 301 | ) 302 | 303 | ## Stop looking for devices on network. 304 | def stop(self) -> None: 305 | self._keep_alive_timer.stop() 306 | 307 | if self._browser: 308 | self._browser.cancel() 309 | self._browser = None # type: Optional[ServiceBrowser] 310 | 311 | if self._zeroconf: 312 | self._zeroconf.close() 313 | 314 | def getInstances(self) -> Dict[str, Any]: 315 | return self._instances 316 | 317 | def getInstanceById(self, instance_id: str) -> Optional[OctoPrintOutputDevice]: 318 | instance = self._instances.get(instance_id, None) 319 | if instance: 320 | return instance 321 | Logger.log("w", "No instance found with id %s", instance_id) 322 | return None 323 | 324 | def reCheckConnections(self) -> None: 325 | global_container_stack = Application.getInstance().getGlobalContainerStack() 326 | if not global_container_stack: 327 | return 328 | 329 | for key in self._instances: 330 | if key == global_container_stack.getMetaDataEntry("octoprint_id"): 331 | self._configureAndConnectInstance( 332 | self._instances[key], global_container_stack 333 | ) 334 | else: 335 | if self._instances[key].isConnected(): 336 | self._instances[key].close() 337 | 338 | ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. 339 | def addInstance( 340 | self, name: str, address: str, port: int, properties: Dict[bytes, bytes] 341 | ) -> None: 342 | global_container_stack = Application.getInstance().getGlobalContainerStack() 343 | if not global_container_stack: 344 | return 345 | 346 | instance = OctoPrintOutputDevice(name, address, port, properties) 347 | self._instances[instance.getId()] = instance 348 | if instance.getId() == global_container_stack.getMetaDataEntry("octoprint_id"): 349 | self._configureAndConnectInstance(instance, global_container_stack) 350 | 351 | def _configureAndConnectInstance( 352 | self, instance: OctoPrintOutputDevice, global_container_stack: ContainerStack 353 | ) -> None: 354 | api_key = global_container_stack.getMetaDataEntry("octoprint_api_key", "") 355 | 356 | instance.setApiKey(self._deobfuscateString(api_key)) 357 | instance.setShowCamera( 358 | parseBool( 359 | global_container_stack.getMetaDataEntry("octoprint_show_camera", "true") 360 | ) 361 | ) 362 | instance.setConfirmUploadOptions( 363 | parseBool( 364 | global_container_stack.getMetaDataEntry( 365 | "octoprint_confirm_upload_options", "false" 366 | ) 367 | ) 368 | ) 369 | instance.connectionStateChanged.connect(self._onInstanceConnectionStateChanged) 370 | instance.connect() 371 | 372 | def removeInstance(self, name: str) -> None: 373 | instance = self._instances.pop(name, None) 374 | if instance: 375 | if instance.isConnected(): 376 | instance.connectionStateChanged.disconnect( 377 | self._onInstanceConnectionStateChanged 378 | ) 379 | instance.disconnect() 380 | 381 | ## Utility handler to base64-decode a string (eg an obfuscated API key), if it has been encoded before 382 | def _deobfuscateString(self, source: str) -> str: 383 | try: 384 | return base64.b64decode(source.encode("ascii")).decode("ascii") 385 | except UnicodeDecodeError: 386 | return source 387 | 388 | ## Handler for when the connection state of one of the detected instances changes 389 | def _onInstanceConnectionStateChanged(self, key: str) -> None: 390 | if key not in self._instances: 391 | return 392 | 393 | if self._instances[key].isConnected(): 394 | self.getOutputDeviceManager().addOutputDevice(self._instances[key]) 395 | else: 396 | self.getOutputDeviceManager().removeOutputDevice(key) 397 | 398 | ## Handler for zeroConf detection 399 | def _onServiceChanged( 400 | self, 401 | zeroconf: Zeroconf, 402 | service_type: str, 403 | name: str, 404 | state_change: ServiceStateChange, 405 | ) -> None: 406 | if state_change == ServiceStateChange.Added: 407 | result = self._name_regex.match(name) 408 | if result: 409 | if result.group(1) == "on ": 410 | instance_name = result.group(2) 411 | else: 412 | instance_name = result.group(1) + result.group(2) 413 | else: 414 | instance_name = name 415 | 416 | Logger.log("d", "Bonjour service added: %s" % instance_name) 417 | 418 | address = "" 419 | try: 420 | info = zeroconf.get_service_info(service_type, name) 421 | for scoped_address in info.parsed_scoped_addresses(): 422 | address = self._validateIP(scoped_address) 423 | if address: 424 | break 425 | 426 | except AttributeError: 427 | info = ServiceInfo(service_type, name) 428 | # First try getting info from zeroconf cache 429 | for record in zeroconf.cache.entries_with_name(name.lower()): 430 | info.update_record(zeroconf, time.time(), record) 431 | 432 | for record in zeroconf.cache.entries_with_name(info.server): 433 | info.update_record(zeroconf, time.time(), record) 434 | if not isinstance(record, DNSAddress): 435 | continue 436 | address = self._validateIP(record.address) 437 | if address: 438 | break 439 | 440 | # Request more data if info is not complete 441 | if not address or not info.port: 442 | Logger.log("d", "Trying to get address of %s", instance_name) 443 | requested_info = zeroconf.get_service_info(service_type, name) 444 | 445 | if not requested_info: 446 | Logger.log( 447 | "w", "Could not get information about %s" % instance_name 448 | ) 449 | return 450 | 451 | info = requested_info 452 | 453 | if address and info.port: 454 | self.addInstanceSignal.emit( 455 | instance_name, address, info.port, info.properties 456 | ) 457 | else: 458 | Logger.log( 459 | "d", 460 | "Discovered instance named %s but received no address", 461 | instance_name, 462 | ) 463 | 464 | elif state_change == ServiceStateChange.Removed: 465 | self.removeInstanceSignal.emit(str(name)) 466 | 467 | def _validateIP(self, address: str) -> str: 468 | ip = None # type: Optional[Union[ipaddress.IPv4Address, ipaddress.IPv6Address]] 469 | try: 470 | ip = ipaddress.IPv4Address(address) # IPv4 471 | except ipaddress.AddressValueError: 472 | ip = ipaddress.IPv6Address(address) # IPv6 473 | except: 474 | return "" 475 | 476 | if ip and not ip.is_link_local: # don't accept 169.254.x.x address 477 | return str(ip) if ip.version == 4 else "[%s]" % str(ip) 478 | else: 479 | return "" 480 | -------------------------------------------------------------------------------- /PowerPlugins.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | # OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | from collections import OrderedDict 5 | from typing import Any, Tuple, List, Dict 6 | 7 | 8 | class PowerPlugins: 9 | def __init__(self) -> None: 10 | self._available_plugs = OrderedDict() # type: Dict[str, Any] 11 | 12 | def parsePluginData(self, plugin_data: Dict[str, Any]) -> None: 13 | self._available_plugs = OrderedDict() # type: Dict[str, Any] 14 | 15 | # plugins that only support a single plug 16 | simple_plugins = [ 17 | ("psucontrol", "PSU Control", []), 18 | ("mystromswitch", "MyStrom Switch", ["ip"]), 19 | ("ikea_tradfri", "IKEA Trådfri", ["gateway_ip", "selected_outlet"]), 20 | ] # type: List[Tuple[str, str, List[str]]] 21 | for (plugin_id, plugin_name, additional_data) in simple_plugins: 22 | if plugin_id in plugin_data: 23 | plug_data = plugin_data[plugin_id] 24 | all_config_set = True 25 | for config_item in additional_data: 26 | if not plug_data.get(config_item, None): 27 | all_config_set = False 28 | break 29 | if all_config_set: 30 | plug = OrderedDict([("plugin", plugin_id), ("name", plugin_name)]) 31 | self._available_plugs[self._createPlugId(plug)] = plug 32 | 33 | # plugins that have a `label` and `ip` specified in `arrSmartplugs` 34 | common_api_plugins = [ 35 | ("tplinksmartplug", "TP-Link Smartplug", []), # ip 36 | ("orvibos20", "Orvibo S20", []), # ip 37 | ("wemoswitch", "Wemo Switch", []), # ip 38 | ("tuyasmartplug", "Tuya Smartplug", []), # label 39 | ( 40 | "domoticz", 41 | "Domoticz", 42 | ["idx", "username", "password"], 43 | ), # ip, idx, username, password 44 | ( 45 | "tasmota", 46 | "Tasmota", 47 | ["idx"], 48 | ), # ip, idx, username, password, backlog_delay 49 | ] # type: List[Tuple[str, str, List[str]]] 50 | for (plugin_id, plugin_name, additional_data) in common_api_plugins: 51 | if plugin_id in plugin_data and "arrSmartplugs" in plugin_data[plugin_id]: 52 | for plug_data in plugin_data[plugin_id]["arrSmartplugs"]: 53 | if plug_data.get("ip", ""): 54 | plug_label = plug_data.get("label", "") 55 | plug = OrderedDict( 56 | [ 57 | ("plugin", plugin_id), 58 | ( 59 | "name", 60 | "%s (%s)" % (plug_label, plugin_name) 61 | if plug_label 62 | else plugin_name, 63 | ), 64 | ("label", plug_label), 65 | ("ip", plug_data["ip"]), 66 | ] 67 | ) 68 | for key in additional_data: 69 | plug[key] = plug_data.get(key, "") 70 | self._available_plugs[self._createPlugId(plug)] = plug 71 | 72 | # `tasmota_mqtt` has a slightly different settings dialect 73 | if "tasmota_mqtt" in plugin_data: 74 | plugin_id = "tasmota_mqtt" 75 | plugin_name = "Tasmota MQTT" 76 | for plug_data in plugin_data[plugin_id]["arrRelays"]: 77 | if plug_data.get("topic", "") and plug_data.get("relayN", ""): 78 | plug = OrderedDict( 79 | [ 80 | ("plugin", plugin_id), 81 | ( 82 | "name", 83 | "%s/%s (%s)" 84 | % ( 85 | plug_data["topic"], 86 | plug_data["relayN"], 87 | plugin_name, 88 | ), 89 | ), 90 | ("topic", plug_data["topic"]), 91 | ("relayN", plug_data["relayN"]), 92 | ] 93 | ) 94 | self._available_plugs[self._createPlugId(plug)] = plug 95 | 96 | def _createPlugId(self, plug_data: Dict[str, Any]) -> str: 97 | interesting_bits = [v for (k, v) in plug_data.items() if k != "name"] 98 | return "/".join(interesting_bits) 99 | 100 | def getAvailablePowerPlugs(self) -> Dict[str, Any]: 101 | return self._available_plugs 102 | 103 | def getSetStateCommand( 104 | self, plug_id: str, state: bool 105 | ) -> Tuple[str, Dict[str, Any]]: 106 | if plug_id not in self._available_plugs: 107 | return ("", {}) 108 | 109 | plugin_id = self._available_plugs[plug_id]["plugin"] 110 | end_point = "plugin/" + plugin_id 111 | 112 | if plugin_id == "psucontrol": 113 | return ( 114 | end_point, 115 | OrderedDict([("command", "turnPSUOn" if state else "turnPSUOff")]), 116 | ) 117 | 118 | if plugin_id == "mystromswitch": 119 | return ( 120 | end_point, 121 | OrderedDict( 122 | [("command", "enableRelais" if state else "disableRelais")] 123 | ), 124 | ) 125 | 126 | plug_data = self._available_plugs[plug_id] 127 | command = OrderedDict([("command", "turnOn" if state else "turnOff")]) 128 | arguments = [] # type: List[str] 129 | if plugin_id in ["tplinksmartplug", "orvibos20", "wemoswitch"]: 130 | # ip 131 | arguments = ["ip"] 132 | elif plugin_id == "domoticz": 133 | # ip, idx, username, password 134 | arguments = ["ip", "idx", "username", "password"] 135 | elif plugin_id == "tasmota_mqtt": 136 | # topic, relayN 137 | arguments = ["topic", "relayN"] 138 | elif plugin_id == "tasmota": 139 | # ip, idx 140 | arguments = ["ip", "idx"] 141 | elif plugin_id == "tuyasmartplug": 142 | # label 143 | arguments = ["label"] 144 | 145 | for key in arguments: 146 | command[key] = plug_data[key] 147 | 148 | return (end_point, command) 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OctoPrintPlugin 2 | Cura plugin which enables printing directly to OctoPrint and monitoring the progress. 3 | 4 | OctoPrint is a registered trademark. For more information about OctoPrint, see 5 | [octoprint.org](https://octoprint.org). 6 | 7 | This plugin started out as a fork of the [UM3NetworkPrinting plugin](https://github.com/Ultimaker/Cura/tree/2.4/plugins/UM3NetworkPrinting) 8 | 9 | This plugin is made possible in part by a contribution of [@ErikDeBruijn](https://github.com/ErikDeBruijn) 10 | and my other github sponsors. The development of this plugin can be sponsored via 11 | [Github Sponsors](https://github.com/sponsors/fieldofview) or [Paypal](https://www.paypal.me/fieldofview). 12 | 13 | Installation 14 | ---- 15 | #### Marketplace (recommended): 16 | The plugin is available through the Cura Marketplace as the OctoPrint Connection plugin 17 | #### Manually: 18 | Download or clone the repository into `[Cura configuration folder]/plugins/OctoPrintPlugin`. 19 | When cloning the repository, make sure to use the `--recursive` flag to include the submodules. 20 | 21 | The configuration folder can be found via Help -> Show Configuration Folder inside Cura. 22 | This opens the following folder: 23 | * Windows: `%APPDATA%\cura\\`, (usually `C:\Users\\AppData\Roaming\cura\\`) 24 | * Mac OS: `$HOME/Library/Application Support/cura//` 25 | * Linux: `$HOME/.local/share/cura//` 26 | 27 | How to use 28 | ---- 29 | - Make sure OctoPrint is up and running, and the discovery plugin is not disabled 30 | - In Cura, add a local printer matching the 3d printer you have connected to OctoPrint 31 | - Select "Connect to OctoPrint" on the Printers pane of the preferences. 32 | - Select your OctoPrint instance from the list and enter the API key which is 33 | available in the OctoPrint settings, or push the "Request..." button to request an 34 | application key from the OctoPrint instance. 35 | - Press the "Connect" button to connect the printer in Cura with the OctoPrint instance. 36 | - From this point on, the print monitor should be functional and you should be 37 | able to switch to "Print to Octoprint" in the lower right of the Cura window. 38 | 39 | Plugins 40 | --- 41 | The OctoPrint Connection plugin has special support for the following OctoPrint plugins: 42 | 43 | ### [Ultimaker Package Format](https://plugins.octoprint.org/plugins/UltimakerFormatPackage/) 44 | Support for including a thubmnail of the model along with the gcode. 45 | 46 | ### [PSU Control](https://plugins.octoprint.org/plugins/psucontrol/), [TP-Link Smartplug](https://plugins.octoprint.org/plugins/tplinksmartplug/), [Orvibo S20](https://plugins.octoprint.org/plugins/orvibos20/), [Wemo Switch](https://plugins.octoprint.org/plugins/wemoswitch/), [Tuya Smartplug](https://plugins.octoprint.org/plugins/tuyasmartplug/), [Domoticz](https://plugins.octoprint.org/plugins/domoticz/), [Tasmota](https://plugins.octoprint.org/plugins/tasmota/), [MyStrom Switch](https://plugins.octoprint.org/plugins/mystromswitch/), [IKEA Trådfri](https://plugins.octoprint.org/plugins/ikea_tradfri/) 47 | Support turning on the printer before sending a print job to OctoPrint. 48 | 49 | ### [MultiCam](https://plugins.octoprint.org/plugins/multicam/) 50 | Support for multiple cameras in the monitor view. 51 | 52 | ### [Print Time Genius](https://plugins.octoprint.org/plugins/PrintTimeGenius) 53 | Delay starting the print until after gcode analysis is done. 54 | 55 | Notes on UltiGCode (Ultimaker 2/Ultimaker 2+) 56 | ---- 57 | The Ultimaker 2(+) family uses a flavor of GCode named UltiGCode. Unfortunately printing 58 | using UltiGCode flavor does not work when printing over the USB connection. That is why 59 | using OctoPrint does not work with UltiGCode flavor. 60 | 61 | Included dependencies 62 | ---- 63 | This plugin contains a submodule/copy of the following dependecies: 64 | 65 | ### [zeroconf](https://github.com/jstasiak/python-zeroconf) as maintained by jstasiak. 66 | Python-zeroconf is licensed under the LGPL-2.1 67 | 68 | The module is included in the OctoPrintPlugin to replace the version that ships with 69 | older versions of Cura because that version has bugs. 70 | 71 | ### [ifaddr](https://github.com/pydron/ifaddr) as maintained by pydron. 72 | ifaddr is licensed under the MIT license. 73 | 74 | ### [async-timeout](https://github.com/aio-libs/async-timeout) as maintained by aio-libs 75 | async-timeout is licensed under the Apache License, Version 2.0. 76 | 77 | ifaddr and async-timeout are included in the OctoPrintPlugin because it is a dependency 78 | of python-zeroconf and they are not included with older versions of Cura. -------------------------------------------------------------------------------- /UploadOptions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | # OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | from UM.Application import Application 5 | from UM.Version import Version 6 | from UM.Util import parseBool 7 | 8 | try: 9 | from cura.ApplicationMetadata import CuraSDKVersion 10 | except ImportError: # Cura <= 3.6 11 | CuraSDKVersion = "6.0.0" 12 | USE_QT5 = False 13 | if CuraSDKVersion >= "8.0.0": 14 | from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot 15 | else: 16 | from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot 17 | USE_QT5 = True 18 | 19 | import os.path 20 | 21 | from typing import Any, Tuple, List, Dict, Callable, Optional 22 | 23 | 24 | class UploadOptions(QObject): 25 | def __init__(self) -> None: 26 | super().__init__() 27 | self._application = Application.getInstance() 28 | 29 | self._proceed_callback = None # type: Optional[Callable] 30 | 31 | self._file_name = "" 32 | self._file_path = "" 33 | 34 | self._auto_select = False 35 | self._auto_print = False 36 | 37 | self._qml_folder = "qml" if not USE_QT5 else "qml_qt5" 38 | 39 | 40 | def configure(self, global_container_stack, file_name) -> None: 41 | self.setAutoPrint( 42 | parseBool( 43 | global_container_stack.getMetaDataEntry("octoprint_auto_print", True) 44 | ) 45 | ) 46 | self.setAutoSelect( 47 | parseBool( 48 | global_container_stack.getMetaDataEntry("octoprint_auto_select", False) 49 | ) 50 | ) 51 | 52 | file_name_segments = file_name.split("/") 53 | self.setFileName(file_name_segments.pop()) 54 | self.setFilePath("/".join(file_name_segments)) 55 | 56 | def setProceedCallback(self, callback: Callable) -> None: 57 | self._proceed_callback = callback 58 | 59 | def showOptionsDialog(self) -> None: 60 | path = os.path.join( 61 | os.path.dirname(os.path.abspath(__file__)), self._qml_folder, "UploadOptions.qml" 62 | ) 63 | 64 | self._settings_dialog = self._application.createQmlComponent( 65 | path, {"manager": self} 66 | ) 67 | if self._settings_dialog: 68 | self._settings_dialog.show() 69 | 70 | @pyqtSlot() 71 | def acceptOptionsDialog(self) -> None: 72 | if self._proceed_callback: 73 | self._proceed_callback() 74 | 75 | fileNameChanged = pyqtSignal() 76 | 77 | def setFileName(self, file_name: str) -> None: 78 | self._file_name = file_name 79 | self.fileNameChanged.emit() 80 | 81 | @pyqtProperty(str, notify=fileNameChanged, fset=setFileName) 82 | def fileName(self) -> str: 83 | return self._file_name 84 | 85 | filePathChanged = pyqtSignal() 86 | 87 | def setFilePath(self, file_path: str) -> None: 88 | self._file_path = file_path 89 | self.filePathChanged.emit() 90 | 91 | @pyqtProperty(str, notify=filePathChanged, fset=setFilePath) 92 | def filePath(self) -> str: 93 | return self._file_path 94 | 95 | autoSelectChanged = pyqtSignal() 96 | 97 | def setAutoSelect(self, auto_select: bool) -> None: 98 | self._auto_select = auto_select 99 | self.autoSelectChanged.emit() 100 | 101 | @pyqtProperty(bool, notify=autoSelectChanged, fset=setAutoSelect) 102 | def autoSelect(self) -> bool: 103 | return self._auto_select 104 | 105 | autoPrintChanged = pyqtSignal() 106 | 107 | def setAutoPrint(self, auto_print: bool) -> None: 108 | self._auto_print = auto_print 109 | self.autoPrintChanged.emit() 110 | 111 | @pyqtProperty(bool, notify=autoPrintChanged, fset=setAutoPrint) 112 | def autoPrint(self) -> bool: 113 | return self._auto_print 114 | -------------------------------------------------------------------------------- /WebcamsModel.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | # OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | try: 5 | from cura.ApplicationMetadata import CuraSDKVersion 6 | except ImportError: # Cura <= 3.6 7 | CuraSDKVersion = "6.0.0" 8 | if CuraSDKVersion >= "8.0.0": 9 | from PyQt6.QtCore import Qt 10 | else: 11 | from PyQt5.QtCore import Qt 12 | 13 | from UM.Qt.ListModel import ListModel 14 | from UM.Logger import Logger 15 | 16 | from typing import List, Dict, Any, Union 17 | 18 | 19 | class WebcamsModel(ListModel): 20 | def __init__( 21 | self, 22 | protocol: str, 23 | address: str, 24 | port: int = 80, 25 | basic_auth_string: str = "", 26 | parent=None, 27 | ) -> None: 28 | super().__init__(parent) 29 | 30 | self._protocol = protocol 31 | self._address = address 32 | self._port = port 33 | self._basic_auth_string = basic_auth_string 34 | 35 | try: 36 | user_role = Qt.ItemDataRole.UserRole 37 | except AttributeError: 38 | user_role = Qt.UserRole 39 | 40 | self.addRoleName(user_role + 1, "name") 41 | self.addRoleName(user_role + 2, "stream_url") 42 | self.addRoleName(user_role + 3, "rotation") 43 | self.addRoleName(user_role + 4, "mirror") 44 | 45 | def deserialise(self, data: List[Dict[str, Any]]) -> None: 46 | items = [] 47 | 48 | for webcam in data: 49 | item = { 50 | "name": "_default", 51 | "stream_url": "", 52 | "rotation": 0, 53 | "mirror": False, 54 | } 55 | 56 | stream_url = "" 57 | if "streamUrl" in webcam and webcam["streamUrl"] != None: # from /webcam/ 58 | stream_url = webcam["streamUrl"].strip() 59 | elif "URL" in webcam and webcam["URL"] != None: # from /plugins/multicam 60 | stream_url = webcam["URL"].strip() 61 | 62 | if not stream_url: # empty string or None 63 | continue 64 | elif stream_url[:4].lower() == "http": # absolute uri 65 | item["stream_url"] = stream_url 66 | elif stream_url[:2] == "//": # protocol-relative 67 | item["stream_url"] = "%s:%s" % (self._protocol, stream_url) 68 | elif stream_url[:1] == ":": # domain-relative (on another port) 69 | item["stream_url"] = "%s://%s%s" % ( 70 | self._protocol, 71 | self._address, 72 | stream_url, 73 | ) 74 | elif stream_url[:1] == "/": # domain-relative (on same port) 75 | if not self._basic_auth_string: 76 | item["stream_url"] = "%s://%s:%d%s" % ( 77 | self._protocol, 78 | self._address, 79 | self._port, 80 | stream_url, 81 | ) 82 | else: 83 | item["stream_url"] = "%s://%s@%s:%d%s" % ( 84 | self._protocol, 85 | self._basic_auth_string, 86 | self._address, 87 | self._port, 88 | stream_url, 89 | ) 90 | else: 91 | Logger.log("w", "Unusable stream url received: %s", stream_url) 92 | item["stream_url"] = "" 93 | 94 | if "rotate90" in webcam: 95 | item["rotation"] = -90 if webcam["rotate90"] else 0 96 | if webcam["flipH"] and webcam["flipV"]: 97 | item["mirror"] = False 98 | item["rotation"] += 180 # type: ignore 99 | elif webcam["flipH"]: 100 | item["mirror"] = True 101 | item["rotation"] += 180 # type: ignore 102 | elif webcam["flipV"]: 103 | item["mirror"] = True 104 | else: 105 | item["mirror"] = False 106 | 107 | if "name" in webcam and webcam["name"] != None: 108 | item["name"] = webcam["name"] 109 | 110 | items.append(item) 111 | 112 | if self._items != items: 113 | self.setItems(items) 114 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | # OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import os, json 5 | 6 | from . import OctoPrintOutputDevicePlugin 7 | from . import DiscoverOctoPrintAction 8 | from . import NetworkMJPGImage 9 | 10 | from UM.Version import Version 11 | from UM.Application import Application 12 | from UM.Logger import Logger 13 | 14 | try: 15 | from cura.ApplicationMetadata import CuraSDKVersion 16 | except ImportError: # Cura <= 3.6 17 | CuraSDKVersion = "6.0.0" 18 | if CuraSDKVersion >= "8.0.0": 19 | from PyQt6.QtQml import qmlRegisterType 20 | else: 21 | from PyQt5.QtQml import qmlRegisterType 22 | 23 | def getMetaData(): 24 | return {} 25 | 26 | 27 | def register(app): 28 | qmlRegisterType( 29 | NetworkMJPGImage.NetworkMJPGImage, "OctoPrintPlugin", 1, 0, "NetworkMJPGImage" 30 | ) 31 | 32 | return { 33 | "output_device": OctoPrintOutputDevicePlugin.OctoPrintOutputDevicePlugin(), 34 | "machine_action": DiscoverOctoPrintAction.DiscoverOctoPrintAction(), 35 | } 36 | -------------------------------------------------------------------------------- /i18n/cs_CZ/LC_MESSAGES/octoprint.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fieldOfView/Cura-OctoPrintPlugin/67518c3306d40fc5c4e70ab9828d06bbbac6cbd9/i18n/cs_CZ/LC_MESSAGES/octoprint.mo -------------------------------------------------------------------------------- /i18n/cs_CZ/octoprint.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the OctoPrint Connection Plugin package. 4 | # FIRST AUTHOR michael.gruz@gmail, 2022. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: OctoPrint Connection Plugin\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2022-10-10 15:31+0000\n" 11 | "PO-Revision-Date: 2022-08-16 13:39+0200\n" 12 | "Last-Translator: \n" 13 | "Language-Team: \n" 14 | "Language: cs_CZ\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2);\n" 19 | "X-Generator: Poedit 3.1\n" 20 | 21 | #: DiscoverOctoPrintAction.py:74 22 | msgctxt "@action" 23 | msgid "Connect OctoPrint" 24 | msgstr "Připojení OctoPrintu" 25 | 26 | #: OctoPrintOutputDevice.py:254 27 | msgctxt "@action:button" 28 | msgid "Print with OctoPrint" 29 | msgstr "Tisk přes OctoPrint" 30 | 31 | #: OctoPrintOutputDevice.py:257 32 | msgctxt "@properties:tooltip" 33 | msgid "Print with OctoPrint" 34 | msgstr "Tisknout pomocí OctoPrintu" 35 | 36 | #: OctoPrintOutputDevice.py:261 OctoPrintOutputDevice.py:1019 37 | #, python-brace-format 38 | msgctxt "@info:status" 39 | msgid "Connected to OctoPrint on {0}" 40 | msgstr "Připojeno k OctoPrintu na {0}" 41 | 42 | #: OctoPrintOutputDevice.py:488 43 | #, python-brace-format 44 | msgctxt "@info:status" 45 | msgid "Connecting to OctoPrint on {0}" 46 | msgstr "Připojování k OctoPrintu na {0}" 47 | 48 | #: OctoPrintOutputDevice.py:652 49 | msgctxt "@info:status" 50 | msgid "Waiting for OctoPrint to connect to the printer..." 51 | msgstr "Čeká se, až se OctoPrint připojí k tiskárně..." 52 | 53 | #: OctoPrintOutputDevice.py:654 OctoPrintOutputDevice.py:785 54 | #: OctoPrintOutputDevice.py:1252 OctoPrintOutputDevice.py:1558 55 | #: OctoPrintOutputDevice.py:1648 OctoPrintOutputDevice.py:1677 56 | msgctxt "@label" 57 | msgid "OctoPrint" 58 | msgstr "OctoPrint" 59 | 60 | #: OctoPrintOutputDevice.py:662 61 | msgctxt "@action:button" 62 | msgid "Queue" 63 | msgstr "Fronta" 64 | 65 | #: OctoPrintOutputDevice.py:666 66 | msgctxt "@action:tooltip" 67 | msgid "Stop waiting for the printer and queue the print job instead" 68 | msgstr "" 69 | "Přestaňte čekat na tiskárnu a místo toho zařaďte tiskovou úlohu do fronty" 70 | 71 | #: OctoPrintOutputDevice.py:672 OctoPrintOutputDevice.py:793 72 | #: OctoPrintOutputDevice.py:1695 qml_qt5/ManualInstanceDialog.qml:319 73 | #: qml_qt5/UploadOptions.qml:107 qml/ManualInstanceDialog.qml:323 74 | #: qml/UploadOptions.qml:109 75 | msgctxt "@action:button" 76 | msgid "Cancel" 77 | msgstr "Zrušit" 78 | 79 | #: OctoPrintOutputDevice.py:674 OctoPrintOutputDevice.py:795 80 | #: OctoPrintOutputDevice.py:1697 81 | msgctxt "@action:tooltip" 82 | msgid "Abort the print job" 83 | msgstr "Zrušte tiskovou úlohu" 84 | 85 | #: OctoPrintOutputDevice.py:696 86 | msgctxt "@info:status" 87 | msgid "The printer is offline. Unable to start a new job." 88 | msgstr "Tiskárna je offline. Nelze zahájit novou práci." 89 | 90 | #: OctoPrintOutputDevice.py:700 91 | msgctxt "@info:status" 92 | msgid "OctoPrint is busy. Unable to start a new job." 93 | msgstr "OctoPrint je zaneprázdněn. Nelze zahájit novou práci." 94 | 95 | #: OctoPrintOutputDevice.py:707 OctoPrintOutputDevice.py:878 96 | #: OctoPrintOutputDevice.py:1806 97 | msgctxt "@label" 98 | msgid "OctoPrint error" 99 | msgstr "Chyba OctoPrintu" 100 | 101 | #: OctoPrintOutputDevice.py:711 102 | msgctxt "@action:button" 103 | msgid "Queue job" 104 | msgstr "Práce ve frontě" 105 | 106 | #: OctoPrintOutputDevice.py:715 107 | msgctxt "@action:tooltip" 108 | msgid "Queue this print job so it can be printed later" 109 | msgstr "" 110 | "Zařaďte tuto tiskovou úlohu do fronty, abyste ji mohli vytisknout později" 111 | 112 | #: OctoPrintOutputDevice.py:784 113 | msgctxt "@info:status" 114 | msgid "Sending data to OctoPrint..." 115 | msgstr "Odesílání dat do OctoPrintu..." 116 | 117 | #: OctoPrintOutputDevice.py:877 118 | msgctxt "@info:status" 119 | msgid "Unable to send data to OctoPrint." 120 | msgstr "Nelze odeslat data do OctoPrintu." 121 | 122 | #: OctoPrintOutputDevice.py:1121 123 | #, python-brace-format 124 | msgctxt "@info:status" 125 | msgid "OctoPrint on {0} does not allow access to the printer state" 126 | msgstr "OctoPrint na {0} neumožňuje přístup ke stavu tiskárny" 127 | 128 | #: OctoPrintOutputDevice.py:1136 129 | #, python-brace-format 130 | msgctxt "@info:status" 131 | msgid "The printer connected to OctoPrint on {0} is not operational" 132 | msgstr "Tiskárna připojená k OctoPrintu dne {0} není funkční" 133 | 134 | #: OctoPrintOutputDevice.py:1148 OctoPrintOutputDevice.py:1296 135 | #, python-brace-format 136 | msgctxt "@info:status" 137 | msgid "OctoPrint on {0} is not running" 138 | msgstr "OctoPrint na {0} není spuštěn" 139 | 140 | #: OctoPrintOutputDevice.py:1247 OctoPrintOutputDevice.py:1263 141 | msgctxt "@info:status" 142 | msgid "Streaming file to the SD card of the printer..." 143 | msgstr "Streamování souboru na SD kartu tiskárny..." 144 | 145 | #: OctoPrintOutputDevice.py:1284 146 | #, python-brace-format 147 | msgctxt "@info:status" 148 | msgid "OctoPrint on {0} does not allow access to the job state" 149 | msgstr "OctoPrint na {0} neumožňuje přístup ke stavu úlohy" 150 | 151 | #: OctoPrintOutputDevice.py:1393 152 | msgctxt "@info:error" 153 | msgid "" 154 | "You are not allowed to start print jobs on OctoPrint with the configured API " 155 | "key." 156 | msgstr "" 157 | "Nemáte povoleno spouštět tiskové úlohy na OctoPrintu pomocí " 158 | "nakonfigurovaného klíče API." 159 | 160 | #: OctoPrintOutputDevice.py:1420 161 | msgctxt "@info:error" 162 | msgid "" 163 | "You are not allowed to control print jobs on OctoPrint with the configured " 164 | "API key." 165 | msgstr "" 166 | "Nemáte povoleno ovládat tiskové úlohy na OctoPrint pomocí nakonfigurovaného " 167 | "klíče API." 168 | 169 | #: OctoPrintOutputDevice.py:1437 170 | msgctxt "@info:error" 171 | msgid "" 172 | "You are not allowed to send gcode commands to OctoPrint with the configured " 173 | "API key." 174 | msgstr "" 175 | "Nemáte povoleno odesílat příkazy gcode do OctoPrintu s nakonfigurovaným " 176 | "klíčem API." 177 | 178 | #: OctoPrintOutputDevice.py:1459 OctoPrintOutputDevice.py:1466 179 | msgctxt "@label" 180 | msgid "Anonymous user" 181 | msgstr "Anonymní uživatel" 182 | 183 | #: OctoPrintOutputDevice.py:1473 184 | msgctxt "@label" 185 | msgid "Unknown user" 186 | msgstr "Neznámý uživatel" 187 | 188 | #: OctoPrintOutputDevice.py:1479 189 | msgctxt "@info:error" 190 | msgid "You are not allowed to access to OctoPrint with the configured API key." 191 | msgstr "" 192 | "Nemáte povolen přístup k OctoPrintu pomocí nakonfigurovaného klíče API." 193 | 194 | #: OctoPrintOutputDevice.py:1493 195 | msgctxt "@info:error" 196 | msgid "" 197 | "You are not allowed to control printer connections on OctoPrint with the " 198 | "configured API key." 199 | msgstr "" 200 | "Nemáte povoleno ovládat připojení tiskárny na OctoPrintu pomocí " 201 | "nakonfigurovaného klíče API." 202 | 203 | #: OctoPrintOutputDevice.py:1512 204 | msgctxt "@info:error" 205 | msgid "You are not allowed to access OctoPrint with the configured API key." 206 | msgstr "" 207 | "Nemáte povolen přístup k OctoPrintu pomocí nakonfigurovaného klíče API." 208 | 209 | #: OctoPrintOutputDevice.py:1526 OctoPrintOutputDevice.py:1603 210 | msgctxt "@info:error" 211 | msgid "OctoPrint responded with an unknown error" 212 | msgstr "" 213 | 214 | #: OctoPrintOutputDevice.py:1554 215 | msgctxt "@info:status" 216 | msgid "Storing data on OctoPrint" 217 | msgstr "Ukládání dat na OctoPrint" 218 | 219 | #: OctoPrintOutputDevice.py:1578 220 | msgctxt "@info:error" 221 | msgid "" 222 | "You are not allowed to upload files to OctoPrint with the configured API key." 223 | msgstr "" 224 | "Nemáte oprávnění nahrávat soubory do OctoPrintu pomocí nakonfigurovaného " 225 | "klíče API." 226 | 227 | #: OctoPrintOutputDevice.py:1584 228 | msgctxt "@info:error" 229 | msgid "Can't store a print job on SD card of the printer at this time." 230 | msgstr "V tuto chvíli nelze uložit tiskovou úlohu na SD kartu tiskárny." 231 | 232 | #: OctoPrintOutputDevice.py:1589 233 | msgctxt "@info:error" 234 | msgid "" 235 | "Can't store the print job with the same name as the one that is currently " 236 | "printing." 237 | msgstr "" 238 | "Nelze uložit tiskovou úlohu se stejným názvem jako ta, která se právě tiskne." 239 | 240 | #: OctoPrintOutputDevice.py:1641 241 | #, python-brace-format 242 | msgctxt "@info:status" 243 | msgid "Saved to OctoPrint as {0}" 244 | msgstr "Uloženo do OctoPrintu jako {0}" 245 | 246 | #: OctoPrintOutputDevice.py:1646 247 | msgctxt "@info:status" 248 | msgid "Saved to OctoPrint" 249 | msgstr "Uloženo do OctoPrintu" 250 | 251 | #: OctoPrintOutputDevice.py:1651 qml_qt5/OctoPrintComponents.qml:24 252 | #: qml/OctoPrintComponents.qml:22 253 | msgctxt "@action:button" 254 | msgid "OctoPrint..." 255 | msgstr "OctoPrint..." 256 | 257 | #: OctoPrintOutputDevice.py:1653 qml_qt5/OctoPrintComponents.qml:23 258 | #: qml/OctoPrintComponents.qml:21 259 | msgctxt "@info:tooltip" 260 | msgid "Open the OctoPrint web interface" 261 | msgstr "Otevřete webové rozhraní OctoPrintu" 262 | 263 | #: OctoPrintOutputDevice.py:1675 264 | msgctxt "@info:status" 265 | msgid "Waiting for OctoPrint to complete G-code analysis..." 266 | msgstr "Čekání, až OctoPrint dokončí analýzu G-kódu..." 267 | 268 | #: OctoPrintOutputDevice.py:1685 269 | msgctxt "@action:button" 270 | msgid "Print now" 271 | msgstr "Vytiskněte nyní" 272 | 273 | #: OctoPrintOutputDevice.py:1689 274 | msgctxt "@action:tooltip" 275 | msgid "Stop waiting for the G-code analysis and start printing immediately" 276 | msgstr "Přestaňte čekat na analýzu G-kódu a začněte ihned tisknout" 277 | 278 | #: qml_qt5/DiscoverOctoPrintAction.qml:77 qml/DiscoverOctoPrintAction.qml:76 279 | msgctxt "@title" 280 | msgid "Connect to OctoPrint" 281 | msgstr "Připojení k OctoPrintu" 282 | 283 | #: qml_qt5/DiscoverOctoPrintAction.qml:98 qml/DiscoverOctoPrintAction.qml:94 284 | msgctxt "@label" 285 | msgid "Select your OctoPrint instance from the list below." 286 | msgstr "Vyberte svou instanci OctoPrintu ze seznamu níže." 287 | 288 | #: qml_qt5/DiscoverOctoPrintAction.qml:108 qml/DiscoverOctoPrintAction.qml:104 289 | msgctxt "@action:button" 290 | msgid "Add" 291 | msgstr "Přidat" 292 | 293 | #: qml_qt5/DiscoverOctoPrintAction.qml:118 qml/DiscoverOctoPrintAction.qml:114 294 | msgctxt "@action:button" 295 | msgid "Edit" 296 | msgstr "Upravit" 297 | 298 | #: qml_qt5/DiscoverOctoPrintAction.qml:134 qml/DiscoverOctoPrintAction.qml:130 299 | msgctxt "@action:button" 300 | msgid "Remove" 301 | msgstr "Odstranit" 302 | 303 | #: qml_qt5/DiscoverOctoPrintAction.qml:142 qml/DiscoverOctoPrintAction.qml:138 304 | msgctxt "@action:button" 305 | msgid "Refresh" 306 | msgstr "Obnovit" 307 | 308 | #: qml_qt5/DiscoverOctoPrintAction.qml:239 qml/DiscoverOctoPrintAction.qml:231 309 | msgctxt "@label" 310 | msgid "Automatically discover local OctoPrint instances" 311 | msgstr "Automaticky zjistit místní instance OctoPrintu" 312 | 313 | #: qml_qt5/DiscoverOctoPrintAction.qml:277 qml/DiscoverOctoPrintAction.qml:267 314 | msgctxt "@label" 315 | msgid "Address" 316 | msgstr "Adresa" 317 | 318 | #: qml_qt5/DiscoverOctoPrintAction.qml:289 qml/DiscoverOctoPrintAction.qml:278 319 | msgctxt "@label" 320 | msgid "Version" 321 | msgstr "Verze" 322 | 323 | #: qml_qt5/DiscoverOctoPrintAction.qml:301 qml/DiscoverOctoPrintAction.qml:288 324 | msgctxt "@label" 325 | msgid "API Key" 326 | msgstr "API Klíč" 327 | 328 | #: qml_qt5/DiscoverOctoPrintAction.qml:319 qml/DiscoverOctoPrintAction.qml:308 329 | msgctxt "@action" 330 | msgid "Request..." 331 | msgstr "Žádost..." 332 | 333 | #: qml_qt5/DiscoverOctoPrintAction.qml:331 qml/DiscoverOctoPrintAction.qml:319 334 | msgctxt "@label" 335 | msgid "Username" 336 | msgstr "Uživatel" 337 | 338 | #: qml_qt5/DiscoverOctoPrintAction.qml:405 qml/DiscoverOctoPrintAction.qml:392 339 | msgctxt "@label" 340 | msgid "Please enter the API key to access OctoPrint." 341 | msgstr "Zadejte klíč API pro přístup k OctoPrintu." 342 | 343 | #: qml_qt5/DiscoverOctoPrintAction.qml:411 qml/DiscoverOctoPrintAction.qml:398 344 | msgctxt "@label" 345 | msgid "OctoPrint is not available." 346 | msgstr "OctoPrint není k dispozici." 347 | 348 | #: qml_qt5/DiscoverOctoPrintAction.qml:421 qml/DiscoverOctoPrintAction.qml:408 349 | msgctxt "@label" 350 | msgid "The API key is invalid." 351 | msgstr "Klíč API je neplatný." 352 | 353 | #: qml_qt5/DiscoverOctoPrintAction.qml:426 qml/DiscoverOctoPrintAction.qml:413 354 | msgctxt "@label" 355 | msgid "Checking the API key..." 356 | msgstr "Kontrola klíče API..." 357 | 358 | #: qml_qt5/DiscoverOctoPrintAction.qml:429 qml/DiscoverOctoPrintAction.qml:416 359 | msgctxt "@label" 360 | msgid "You can get the API key through the OctoPrint web page." 361 | msgstr "Klíč API můžete získat prostřednictvím webové stránky OctoPrintu." 362 | 363 | #: qml_qt5/DiscoverOctoPrintAction.qml:445 qml_qt5/UploadOptions.qml:91 364 | #: qml/DiscoverOctoPrintAction.qml:431 qml/UploadOptions.qml:93 365 | msgctxt "@label" 366 | msgid "Start print job after uploading" 367 | msgstr "Po nahrání spusťit tiskovou úlohu" 368 | 369 | #: qml_qt5/DiscoverOctoPrintAction.qml:456 qml_qt5/UploadOptions.qml:98 370 | #: qml/DiscoverOctoPrintAction.qml:442 qml/UploadOptions.qml:100 371 | msgctxt "@label" 372 | msgid "Select print job after uploading" 373 | msgstr "Po nahrání vyber tiskovou úlohu" 374 | 375 | #: qml_qt5/DiscoverOctoPrintAction.qml:471 qml/DiscoverOctoPrintAction.qml:457 376 | msgctxt "@label" 377 | msgid "Turn on printer with" 378 | msgstr "Zapněte tiskárnu pomocí" 379 | 380 | #: qml_qt5/DiscoverOctoPrintAction.qml:530 qml/DiscoverOctoPrintAction.qml:519 381 | msgctxt "@label" 382 | msgid "Unknown plug" 383 | msgstr "Neznámá zástrčka" 384 | 385 | #: qml_qt5/DiscoverOctoPrintAction.qml:552 qml/DiscoverOctoPrintAction.qml:541 386 | msgctxt "@label" 387 | msgid "Connect to printer before sending print job" 388 | msgstr "Před odesláním tiskové úlohy se připoj k tiskárně" 389 | 390 | #: qml_qt5/DiscoverOctoPrintAction.qml:563 qml/DiscoverOctoPrintAction.qml:552 391 | msgctxt "@label" 392 | msgid "Store G-code on the SD card of the printer" 393 | msgstr "Uložte G-kód na SD kartu tiskárny" 394 | 395 | #: qml_qt5/DiscoverOctoPrintAction.qml:576 qml/DiscoverOctoPrintAction.qml:564 396 | msgctxt "@label" 397 | msgid "" 398 | "Note: Transferring files to the printer SD card takes very long. Using this " 399 | "option is not recommended." 400 | msgstr "" 401 | "Poznámka: Přenos souborů na kartu SD tiskárny trvá velmi dlouho. Použití " 402 | "této možnosti se nedoporučuje." 403 | 404 | #: qml_qt5/DiscoverOctoPrintAction.qml:581 qml/DiscoverOctoPrintAction.qml:569 405 | msgctxt "@label" 406 | msgid "Confirm print job options before sending" 407 | msgstr "Před odesláním potvrďit možnosti tiskové úlohy" 408 | 409 | #: qml_qt5/DiscoverOctoPrintAction.qml:591 qml/DiscoverOctoPrintAction.qml:579 410 | msgctxt "@label" 411 | msgid "Show webcam image" 412 | msgstr "Zobrazit webovou kameru" 413 | 414 | #: qml_qt5/DiscoverOctoPrintAction.qml:602 qml/DiscoverOctoPrintAction.qml:590 415 | msgctxt "@label" 416 | msgid "Set G-code flavor to \"Marlin\"" 417 | msgstr "Nastavit příchuť G-code na \"Marlin\"" 418 | 419 | #: qml_qt5/DiscoverOctoPrintAction.qml:608 qml/DiscoverOctoPrintAction.qml:596 420 | msgctxt "@label" 421 | msgid "" 422 | "Note: Printing UltiGCode using OctoPrint does not work. Setting G-code " 423 | "flavor to \"Marlin\" fixes this, but overrides material settings on your " 424 | "printer." 425 | msgstr "" 426 | "Poznámka: Tisk UltiGCode pomocí OctoPrintu nefunguje. Nastavení varianty G-" 427 | "code na \"Marlin\" to opraví, ale přepíše nastavení materiálu na vaší " 428 | "tiskárně." 429 | 430 | #: qml_qt5/DiscoverOctoPrintAction.qml:622 qml/DiscoverOctoPrintAction.qml:609 431 | msgctxt "@action" 432 | msgid "Open in browser..." 433 | msgstr "Otevřít v prohlížeči..." 434 | 435 | #: qml_qt5/DiscoverOctoPrintAction.qml:634 qml/DiscoverOctoPrintAction.qml:621 436 | msgctxt "@action:button" 437 | msgid "Disconnect" 438 | msgstr "Odpojit" 439 | 440 | #: qml_qt5/DiscoverOctoPrintAction.qml:637 qml/DiscoverOctoPrintAction.qml:624 441 | msgctxt "@action:button" 442 | msgid "Connect" 443 | msgstr "Připojit" 444 | 445 | #: qml_qt5/ManualInstanceDialog.qml:24 qml/ManualInstanceDialog.qml:24 446 | msgctxt "@title:window" 447 | msgid "Manually added OctoPrint instance" 448 | msgstr "Ručně přidaná instance OctoPrintu" 449 | 450 | #: qml_qt5/ManualInstanceDialog.qml:162 qml/ManualInstanceDialog.qml:166 451 | msgctxt "@label" 452 | msgid "Instance Name" 453 | msgstr "Název instance" 454 | 455 | #: qml_qt5/ManualInstanceDialog.qml:179 qml/ManualInstanceDialog.qml:183 456 | msgctxt "@label" 457 | msgid "IP Address or Hostname" 458 | msgstr "IP adresa nebo název hostitele" 459 | 460 | #: qml_qt5/ManualInstanceDialog.qml:197 qml/ManualInstanceDialog.qml:201 461 | msgctxt "@label" 462 | msgid "Port Number" 463 | msgstr "Číslo portu" 464 | 465 | #: qml_qt5/ManualInstanceDialog.qml:225 qml_qt5/UploadOptions.qml:37 466 | #: qml/ManualInstanceDialog.qml:229 qml/UploadOptions.qml:39 467 | msgctxt "@label" 468 | msgid "Path" 469 | msgstr "Cesta" 470 | 471 | #: qml_qt5/ManualInstanceDialog.qml:262 qml/ManualInstanceDialog.qml:266 472 | msgctxt "@label" 473 | msgid "" 474 | "In order to use HTTPS or a HTTP username and password, you need to configure " 475 | "a reverse proxy or another service." 476 | msgstr "" 477 | "Abyste mohli používat HTTPS nebo HTTP uživatelské jméno a heslo, musíte " 478 | "nakonfigurovat reverzní proxy nebo jinou službu." 479 | 480 | #: qml_qt5/ManualInstanceDialog.qml:267 qml/ManualInstanceDialog.qml:271 481 | msgctxt "@label" 482 | msgid "Use HTTPS" 483 | msgstr "Použijte HTTPS" 484 | 485 | #: qml_qt5/ManualInstanceDialog.qml:291 qml/ManualInstanceDialog.qml:295 486 | msgctxt "@label" 487 | msgid "HTTP username" 488 | msgstr "HTTP uživatelské jméno" 489 | 490 | #: qml_qt5/ManualInstanceDialog.qml:304 qml/ManualInstanceDialog.qml:308 491 | msgctxt "@label" 492 | msgid "HTTP password" 493 | msgstr "HTTP heslo" 494 | 495 | #: qml_qt5/ManualInstanceDialog.qml:327 qml/ManualInstanceDialog.qml:331 496 | msgctxt "@action:button" 497 | msgid "Ok" 498 | msgstr "Ok" 499 | 500 | #: qml_qt5/UploadOptions.qml:14 qml/UploadOptions.qml:14 501 | msgctxt "@action:button" 502 | msgid "Upload to OctoPrint Options" 503 | msgstr "Nahrát do Možnosti OctoPrintu" 504 | 505 | #: qml_qt5/UploadOptions.qml:56 qml/UploadOptions.qml:58 506 | msgctxt "@label" 507 | msgid "Filename" 508 | msgstr "Název souboru" 509 | 510 | #: qml_qt5/UploadOptions.qml:78 qml/UploadOptions.qml:80 511 | msgctxt "@label" 512 | msgid "A file extenstion will be added automatically." 513 | msgstr "Přípona souboru bude přidána automaticky." 514 | 515 | #: qml_qt5/UploadOptions.qml:111 qml/UploadOptions.qml:113 516 | msgctxt "@action:button" 517 | msgid "OK" 518 | msgstr "OK" 519 | -------------------------------------------------------------------------------- /i18n/de_DE/LC_MESSAGES/octoprint.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fieldOfView/Cura-OctoPrintPlugin/67518c3306d40fc5c4e70ab9828d06bbbac6cbd9/i18n/de_DE/LC_MESSAGES/octoprint.mo -------------------------------------------------------------------------------- /i18n/de_DE/octoprint.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the OctoPrint Connection Plugin package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: OctoPrint Connection Plugin\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-05-05 13:43+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: DiscoverOctoPrintAction.py:68 21 | msgctxt "@action" 22 | msgid "Connect OctoPrint" 23 | msgstr "OctoPrint verbinden" 24 | 25 | #: OctoPrintOutputDevice.py:249 26 | msgctxt "@action:button" 27 | msgid "Print with OctoPrint" 28 | msgstr "Mit OctoPrint drucken" 29 | 30 | #: OctoPrintOutputDevice.py:252 31 | msgctxt "@properties:tooltip" 32 | msgid "Print with OctoPrint" 33 | msgstr "Mit OctoPrint drucken" 34 | 35 | #: OctoPrintOutputDevice.py:256 OctoPrintOutputDevice.py:1012 36 | #, python-brace-format 37 | msgctxt "@info:status" 38 | msgid "Connected to OctoPrint on {0}" 39 | msgstr "Verbunden mit OctoPrint auf {0}" 40 | 41 | #: OctoPrintOutputDevice.py:483 42 | #, python-brace-format 43 | msgctxt "@info:status" 44 | msgid "Connecting to OctoPrint on {0}" 45 | msgstr "Verbunden mit OctoPrint auf {0}" 46 | 47 | #: OctoPrintOutputDevice.py:647 48 | msgctxt "@info:status" 49 | msgid "Waiting for OctoPrint to connect to the printer..." 50 | msgstr "Warten auf OctoPrint für die Verbindung mit dem Drucker..." 51 | 52 | #: OctoPrintOutputDevice.py:649 OctoPrintOutputDevice.py:780 53 | #: OctoPrintOutputDevice.py:1245 OctoPrintOutputDevice.py:1544 54 | #: OctoPrintOutputDevice.py:1625 OctoPrintOutputDevice.py:1654 55 | msgctxt "@label" 56 | msgid "OctoPrint" 57 | msgstr "OctoPrint" 58 | 59 | #: OctoPrintOutputDevice.py:657 60 | msgctxt "@action:button" 61 | msgid "Queue" 62 | msgstr "Warteschlange" 63 | 64 | #: OctoPrintOutputDevice.py:661 65 | msgctxt "@action:tooltip" 66 | msgid "Stop waiting for the printer and queue the print job instead" 67 | msgstr "Stoppen Sie das Warten auf den Drucker und stellen Sie stattdessen den Druckauftrag in die Warteschlange" 68 | 69 | #: OctoPrintOutputDevice.py:667 OctoPrintOutputDevice.py:788 70 | #: OctoPrintOutputDevice.py:1672 qml_qt5/ManualInstanceDialog.qml:319 71 | #: qml_qt5/UploadOptions.qml:107 qml/ManualInstanceDialog.qml:323 72 | #: qml/UploadOptions.qml:109 73 | msgctxt "@action:button" 74 | msgid "Cancel" 75 | msgstr "Abbrechen" 76 | 77 | #: OctoPrintOutputDevice.py:669 OctoPrintOutputDevice.py:790 78 | #: OctoPrintOutputDevice.py:1674 79 | msgctxt "@action:tooltip" 80 | msgid "Abort the print job" 81 | msgstr "Über den Druckauftrag" 82 | 83 | #: OctoPrintOutputDevice.py:691 84 | msgctxt "@info:status" 85 | msgid "The printer is offline. Unable to start a new job." 86 | msgstr "Der Drucker ist offline. Ein neuer Druckjob ist nicht möglich" 87 | 88 | #: OctoPrintOutputDevice.py:695 89 | msgctxt "@info:status" 90 | msgid "OctoPrint is busy. Unable to start a new job." 91 | msgstr "OctoPrint ist beschäftig. Ein neuer Druckjob ist nicht möglich" 92 | 93 | #: OctoPrintOutputDevice.py:702 OctoPrintOutputDevice.py:873 94 | #: OctoPrintOutputDevice.py:1783 95 | msgctxt "@label" 96 | msgid "OctoPrint error" 97 | msgstr "OctoPrint Fehler" 98 | 99 | #: OctoPrintOutputDevice.py:706 100 | msgctxt "@action:button" 101 | msgid "Queue job" 102 | msgstr "Warteschlangenjob" 103 | 104 | #: OctoPrintOutputDevice.py:710 105 | msgctxt "@action:tooltip" 106 | msgid "Queue this print job so it can be printed later" 107 | msgstr "Druckauftrag in die Warteschlange einfügen, damit er später gedruckt werden kann" 108 | 109 | #: OctoPrintOutputDevice.py:779 110 | msgctxt "@info:status" 111 | msgid "Sending data to OctoPrint..." 112 | msgstr "Sende Daten zu OctoPrint..." 113 | 114 | #: OctoPrintOutputDevice.py:872 115 | msgctxt "@info:status" 116 | msgid "Unable to send data to OctoPrint." 117 | msgstr "Daten an OctoPrint senden ist nicht möglich." 118 | 119 | #: OctoPrintOutputDevice.py:1114 120 | #, python-brace-format 121 | msgctxt "@info:status" 122 | msgid "OctoPrint on {0} does not allow access to the printer state" 123 | msgstr "OctoPrint von {0} erlaubt keinen Zugriff auf den Druckerstatus" 124 | 125 | #: OctoPrintOutputDevice.py:1129 126 | #, python-brace-format 127 | msgctxt "@info:status" 128 | msgid "The printer connected to OctoPrint on {0} is not operational" 129 | msgstr "Der mit OctoPrint auf {0} verbundene Drucker ist nicht betriebsbereit" 130 | 131 | #: OctoPrintOutputDevice.py:1141 OctoPrintOutputDevice.py:1289 132 | #, python-brace-format 133 | msgctxt "@info:status" 134 | msgid "OctoPrint on {0} is not running" 135 | msgstr "OctoPrint auf {0} läuft nicht" 136 | 137 | #: OctoPrintOutputDevice.py:1240 OctoPrintOutputDevice.py:1256 138 | msgctxt "@info:status" 139 | msgid "Streaming file to the SD card of the printer..." 140 | msgstr "Streaming-Datei auf die SD-Karte des Druckers..." 141 | 142 | #: OctoPrintOutputDevice.py:1277 143 | #, python-brace-format 144 | msgctxt "@info:status" 145 | msgid "OctoPrint on {0} does not allow access to the job state" 146 | msgstr "OctoPrint auf {0} erlaubt keinen Zugriff auf den Auftragsstatus" 147 | 148 | #: OctoPrintOutputDevice.py:1386 149 | msgctxt "@info:error" 150 | msgid "" 151 | "You are not allowed to start print jobs on OctoPrint with the configured API " 152 | "key." 153 | msgstr "" 154 | "Sie dürfen keine Druckaufträge auf OctoPrint mit der konfigurierten API starten" 155 | "Schlüssel." 156 | 157 | #: OctoPrintOutputDevice.py:1413 158 | msgctxt "@info:error" 159 | msgid "" 160 | "You are not allowed to control print jobs on OctoPrint with the configured " 161 | "API key." 162 | msgstr "" 163 | "Es ist nicht erlaubt die OctoPrint Druckjobs mit dieses Konfiguration zu steuern " 164 | "API Schlüssel." 165 | 166 | #: OctoPrintOutputDevice.py:1430 167 | msgctxt "@info:error" 168 | msgid "" 169 | "You are not allowed to send gcode commands to OctoPrint with the configured " 170 | "API key." 171 | msgstr "" 172 | "Es ist nicht erlaubt gcode Befehle mit dieser Konfiguration an OctoPrint zu schicken" 173 | "API Schlüssel." 174 | 175 | #: OctoPrintOutputDevice.py:1452 OctoPrintOutputDevice.py:1459 176 | msgctxt "@label" 177 | msgid "Anonymous user" 178 | msgstr "Anonymer Benutzer" 179 | 180 | #: OctoPrintOutputDevice.py:1466 181 | msgctxt "@label" 182 | msgid "Unknown user" 183 | msgstr "Unbekannter Benutzer" 184 | 185 | #: OctoPrintOutputDevice.py:1472 186 | msgctxt "@info:error" 187 | msgid "You are not allowed to access to OctoPrint with the configured API key." 188 | msgstr "Es ist mit diesem API Schlüssel kein Zugriff auf OctoPrint erlaubt." 189 | 190 | #: OctoPrintOutputDevice.py:1486 191 | msgctxt "@info:error" 192 | msgid "" 193 | "You are not allowed to control printer connections on OctoPrint with the " 194 | "configured API key." 195 | msgstr "" 196 | "Mit diesem API Schlüssel ist es nicht erlaubt Druckerverbindungen zu OctoPrint zu steuern." 197 | "" 198 | 199 | #: OctoPrintOutputDevice.py:1505 200 | msgctxt "@info:error" 201 | msgid "You are not allowed to access OctoPrint with the configured API key." 202 | msgstr "Der Zugriff auf OctoPrint ist mit diesem API Schlüssel nicht erlaubt." 203 | 204 | #: OctoPrintOutputDevice.py:1540 205 | msgctxt "@info:status" 206 | msgid "Storing data on OctoPrint" 207 | msgstr "Daten auf OctoPrint speichern" 208 | 209 | #: OctoPrintOutputDevice.py:1562 210 | msgctxt "@info:error" 211 | msgid "" 212 | "You are not allowed to upload files to OctoPrint with the configured API key." 213 | msgstr "" 214 | "Mit diesem API Schlüssel ist das Hochladen zu OctoPrint nicht erlaubt." 215 | 216 | #: OctoPrintOutputDevice.py:1568 217 | msgctxt "@info:error" 218 | msgid "Can't store a print job on SD card of the printer at this time." 219 | msgstr "Es kann kein Druckauftrag auf der SD-Karte des Druckers gespeichert werden" 220 | 221 | #: OctoPrintOutputDevice.py:1573 222 | msgctxt "@info:error" 223 | msgid "" 224 | "Can't store the print job with the same name as the one that is currently " 225 | "printing." 226 | msgstr "" 227 | "Der Druckauftrag kann nicht unter demselben Namen wie der gerade gedruckte " 228 | "gespeichert werden. " 229 | 230 | #: OctoPrintOutputDevice.py:1618 231 | #, python-brace-format 232 | msgctxt "@info:status" 233 | msgid "Saved to OctoPrint as {0}" 234 | msgstr "" 235 | 236 | #: OctoPrintOutputDevice.py:1623 237 | msgctxt "@info:status" 238 | msgid "Saved to OctoPrint" 239 | msgstr "Gespeichert in OctoPrint" 240 | 241 | #: OctoPrintOutputDevice.py:1628 qml_qt5/OctoPrintComponents.qml:24 242 | #: qml/OctoPrintComponents.qml:22 243 | msgctxt "@action:button" 244 | msgid "OctoPrint..." 245 | msgstr "OctoPrint..." 246 | 247 | #: OctoPrintOutputDevice.py:1630 qml_qt5/OctoPrintComponents.qml:23 248 | #: qml/OctoPrintComponents.qml:21 249 | msgctxt "@info:tooltip" 250 | msgid "Open the OctoPrint web interface" 251 | msgstr "Öffne die OctoPrint-Weboberfläche" 252 | 253 | #: OctoPrintOutputDevice.py:1652 254 | msgctxt "@info:status" 255 | msgid "Waiting for OctoPrint to complete G-code analysis..." 256 | msgstr "Warten, bis OctoPrint die G-Code-Analyse abgeschlossen hat..." 257 | 258 | #: OctoPrintOutputDevice.py:1662 259 | msgctxt "@action:button" 260 | msgid "Print now" 261 | msgstr "Jetzt drucken" 262 | 263 | #: OctoPrintOutputDevice.py:1666 264 | msgctxt "@action:tooltip" 265 | msgid "Stop waiting for the G-code analysis and start printing immediately" 266 | msgstr "Das Warten auf die G-Code Analyse abbrechen und sofort mit dem Drucken beginnen" 267 | 268 | #: qml_qt5/DiscoverOctoPrintAction.qml:77 qml/DiscoverOctoPrintAction.qml:76 269 | msgctxt "@title" 270 | msgid "Connect to OctoPrint" 271 | msgstr "Verbindung mit OctoPrint" 272 | 273 | #: qml_qt5/DiscoverOctoPrintAction.qml:98 qml/DiscoverOctoPrintAction.qml:94 274 | msgctxt "@label" 275 | msgid "Select your OctoPrint instance from the list below." 276 | msgstr "Wählen Sie Ihre OctoPrint-Instanz aus der Liste unten aus." 277 | 278 | #: qml_qt5/DiscoverOctoPrintAction.qml:108 qml/DiscoverOctoPrintAction.qml:104 279 | msgctxt "@action:button" 280 | msgid "Add" 281 | msgstr "Hinzufügen" 282 | 283 | #: qml_qt5/DiscoverOctoPrintAction.qml:118 qml/DiscoverOctoPrintAction.qml:114 284 | msgctxt "@action:button" 285 | msgid "Edit" 286 | msgstr "Bearbeiten" 287 | 288 | #: qml_qt5/DiscoverOctoPrintAction.qml:134 qml/DiscoverOctoPrintAction.qml:130 289 | msgctxt "@action:button" 290 | msgid "Remove" 291 | msgstr "Entfernen" 292 | 293 | #: qml_qt5/DiscoverOctoPrintAction.qml:142 qml/DiscoverOctoPrintAction.qml:138 294 | msgctxt "@action:button" 295 | msgid "Refresh" 296 | msgstr "Aktualisieren" 297 | 298 | #: qml_qt5/DiscoverOctoPrintAction.qml:239 qml/DiscoverOctoPrintAction.qml:231 299 | msgctxt "@label" 300 | msgid "Automatically discover local OctoPrint instances" 301 | msgstr "Lokale OctoPrint-Instanzen automatisch erkennen" 302 | 303 | #: qml_qt5/DiscoverOctoPrintAction.qml:277 qml/DiscoverOctoPrintAction.qml:267 304 | msgctxt "@label" 305 | msgid "Address" 306 | msgstr "Adresse" 307 | 308 | #: qml_qt5/DiscoverOctoPrintAction.qml:289 qml/DiscoverOctoPrintAction.qml:278 309 | msgctxt "@label" 310 | msgid "Version" 311 | msgstr "Version" 312 | 313 | #: qml_qt5/DiscoverOctoPrintAction.qml:301 qml/DiscoverOctoPrintAction.qml:288 314 | msgctxt "@label" 315 | msgid "API Key" 316 | msgstr "API Schlüssel" 317 | 318 | #: qml_qt5/DiscoverOctoPrintAction.qml:319 qml/DiscoverOctoPrintAction.qml:308 319 | msgctxt "@action" 320 | msgid "Request..." 321 | msgstr "Anfragen..." 322 | 323 | #: qml_qt5/DiscoverOctoPrintAction.qml:331 qml/DiscoverOctoPrintAction.qml:319 324 | msgctxt "@label" 325 | msgid "Username" 326 | msgstr "Benutzer" 327 | 328 | #: qml_qt5/DiscoverOctoPrintAction.qml:405 qml/DiscoverOctoPrintAction.qml:392 329 | msgctxt "@label" 330 | msgid "Please enter the API key to access OctoPrint." 331 | msgstr "Bitte geben Sie den API-Schlüssel ein, um auf OctoPrint zuzugreifen." 332 | 333 | #: qml_qt5/DiscoverOctoPrintAction.qml:411 qml/DiscoverOctoPrintAction.qml:398 334 | msgctxt "@label" 335 | msgid "OctoPrint is not available." 336 | msgstr "OctoPrint ist nicht verfügbar." 337 | 338 | #: qml_qt5/DiscoverOctoPrintAction.qml:421 qml/DiscoverOctoPrintAction.qml:408 339 | msgctxt "@label" 340 | msgid "The API key is invalid." 341 | msgstr "Der API Schlüssel ist ungültig." 342 | 343 | #: qml_qt5/DiscoverOctoPrintAction.qml:426 qml/DiscoverOctoPrintAction.qml:413 344 | msgctxt "@label" 345 | msgid "Checking the API key..." 346 | msgstr "Überprüfe den API Schlüssel..." 347 | 348 | #: qml_qt5/DiscoverOctoPrintAction.qml:429 qml/DiscoverOctoPrintAction.qml:416 349 | msgctxt "@label" 350 | msgid "You can get the API key through the OctoPrint web page." 351 | msgstr "Sie können den API-Schlüssel über die OctoPrint-Webseite erhalten." 352 | 353 | #: qml_qt5/DiscoverOctoPrintAction.qml:445 qml_qt5/UploadOptions.qml:91 354 | #: qml/DiscoverOctoPrintAction.qml:431 qml/UploadOptions.qml:93 355 | msgctxt "@label" 356 | msgid "Start print job after uploading" 357 | msgstr "Druckauftrag nach dem Hochladen sofort starten" 358 | 359 | #: qml_qt5/DiscoverOctoPrintAction.qml:456 qml_qt5/UploadOptions.qml:98 360 | #: qml/DiscoverOctoPrintAction.qml:442 qml/UploadOptions.qml:100 361 | msgctxt "@label" 362 | msgid "Select print job after uploading" 363 | msgstr "Druckauftrag nach dem Hochladen auswählen" 364 | 365 | #: qml_qt5/DiscoverOctoPrintAction.qml:471 qml/DiscoverOctoPrintAction.qml:457 366 | msgctxt "@label" 367 | msgid "Turn on printer with" 368 | msgstr "Drucker mit einschalten" 369 | 370 | #: qml_qt5/DiscoverOctoPrintAction.qml:530 qml/DiscoverOctoPrintAction.qml:519 371 | msgctxt "@label" 372 | msgid "Unknown plug" 373 | msgstr "Unbekannter Stecker" 374 | 375 | #: qml_qt5/DiscoverOctoPrintAction.qml:552 qml/DiscoverOctoPrintAction.qml:541 376 | msgctxt "@label" 377 | msgid "Connect to printer before sending print job" 378 | msgstr "Verbinden bevor der Druckauftrag gesendet wird" 379 | 380 | #: qml_qt5/DiscoverOctoPrintAction.qml:563 qml/DiscoverOctoPrintAction.qml:552 381 | msgctxt "@label" 382 | msgid "Store G-code on the SD card of the printer" 383 | msgstr "G-Code auf der SD-Karte des Druckers speichern" 384 | 385 | #: qml_qt5/DiscoverOctoPrintAction.qml:576 qml/DiscoverOctoPrintAction.qml:564 386 | msgctxt "@label" 387 | msgid "" 388 | "Note: Transferring files to the printer SD card takes very long. Using this " 389 | "option is not recommended." 390 | msgstr "" 391 | "Hinweis: Das Übertragen von Dateien auf die SD-Karte des Druckers dauert " 392 | "sehr lange. Die Verwendung dieser Option wird nicht empfohlen. " 393 | 394 | #: qml_qt5/DiscoverOctoPrintAction.qml:581 qml/DiscoverOctoPrintAction.qml:569 395 | msgctxt "@label" 396 | msgid "Confirm print job options before sending" 397 | msgstr "Druckauftragsoptionen vor dem Senden bestätigen" 398 | 399 | #: qml_qt5/DiscoverOctoPrintAction.qml:591 qml/DiscoverOctoPrintAction.qml:579 400 | msgctxt "@label" 401 | msgid "Show webcam image" 402 | msgstr "Webcam-Bild anzeigen" 403 | 404 | #: qml_qt5/DiscoverOctoPrintAction.qml:602 qml/DiscoverOctoPrintAction.qml:590 405 | msgctxt "@label" 406 | msgid "Set G-code flavor to \"Marlin\"" 407 | msgstr "Stellt den G-Code-Flavor auf \"Marlin\"" 408 | 409 | #: qml_qt5/DiscoverOctoPrintAction.qml:608 qml/DiscoverOctoPrintAction.qml:596 410 | msgctxt "@label" 411 | msgid "" 412 | "Note: Printing UltiGCode using OctoPrint does not work. Setting G-code " 413 | "flavor to \"Marlin\" fixes this, but overrides material settings on your " 414 | "printer." 415 | msgstr "" 416 | "Hinweis: Das Drucken von UltiGCode mit OctoPrint funktioniert nicht. Das " 417 | "Festlegen des G-Code-Geschmacks auf \"Marlin\" behebt dies, überschreibt " 418 | "jedoch die Materialeinstellungen auf Ihrem Drucker." 419 | 420 | 421 | #: qml_qt5/DiscoverOctoPrintAction.qml:622 qml/DiscoverOctoPrintAction.qml:609 422 | msgctxt "@action" 423 | msgid "Open in browser..." 424 | msgstr "Im Browser öffnen..." 425 | 426 | #: qml_qt5/DiscoverOctoPrintAction.qml:634 qml/DiscoverOctoPrintAction.qml:621 427 | msgctxt "@action:button" 428 | msgid "Disconnect" 429 | msgstr "Trennen" 430 | 431 | #: qml_qt5/DiscoverOctoPrintAction.qml:637 qml/DiscoverOctoPrintAction.qml:624 432 | msgctxt "@action:button" 433 | msgid "Connect" 434 | msgstr "Verbinden" 435 | 436 | #: qml_qt5/ManualInstanceDialog.qml:24 qml/ManualInstanceDialog.qml:24 437 | msgctxt "@title:window" 438 | msgid "Manually added OctoPrint instance" 439 | msgstr "Manuell hinzugefügte OctoPrint-Instanz" 440 | 441 | #: qml_qt5/ManualInstanceDialog.qml:162 qml/ManualInstanceDialog.qml:166 442 | msgctxt "@label" 443 | msgid "Instance Name" 444 | msgstr "Instanz Name" 445 | 446 | #: qml_qt5/ManualInstanceDialog.qml:179 qml/ManualInstanceDialog.qml:183 447 | msgctxt "@label" 448 | msgid "IP Address or Hostname" 449 | msgstr "IP Adresse oder Hostname" 450 | 451 | #: qml_qt5/ManualInstanceDialog.qml:197 qml/ManualInstanceDialog.qml:201 452 | msgctxt "@label" 453 | msgid "Port Number" 454 | msgstr "Port Nummer" 455 | 456 | #: qml_qt5/ManualInstanceDialog.qml:225 qml_qt5/UploadOptions.qml:37 457 | #: qml/ManualInstanceDialog.qml:229 qml/UploadOptions.qml:39 458 | msgctxt "@label" 459 | msgid "Path" 460 | msgstr "Pfad" 461 | 462 | #: qml_qt5/ManualInstanceDialog.qml:262 qml/ManualInstanceDialog.qml:266 463 | msgctxt "@label" 464 | msgid "" 465 | "In order to use HTTPS or a HTTP username and password, you need to configure " 466 | "a reverse proxy or another service." 467 | msgstr "" 468 | "Um HTTPS oder einen HTTP-Benutzernamen und ein Kennwort zu verwenden, müssen Sie " 469 | "einen Reverse-Proxy oder einen anderen Dienst konfigurieren." 470 | 471 | #: qml_qt5/ManualInstanceDialog.qml:267 qml/ManualInstanceDialog.qml:271 472 | msgctxt "@label" 473 | msgid "Use HTTPS" 474 | msgstr "HTTPS verwenden" 475 | 476 | #: qml_qt5/ManualInstanceDialog.qml:291 qml/ManualInstanceDialog.qml:295 477 | msgctxt "@label" 478 | msgid "HTTP username" 479 | msgstr "Benutzername" 480 | 481 | #: qml_qt5/ManualInstanceDialog.qml:304 qml/ManualInstanceDialog.qml:308 482 | msgctxt "@label" 483 | msgid "HTTP password" 484 | msgstr "Passwort" 485 | 486 | #: qml_qt5/ManualInstanceDialog.qml:327 qml/ManualInstanceDialog.qml:331 487 | msgctxt "@action:button" 488 | msgid "Ok" 489 | msgstr "Ok" 490 | 491 | #: qml_qt5/UploadOptions.qml:14 qml/UploadOptions.qml:14 492 | msgctxt "@action:button" 493 | msgid "Upload to OctoPrint Options" 494 | msgstr "In OctoPrint-Optionen hochladen" 495 | 496 | #: qml_qt5/UploadOptions.qml:56 qml/UploadOptions.qml:58 497 | msgctxt "@label" 498 | msgid "Filename" 499 | msgstr "Dateiname" 500 | 501 | #: qml_qt5/UploadOptions.qml:78 qml/UploadOptions.qml:80 502 | msgctxt "@label" 503 | msgid "A file extenstion will be added automatically." 504 | msgstr "Eine Dateiendung wird automatisch hinzugefügt." 505 | 506 | #: qml_qt5/UploadOptions.qml:111 qml/UploadOptions.qml:113 507 | msgctxt "@action:button" 508 | msgid "OK" 509 | msgstr "Ok" 510 | -------------------------------------------------------------------------------- /i18n/octoprint.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the OctoPrint Connection Plugin package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: OctoPrint Connection Plugin\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-11-15 10:11+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: OctoPrintOutputDevice.py:250 21 | msgctxt "@action:button" 22 | msgid "Print with OctoPrint" 23 | msgstr "" 24 | 25 | #: OctoPrintOutputDevice.py:253 26 | msgctxt "@properties:tooltip" 27 | msgid "Print with OctoPrint" 28 | msgstr "" 29 | 30 | #: OctoPrintOutputDevice.py:257 OctoPrintOutputDevice.py:1013 31 | #, python-brace-format 32 | msgctxt "@info:status" 33 | msgid "Connected to OctoPrint on {0}" 34 | msgstr "" 35 | 36 | #: OctoPrintOutputDevice.py:484 37 | #, python-brace-format 38 | msgctxt "@info:status" 39 | msgid "Connecting to OctoPrint on {0}" 40 | msgstr "" 41 | 42 | #: OctoPrintOutputDevice.py:648 43 | msgctxt "@info:status" 44 | msgid "Waiting for OctoPrint to connect to the printer..." 45 | msgstr "" 46 | 47 | #: OctoPrintOutputDevice.py:650 OctoPrintOutputDevice.py:781 48 | #: OctoPrintOutputDevice.py:1246 OctoPrintOutputDevice.py:1545 49 | #: OctoPrintOutputDevice.py:1626 OctoPrintOutputDevice.py:1655 50 | msgctxt "@label" 51 | msgid "OctoPrint" 52 | msgstr "" 53 | 54 | #: OctoPrintOutputDevice.py:658 55 | msgctxt "@action:button" 56 | msgid "Queue" 57 | msgstr "" 58 | 59 | #: OctoPrintOutputDevice.py:662 60 | msgctxt "@action:tooltip" 61 | msgid "Stop waiting for the printer and queue the print job instead" 62 | msgstr "" 63 | 64 | #: OctoPrintOutputDevice.py:668 OctoPrintOutputDevice.py:789 65 | #: OctoPrintOutputDevice.py:1673 qml_qt5/UploadOptions.qml:107 66 | #: qml_qt5/ManualInstanceDialog.qml:319 qml/UploadOptions.qml:109 67 | #: qml/ManualInstanceDialog.qml:323 68 | msgctxt "@action:button" 69 | msgid "Cancel" 70 | msgstr "" 71 | 72 | #: OctoPrintOutputDevice.py:670 OctoPrintOutputDevice.py:791 73 | #: OctoPrintOutputDevice.py:1675 74 | msgctxt "@action:tooltip" 75 | msgid "Abort the print job" 76 | msgstr "" 77 | 78 | #: OctoPrintOutputDevice.py:692 79 | msgctxt "@info:status" 80 | msgid "The printer is offline. Unable to start a new job." 81 | msgstr "" 82 | 83 | #: OctoPrintOutputDevice.py:696 84 | msgctxt "@info:status" 85 | msgid "OctoPrint is busy. Unable to start a new job." 86 | msgstr "" 87 | 88 | #: OctoPrintOutputDevice.py:703 OctoPrintOutputDevice.py:874 89 | #: OctoPrintOutputDevice.py:1784 90 | msgctxt "@label" 91 | msgid "OctoPrint error" 92 | msgstr "" 93 | 94 | #: OctoPrintOutputDevice.py:707 95 | msgctxt "@action:button" 96 | msgid "Queue job" 97 | msgstr "" 98 | 99 | #: OctoPrintOutputDevice.py:711 100 | msgctxt "@action:tooltip" 101 | msgid "Queue this print job so it can be printed later" 102 | msgstr "" 103 | 104 | #: OctoPrintOutputDevice.py:780 105 | msgctxt "@info:status" 106 | msgid "Sending data to OctoPrint..." 107 | msgstr "" 108 | 109 | #: OctoPrintOutputDevice.py:873 110 | msgctxt "@info:status" 111 | msgid "Unable to send data to OctoPrint." 112 | msgstr "" 113 | 114 | #: OctoPrintOutputDevice.py:1115 115 | #, python-brace-format 116 | msgctxt "@info:status" 117 | msgid "OctoPrint on {0} does not allow access to the printer state" 118 | msgstr "" 119 | 120 | #: OctoPrintOutputDevice.py:1130 121 | #, python-brace-format 122 | msgctxt "@info:status" 123 | msgid "The printer connected to OctoPrint on {0} is not operational" 124 | msgstr "" 125 | 126 | #: OctoPrintOutputDevice.py:1142 OctoPrintOutputDevice.py:1290 127 | #, python-brace-format 128 | msgctxt "@info:status" 129 | msgid "OctoPrint on {0} is not running" 130 | msgstr "" 131 | 132 | #: OctoPrintOutputDevice.py:1241 OctoPrintOutputDevice.py:1257 133 | msgctxt "@info:status" 134 | msgid "Streaming file to the SD card of the printer..." 135 | msgstr "" 136 | 137 | #: OctoPrintOutputDevice.py:1278 138 | #, python-brace-format 139 | msgctxt "@info:status" 140 | msgid "OctoPrint on {0} does not allow access to the job state" 141 | msgstr "" 142 | 143 | #: OctoPrintOutputDevice.py:1387 144 | msgctxt "@info:error" 145 | msgid "" 146 | "You are not allowed to start print jobs on OctoPrint with the configured API " 147 | "key." 148 | msgstr "" 149 | 150 | #: OctoPrintOutputDevice.py:1414 151 | msgctxt "@info:error" 152 | msgid "" 153 | "You are not allowed to control print jobs on OctoPrint with the configured " 154 | "API key." 155 | msgstr "" 156 | 157 | #: OctoPrintOutputDevice.py:1431 158 | msgctxt "@info:error" 159 | msgid "" 160 | "You are not allowed to send gcode commands to OctoPrint with the configured " 161 | "API key." 162 | msgstr "" 163 | 164 | #: OctoPrintOutputDevice.py:1453 OctoPrintOutputDevice.py:1460 165 | msgctxt "@label" 166 | msgid "Anonymous user" 167 | msgstr "" 168 | 169 | #: OctoPrintOutputDevice.py:1467 170 | msgctxt "@label" 171 | msgid "Unknown user" 172 | msgstr "" 173 | 174 | #: OctoPrintOutputDevice.py:1473 175 | msgctxt "@info:error" 176 | msgid "You are not allowed to access to OctoPrint with the configured API key." 177 | msgstr "" 178 | 179 | #: OctoPrintOutputDevice.py:1487 180 | msgctxt "@info:error" 181 | msgid "" 182 | "You are not allowed to control printer connections on OctoPrint with the " 183 | "configured API key." 184 | msgstr "" 185 | 186 | #: OctoPrintOutputDevice.py:1506 187 | msgctxt "@info:error" 188 | msgid "You are not allowed to access OctoPrint with the configured API key." 189 | msgstr "" 190 | 191 | #: OctoPrintOutputDevice.py:1541 192 | msgctxt "@info:status" 193 | msgid "Storing data on OctoPrint" 194 | msgstr "" 195 | 196 | #: OctoPrintOutputDevice.py:1563 197 | msgctxt "@info:error" 198 | msgid "" 199 | "You are not allowed to upload files to OctoPrint with the configured API key." 200 | msgstr "" 201 | 202 | #: OctoPrintOutputDevice.py:1569 203 | msgctxt "@info:error" 204 | msgid "Can't store a print job on SD card of the printer at this time." 205 | msgstr "" 206 | 207 | #: OctoPrintOutputDevice.py:1574 208 | msgctxt "@info:error" 209 | msgid "" 210 | "Can't store the print job with the same name as the one that is currently " 211 | "printing." 212 | msgstr "" 213 | 214 | #: OctoPrintOutputDevice.py:1619 215 | #, python-brace-format 216 | msgctxt "@info:status" 217 | msgid "Saved to OctoPrint as {0}" 218 | msgstr "" 219 | 220 | #: OctoPrintOutputDevice.py:1624 221 | msgctxt "@info:status" 222 | msgid "Saved to OctoPrint" 223 | msgstr "" 224 | 225 | #: OctoPrintOutputDevice.py:1629 qml_qt5/OctoPrintComponents.qml:24 226 | #: qml/OctoPrintComponents.qml:22 227 | msgctxt "@action:button" 228 | msgid "OctoPrint..." 229 | msgstr "" 230 | 231 | #: OctoPrintOutputDevice.py:1631 qml_qt5/OctoPrintComponents.qml:23 232 | #: qml/OctoPrintComponents.qml:21 233 | msgctxt "@info:tooltip" 234 | msgid "Open the OctoPrint web interface" 235 | msgstr "" 236 | 237 | #: OctoPrintOutputDevice.py:1653 238 | msgctxt "@info:status" 239 | msgid "Waiting for OctoPrint to complete G-code analysis..." 240 | msgstr "" 241 | 242 | #: OctoPrintOutputDevice.py:1663 243 | msgctxt "@action:button" 244 | msgid "Print now" 245 | msgstr "" 246 | 247 | #: OctoPrintOutputDevice.py:1667 248 | msgctxt "@action:tooltip" 249 | msgid "Stop waiting for the G-code analysis and start printing immediately" 250 | msgstr "" 251 | 252 | #: DiscoverOctoPrintAction.py:70 253 | msgctxt "@action" 254 | msgid "Connect OctoPrint" 255 | msgstr "" 256 | 257 | #: qml_qt5/UploadOptions.qml:14 qml/UploadOptions.qml:14 258 | msgctxt "@action:button" 259 | msgid "Upload to OctoPrint Options" 260 | msgstr "" 261 | 262 | #: qml_qt5/UploadOptions.qml:37 qml_qt5/ManualInstanceDialog.qml:225 263 | #: qml/UploadOptions.qml:39 qml/ManualInstanceDialog.qml:229 264 | msgctxt "@label" 265 | msgid "Path" 266 | msgstr "" 267 | 268 | #: qml_qt5/UploadOptions.qml:56 qml/UploadOptions.qml:58 269 | msgctxt "@label" 270 | msgid "Filename" 271 | msgstr "" 272 | 273 | #: qml_qt5/UploadOptions.qml:78 qml/UploadOptions.qml:80 274 | msgctxt "@label" 275 | msgid "A file extenstion will be added automatically." 276 | msgstr "" 277 | 278 | #: qml_qt5/UploadOptions.qml:91 qml_qt5/DiscoverOctoPrintAction.qml:445 279 | #: qml/UploadOptions.qml:93 qml/DiscoverOctoPrintAction.qml:431 280 | msgctxt "@label" 281 | msgid "Start print job after uploading" 282 | msgstr "" 283 | 284 | #: qml_qt5/UploadOptions.qml:98 qml_qt5/DiscoverOctoPrintAction.qml:456 285 | #: qml/UploadOptions.qml:100 qml/DiscoverOctoPrintAction.qml:442 286 | msgctxt "@label" 287 | msgid "Select print job after uploading" 288 | msgstr "" 289 | 290 | #: qml_qt5/UploadOptions.qml:111 qml/UploadOptions.qml:113 291 | msgctxt "@action:button" 292 | msgid "OK" 293 | msgstr "" 294 | 295 | #: qml_qt5/ManualInstanceDialog.qml:24 qml/ManualInstanceDialog.qml:24 296 | msgctxt "@title:window" 297 | msgid "Manually added OctoPrint instance" 298 | msgstr "" 299 | 300 | #: qml_qt5/ManualInstanceDialog.qml:162 qml/ManualInstanceDialog.qml:166 301 | msgctxt "@label" 302 | msgid "Instance Name" 303 | msgstr "" 304 | 305 | #: qml_qt5/ManualInstanceDialog.qml:179 qml/ManualInstanceDialog.qml:183 306 | msgctxt "@label" 307 | msgid "IP Address or Hostname" 308 | msgstr "" 309 | 310 | #: qml_qt5/ManualInstanceDialog.qml:197 qml/ManualInstanceDialog.qml:201 311 | msgctxt "@label" 312 | msgid "Port Number" 313 | msgstr "" 314 | 315 | #: qml_qt5/ManualInstanceDialog.qml:262 qml/ManualInstanceDialog.qml:266 316 | msgctxt "@label" 317 | msgid "" 318 | "In order to use HTTPS or a HTTP username and password, you need to configure " 319 | "a reverse proxy or another service." 320 | msgstr "" 321 | 322 | #: qml_qt5/ManualInstanceDialog.qml:267 qml/ManualInstanceDialog.qml:271 323 | msgctxt "@label" 324 | msgid "Use HTTPS" 325 | msgstr "" 326 | 327 | #: qml_qt5/ManualInstanceDialog.qml:291 qml/ManualInstanceDialog.qml:295 328 | msgctxt "@label" 329 | msgid "HTTP username" 330 | msgstr "" 331 | 332 | #: qml_qt5/ManualInstanceDialog.qml:304 qml/ManualInstanceDialog.qml:308 333 | msgctxt "@label" 334 | msgid "HTTP password" 335 | msgstr "" 336 | 337 | #: qml_qt5/ManualInstanceDialog.qml:327 qml/ManualInstanceDialog.qml:331 338 | msgctxt "@action:button" 339 | msgid "Ok" 340 | msgstr "" 341 | 342 | #: qml_qt5/DiscoverOctoPrintAction.qml:77 qml/DiscoverOctoPrintAction.qml:76 343 | msgctxt "@title" 344 | msgid "Connect to OctoPrint" 345 | msgstr "" 346 | 347 | #: qml_qt5/DiscoverOctoPrintAction.qml:98 qml/DiscoverOctoPrintAction.qml:94 348 | msgctxt "@label" 349 | msgid "Select your OctoPrint instance from the list below." 350 | msgstr "" 351 | 352 | #: qml_qt5/DiscoverOctoPrintAction.qml:108 qml/DiscoverOctoPrintAction.qml:104 353 | msgctxt "@action:button" 354 | msgid "Add" 355 | msgstr "" 356 | 357 | #: qml_qt5/DiscoverOctoPrintAction.qml:118 qml/DiscoverOctoPrintAction.qml:114 358 | msgctxt "@action:button" 359 | msgid "Edit" 360 | msgstr "" 361 | 362 | #: qml_qt5/DiscoverOctoPrintAction.qml:134 qml/DiscoverOctoPrintAction.qml:130 363 | msgctxt "@action:button" 364 | msgid "Remove" 365 | msgstr "" 366 | 367 | #: qml_qt5/DiscoverOctoPrintAction.qml:142 qml/DiscoverOctoPrintAction.qml:138 368 | msgctxt "@action:button" 369 | msgid "Refresh" 370 | msgstr "" 371 | 372 | #: qml_qt5/DiscoverOctoPrintAction.qml:239 qml/DiscoverOctoPrintAction.qml:231 373 | msgctxt "@label" 374 | msgid "Automatically discover local OctoPrint instances" 375 | msgstr "" 376 | 377 | #: qml_qt5/DiscoverOctoPrintAction.qml:277 qml/DiscoverOctoPrintAction.qml:267 378 | msgctxt "@label" 379 | msgid "Address" 380 | msgstr "" 381 | 382 | #: qml_qt5/DiscoverOctoPrintAction.qml:289 qml/DiscoverOctoPrintAction.qml:278 383 | msgctxt "@label" 384 | msgid "Version" 385 | msgstr "" 386 | 387 | #: qml_qt5/DiscoverOctoPrintAction.qml:301 qml/DiscoverOctoPrintAction.qml:288 388 | msgctxt "@label" 389 | msgid "API Key" 390 | msgstr "" 391 | 392 | #: qml_qt5/DiscoverOctoPrintAction.qml:319 qml/DiscoverOctoPrintAction.qml:308 393 | msgctxt "@action" 394 | msgid "Request..." 395 | msgstr "" 396 | 397 | #: qml_qt5/DiscoverOctoPrintAction.qml:331 qml/DiscoverOctoPrintAction.qml:319 398 | msgctxt "@label" 399 | msgid "Username" 400 | msgstr "" 401 | 402 | #: qml_qt5/DiscoverOctoPrintAction.qml:405 qml/DiscoverOctoPrintAction.qml:392 403 | msgctxt "@label" 404 | msgid "Please enter the API key to access OctoPrint." 405 | msgstr "" 406 | 407 | #: qml_qt5/DiscoverOctoPrintAction.qml:411 qml/DiscoverOctoPrintAction.qml:398 408 | msgctxt "@label" 409 | msgid "OctoPrint is not available." 410 | msgstr "" 411 | 412 | #: qml_qt5/DiscoverOctoPrintAction.qml:421 qml/DiscoverOctoPrintAction.qml:408 413 | msgctxt "@label" 414 | msgid "The API key is invalid." 415 | msgstr "" 416 | 417 | #: qml_qt5/DiscoverOctoPrintAction.qml:426 qml/DiscoverOctoPrintAction.qml:413 418 | msgctxt "@label" 419 | msgid "Checking the API key..." 420 | msgstr "" 421 | 422 | #: qml_qt5/DiscoverOctoPrintAction.qml:429 qml/DiscoverOctoPrintAction.qml:416 423 | msgctxt "@label" 424 | msgid "You can get the API key through the OctoPrint web page." 425 | msgstr "" 426 | 427 | #: qml_qt5/DiscoverOctoPrintAction.qml:471 qml/DiscoverOctoPrintAction.qml:457 428 | msgctxt "@label" 429 | msgid "Turn on printer with" 430 | msgstr "" 431 | 432 | #: qml_qt5/DiscoverOctoPrintAction.qml:530 qml/DiscoverOctoPrintAction.qml:519 433 | msgctxt "@label" 434 | msgid "Unknown plug" 435 | msgstr "" 436 | 437 | #: qml_qt5/DiscoverOctoPrintAction.qml:552 qml/DiscoverOctoPrintAction.qml:541 438 | msgctxt "@label" 439 | msgid "Connect to printer before sending print job" 440 | msgstr "" 441 | 442 | #: qml_qt5/DiscoverOctoPrintAction.qml:563 qml/DiscoverOctoPrintAction.qml:552 443 | msgctxt "@label" 444 | msgid "Store G-code on the SD card of the printer" 445 | msgstr "" 446 | 447 | #: qml_qt5/DiscoverOctoPrintAction.qml:576 qml/DiscoverOctoPrintAction.qml:564 448 | msgctxt "@label" 449 | msgid "" 450 | "Note: Transferring files to the printer SD card takes very long. Using this " 451 | "option is not recommended." 452 | msgstr "" 453 | 454 | #: qml_qt5/DiscoverOctoPrintAction.qml:581 qml/DiscoverOctoPrintAction.qml:569 455 | msgctxt "@label" 456 | msgid "Confirm print job options before sending" 457 | msgstr "" 458 | 459 | #: qml_qt5/DiscoverOctoPrintAction.qml:591 qml/DiscoverOctoPrintAction.qml:579 460 | msgctxt "@label" 461 | msgid "Show webcam image" 462 | msgstr "" 463 | 464 | #: qml_qt5/DiscoverOctoPrintAction.qml:602 qml/DiscoverOctoPrintAction.qml:590 465 | msgctxt "@label" 466 | msgid "Set G-code flavor to \"Marlin\"" 467 | msgstr "" 468 | 469 | #: qml_qt5/DiscoverOctoPrintAction.qml:608 qml/DiscoverOctoPrintAction.qml:596 470 | msgctxt "@label" 471 | msgid "" 472 | "Note: Printing UltiGCode using OctoPrint does not work. Setting G-code " 473 | "flavor to \"Marlin\" fixes this, but overrides material settings on your " 474 | "printer." 475 | msgstr "" 476 | 477 | #: qml_qt5/DiscoverOctoPrintAction.qml:622 qml/DiscoverOctoPrintAction.qml:609 478 | msgctxt "@action" 479 | msgid "Open in browser..." 480 | msgstr "" 481 | 482 | #: qml_qt5/DiscoverOctoPrintAction.qml:634 qml/DiscoverOctoPrintAction.qml:621 483 | msgctxt "@action:button" 484 | msgid "Disconnect" 485 | msgstr "" 486 | 487 | #: qml_qt5/DiscoverOctoPrintAction.qml:637 qml/DiscoverOctoPrintAction.qml:624 488 | msgctxt "@action:button" 489 | msgid "Connect" 490 | msgstr "" 491 | -------------------------------------------------------------------------------- /i18n/ru_RU/LC_MESSAGES/octoprint.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fieldOfView/Cura-OctoPrintPlugin/67518c3306d40fc5c4e70ab9828d06bbbac6cbd9/i18n/ru_RU/LC_MESSAGES/octoprint.mo -------------------------------------------------------------------------------- /i18n/ru_RU/octoprint.po: -------------------------------------------------------------------------------- 1 | # Russian translations for OctoPrint package 2 | # Английские переводы для пакета OctoPrint. 3 | # Copyright (C) 2021 THE OctoPrint'S COPYRIGHT HOLDER 4 | # This file is distributed under the same license as the OctoPrint Plugin package. 5 | # Василий Елькин , 2021. 6 | # 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: OctoPrint Plugin\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-10-10 15:31+0000\n" 12 | "PO-Revision-Date: 2021-11-05 21:55+0300\n" 13 | "Last-Translator: Elkin Vasily \n" 14 | "Language-Team: Russian \n" 15 | "Language: ru\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 20 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 21 | "X-Generator: Poedit 3.0\n" 22 | 23 | #: DiscoverOctoPrintAction.py:74 24 | msgctxt "@action" 25 | msgid "Connect OctoPrint" 26 | msgstr "Подключение OctoPrint" 27 | 28 | #: OctoPrintOutputDevice.py:254 29 | msgctxt "@action:button" 30 | msgid "Print with OctoPrint" 31 | msgstr "Печать через OctoPrint" 32 | 33 | #: OctoPrintOutputDevice.py:257 34 | msgctxt "@properties:tooltip" 35 | msgid "Print with OctoPrint" 36 | msgstr "Печать с помощью OctoPrint" 37 | 38 | #: OctoPrintOutputDevice.py:261 OctoPrintOutputDevice.py:1019 39 | #, python-brace-format 40 | msgctxt "@info:status" 41 | msgid "Connected to OctoPrint on {0}" 42 | msgstr "Соединиться с OctoPrint {0}" 43 | 44 | #: OctoPrintOutputDevice.py:488 45 | #, python-brace-format 46 | msgctxt "@info:status" 47 | msgid "Connecting to OctoPrint on {0}" 48 | msgstr "Соединение с OctoPrint {0}" 49 | 50 | #: OctoPrintOutputDevice.py:652 51 | msgctxt "@info:status" 52 | msgid "Waiting for OctoPrint to connect to the printer..." 53 | msgstr "Ожидайте пока OctoPrint соединиться с принтером…" 54 | 55 | #: OctoPrintOutputDevice.py:654 OctoPrintOutputDevice.py:785 56 | #: OctoPrintOutputDevice.py:1252 OctoPrintOutputDevice.py:1558 57 | #: OctoPrintOutputDevice.py:1648 OctoPrintOutputDevice.py:1677 58 | msgctxt "@label" 59 | msgid "OctoPrint" 60 | msgstr "OctoPrint" 61 | 62 | #: OctoPrintOutputDevice.py:662 63 | msgctxt "@action:button" 64 | msgid "Queue" 65 | msgstr "Очередь" 66 | 67 | #: OctoPrintOutputDevice.py:666 68 | #, fuzzy 69 | msgctxt "@action:tooltip" 70 | msgid "Stop waiting for the printer and queue the print job instead" 71 | msgstr "" 72 | "Перестать ожидать ответа от принтера и поставить в очередь задание на печать" 73 | 74 | #: OctoPrintOutputDevice.py:672 OctoPrintOutputDevice.py:793 75 | #: OctoPrintOutputDevice.py:1695 qml_qt5/ManualInstanceDialog.qml:319 76 | #: qml_qt5/UploadOptions.qml:107 qml/ManualInstanceDialog.qml:323 77 | #: qml/UploadOptions.qml:109 78 | msgctxt "@action:button" 79 | msgid "Cancel" 80 | msgstr "Отмена" 81 | 82 | #: OctoPrintOutputDevice.py:674 OctoPrintOutputDevice.py:795 83 | #: OctoPrintOutputDevice.py:1697 84 | #, fuzzy 85 | msgctxt "@action:tooltip" 86 | msgid "Abort the print job" 87 | msgstr "Прервать печать" 88 | 89 | #: OctoPrintOutputDevice.py:696 90 | msgctxt "@info:status" 91 | msgid "The printer is offline. Unable to start a new job." 92 | msgstr "Принтер не в сети. Невозможно начать печать." 93 | 94 | #: OctoPrintOutputDevice.py:700 95 | msgctxt "@info:status" 96 | msgid "OctoPrint is busy. Unable to start a new job." 97 | msgstr "OctoPrint занят. Невозможно начать печать." 98 | 99 | #: OctoPrintOutputDevice.py:707 OctoPrintOutputDevice.py:878 100 | #: OctoPrintOutputDevice.py:1806 101 | msgctxt "@label" 102 | msgid "OctoPrint error" 103 | msgstr "Ошибка OctoPrint" 104 | 105 | #: OctoPrintOutputDevice.py:711 106 | msgctxt "@action:button" 107 | msgid "Queue job" 108 | msgstr "Поставить задание в очередь" 109 | 110 | #: OctoPrintOutputDevice.py:715 111 | msgctxt "@action:tooltip" 112 | msgid "Queue this print job so it can be printed later" 113 | msgstr "" 114 | "Поставить это задание на печать в очередь, чтобы оно было напечатано позднее" 115 | 116 | #: OctoPrintOutputDevice.py:784 117 | #, fuzzy 118 | msgctxt "@info:status" 119 | msgid "Sending data to OctoPrint..." 120 | msgstr "Отправка данных в OctoPrint" 121 | 122 | #: OctoPrintOutputDevice.py:877 123 | msgctxt "@info:status" 124 | msgid "Unable to send data to OctoPrint." 125 | msgstr "Невозможно отправить данные в OctoPrint." 126 | 127 | #: OctoPrintOutputDevice.py:1121 128 | #, python-brace-format 129 | msgctxt "@info:status" 130 | msgid "OctoPrint on {0} does not allow access to the printer state" 131 | msgstr "OctoPrint {0} не предоставляет доступ к состоянию принтера" 132 | 133 | #: OctoPrintOutputDevice.py:1136 134 | #, python-brace-format 135 | msgctxt "@info:status" 136 | msgid "The printer connected to OctoPrint on {0} is not operational" 137 | msgstr "Принтер, подключенный к OctoPrint {0}, неработоспособен" 138 | 139 | #: OctoPrintOutputDevice.py:1148 OctoPrintOutputDevice.py:1296 140 | #, python-brace-format 141 | msgctxt "@info:status" 142 | msgid "OctoPrint on {0} is not running" 143 | msgstr "OctoPrint {0} не запущен" 144 | 145 | #: OctoPrintOutputDevice.py:1247 OctoPrintOutputDevice.py:1263 146 | #, fuzzy 147 | msgctxt "@info:status" 148 | msgid "Streaming file to the SD card of the printer..." 149 | msgstr "Передача файла на SD карту принтера" 150 | 151 | #: OctoPrintOutputDevice.py:1284 152 | #, python-brace-format 153 | msgctxt "@info:status" 154 | msgid "OctoPrint on {0} does not allow access to the job state" 155 | msgstr "" 156 | "OctoPrint {0} не предоставляет доступ к состоянию текущего задания на печать" 157 | 158 | #: OctoPrintOutputDevice.py:1393 159 | msgctxt "@info:error" 160 | msgid "" 161 | "You are not allowed to start print jobs on OctoPrint with the configured API " 162 | "key." 163 | msgstr "Вы не можете начать печать через OctoPrint с заданным ключём API." 164 | 165 | #: OctoPrintOutputDevice.py:1420 166 | msgctxt "@info:error" 167 | msgid "" 168 | "You are not allowed to control print jobs on OctoPrint with the configured " 169 | "API key." 170 | msgstr "" 171 | "Вы не можете контролировать задания на печать на OctoPrint с заданным ключём " 172 | "API." 173 | 174 | #: OctoPrintOutputDevice.py:1437 175 | msgctxt "@info:error" 176 | msgid "" 177 | "You are not allowed to send gcode commands to OctoPrint with the configured " 178 | "API key." 179 | msgstr "" 180 | "Вы не можете отправлять команды gcode на OctoPrint с заданным ключём API." 181 | 182 | #: OctoPrintOutputDevice.py:1459 OctoPrintOutputDevice.py:1466 183 | msgctxt "@label" 184 | msgid "Anonymous user" 185 | msgstr "Анонимный пользователь" 186 | 187 | #: OctoPrintOutputDevice.py:1473 188 | msgctxt "@label" 189 | msgid "Unknown user" 190 | msgstr "Неизвестный пользователь" 191 | 192 | #: OctoPrintOutputDevice.py:1479 193 | msgctxt "@info:error" 194 | msgid "You are not allowed to access to OctoPrint with the configured API key." 195 | msgstr "Вы не можете получить доступ к OctoPrint с заданным ключём API." 196 | 197 | #: OctoPrintOutputDevice.py:1493 198 | msgctxt "@info:error" 199 | msgid "" 200 | "You are not allowed to control printer connections on OctoPrint with the " 201 | "configured API key." 202 | msgstr "" 203 | "Вы не можете управлять подключениями принтера в OctoPrint с заданным ключём " 204 | "API." 205 | 206 | #: OctoPrintOutputDevice.py:1512 207 | msgctxt "@info:error" 208 | msgid "You are not allowed to access OctoPrint with the configured API key." 209 | msgstr "Вы не можете получить доступ к OctoPrint с заданным ключём API." 210 | 211 | #: OctoPrintOutputDevice.py:1526 OctoPrintOutputDevice.py:1603 212 | msgctxt "@info:error" 213 | msgid "OctoPrint responded with an unknown error" 214 | msgstr "" 215 | 216 | #: OctoPrintOutputDevice.py:1554 217 | msgctxt "@info:status" 218 | msgid "Storing data on OctoPrint" 219 | msgstr "Хранение данных на OctoPrint" 220 | 221 | #: OctoPrintOutputDevice.py:1578 222 | msgctxt "@info:error" 223 | msgid "" 224 | "You are not allowed to upload files to OctoPrint with the configured API key." 225 | msgstr "Вы не можете загружать файлы на OctoPrint с заданным ключём API." 226 | 227 | #: OctoPrintOutputDevice.py:1584 228 | #, fuzzy 229 | msgctxt "@info:error" 230 | msgid "Can't store a print job on SD card of the printer at this time." 231 | msgstr "Не удалось сохранить задание на печать на SD карте принтера." 232 | 233 | #: OctoPrintOutputDevice.py:1589 234 | #, fuzzy 235 | msgctxt "@info:error" 236 | msgid "" 237 | "Can't store the print job with the same name as the one that is currently " 238 | "printing." 239 | msgstr "" 240 | "Не удалось сохранить задание на печать с тем же именем, что и находящееся в " 241 | "состоянии печати." 242 | 243 | #: OctoPrintOutputDevice.py:1641 244 | #, python-brace-format 245 | msgctxt "@info:status" 246 | msgid "Saved to OctoPrint as {0}" 247 | msgstr "Сохранено на OctoPrint как {0}" 248 | 249 | #: OctoPrintOutputDevice.py:1646 250 | msgctxt "@info:status" 251 | msgid "Saved to OctoPrint" 252 | msgstr "Сохранено на OctoPrint" 253 | 254 | #: OctoPrintOutputDevice.py:1651 qml_qt5/OctoPrintComponents.qml:24 255 | #: qml/OctoPrintComponents.qml:22 256 | msgctxt "@action:button" 257 | msgid "OctoPrint..." 258 | msgstr "OctoPrint…" 259 | 260 | #: OctoPrintOutputDevice.py:1653 qml_qt5/OctoPrintComponents.qml:23 261 | #: qml/OctoPrintComponents.qml:21 262 | msgctxt "@info:tooltip" 263 | msgid "Open the OctoPrint web interface" 264 | msgstr "Открыть веб-интерфейс OctoPrint" 265 | 266 | #: OctoPrintOutputDevice.py:1675 267 | #, fuzzy 268 | msgctxt "@info:status" 269 | msgid "Waiting for OctoPrint to complete G-code analysis..." 270 | msgstr "Ожидайте пока OctoPrint выполнит анализ Gcode…" 271 | 272 | #: OctoPrintOutputDevice.py:1685 273 | msgctxt "@action:button" 274 | msgid "Print now" 275 | msgstr "Начать печать" 276 | 277 | #: OctoPrintOutputDevice.py:1689 278 | #, fuzzy 279 | msgctxt "@action:tooltip" 280 | msgid "Stop waiting for the G-code analysis and start printing immediately" 281 | msgstr "Прервать анализ Gcode и начать печать немедленно" 282 | 283 | #: qml_qt5/DiscoverOctoPrintAction.qml:77 qml/DiscoverOctoPrintAction.qml:76 284 | msgctxt "@title" 285 | msgid "Connect to OctoPrint" 286 | msgstr "Подключиться к OctoPrint" 287 | 288 | #: qml_qt5/DiscoverOctoPrintAction.qml:98 qml/DiscoverOctoPrintAction.qml:94 289 | msgctxt "@label" 290 | msgid "Select your OctoPrint instance from the list below." 291 | msgstr "Выберите ваше подключение OctoPrint из списка ниже." 292 | 293 | #: qml_qt5/DiscoverOctoPrintAction.qml:108 qml/DiscoverOctoPrintAction.qml:104 294 | msgctxt "@action:button" 295 | msgid "Add" 296 | msgstr "Добавить" 297 | 298 | #: qml_qt5/DiscoverOctoPrintAction.qml:118 qml/DiscoverOctoPrintAction.qml:114 299 | msgctxt "@action:button" 300 | msgid "Edit" 301 | msgstr "Править" 302 | 303 | #: qml_qt5/DiscoverOctoPrintAction.qml:134 qml/DiscoverOctoPrintAction.qml:130 304 | msgctxt "@action:button" 305 | msgid "Remove" 306 | msgstr "Удалить" 307 | 308 | #: qml_qt5/DiscoverOctoPrintAction.qml:142 qml/DiscoverOctoPrintAction.qml:138 309 | msgctxt "@action:button" 310 | msgid "Refresh" 311 | msgstr "Обновить" 312 | 313 | #: qml_qt5/DiscoverOctoPrintAction.qml:239 qml/DiscoverOctoPrintAction.qml:231 314 | msgctxt "@label" 315 | msgid "Automatically discover local OctoPrint instances" 316 | msgstr "Автоматически обнаруживать локальные подключения OctoPrint" 317 | 318 | #: qml_qt5/DiscoverOctoPrintAction.qml:277 qml/DiscoverOctoPrintAction.qml:267 319 | msgctxt "@label" 320 | msgid "Address" 321 | msgstr "Адрес" 322 | 323 | #: qml_qt5/DiscoverOctoPrintAction.qml:289 qml/DiscoverOctoPrintAction.qml:278 324 | msgctxt "@label" 325 | msgid "Version" 326 | msgstr "Версия" 327 | 328 | #: qml_qt5/DiscoverOctoPrintAction.qml:301 qml/DiscoverOctoPrintAction.qml:288 329 | msgctxt "@label" 330 | msgid "API Key" 331 | msgstr "Ключ API" 332 | 333 | #: qml_qt5/DiscoverOctoPrintAction.qml:319 qml/DiscoverOctoPrintAction.qml:308 334 | msgctxt "@action" 335 | msgid "Request..." 336 | msgstr "Запрос…" 337 | 338 | #: qml_qt5/DiscoverOctoPrintAction.qml:331 qml/DiscoverOctoPrintAction.qml:319 339 | #, fuzzy 340 | msgctxt "@label" 341 | msgid "Username" 342 | msgstr "Имя пользователя" 343 | 344 | #: qml_qt5/DiscoverOctoPrintAction.qml:405 qml/DiscoverOctoPrintAction.qml:392 345 | msgctxt "@label" 346 | msgid "Please enter the API key to access OctoPrint." 347 | msgstr "Пожалуйста, введите ключ API для доступа к OctoPrint." 348 | 349 | #: qml_qt5/DiscoverOctoPrintAction.qml:411 qml/DiscoverOctoPrintAction.qml:398 350 | msgctxt "@label" 351 | msgid "OctoPrint is not available." 352 | msgstr "OctoPrint недоступен." 353 | 354 | #: qml_qt5/DiscoverOctoPrintAction.qml:421 qml/DiscoverOctoPrintAction.qml:408 355 | #, fuzzy 356 | msgctxt "@label" 357 | msgid "The API key is invalid." 358 | msgstr "Ключ API недействителен." 359 | 360 | #: qml_qt5/DiscoverOctoPrintAction.qml:426 qml/DiscoverOctoPrintAction.qml:413 361 | msgctxt "@label" 362 | msgid "Checking the API key..." 363 | msgstr "Проверка ключа API…" 364 | 365 | #: qml_qt5/DiscoverOctoPrintAction.qml:429 qml/DiscoverOctoPrintAction.qml:416 366 | msgctxt "@label" 367 | msgid "You can get the API key through the OctoPrint web page." 368 | msgstr "Вы можете получить ключ API через веб-страницу OctoPrint." 369 | 370 | #: qml_qt5/DiscoverOctoPrintAction.qml:445 qml_qt5/UploadOptions.qml:91 371 | #: qml/DiscoverOctoPrintAction.qml:431 qml/UploadOptions.qml:93 372 | msgctxt "@label" 373 | msgid "Start print job after uploading" 374 | msgstr "Начать печать после загрузки задания" 375 | 376 | #: qml_qt5/DiscoverOctoPrintAction.qml:456 qml_qt5/UploadOptions.qml:98 377 | #: qml/DiscoverOctoPrintAction.qml:442 qml/UploadOptions.qml:100 378 | msgctxt "@label" 379 | msgid "Select print job after uploading" 380 | msgstr "Выбрать принтер после загрузки задания" 381 | 382 | #: qml_qt5/DiscoverOctoPrintAction.qml:471 qml/DiscoverOctoPrintAction.qml:457 383 | msgctxt "@label" 384 | msgid "Turn on printer with" 385 | msgstr "Включить принтер с помощью" 386 | 387 | #: qml_qt5/DiscoverOctoPrintAction.qml:530 qml/DiscoverOctoPrintAction.qml:519 388 | msgctxt "@label" 389 | msgid "Unknown plug" 390 | msgstr "Неизвестная розетка" 391 | 392 | #: qml_qt5/DiscoverOctoPrintAction.qml:552 qml/DiscoverOctoPrintAction.qml:541 393 | #, fuzzy 394 | msgctxt "@label" 395 | msgid "Connect to printer before sending print job" 396 | msgstr "Соединиться с принтером перед отправкой задания" 397 | 398 | #: qml_qt5/DiscoverOctoPrintAction.qml:563 qml/DiscoverOctoPrintAction.qml:552 399 | #, fuzzy 400 | msgctxt "@label" 401 | msgid "Store G-code on the SD card of the printer" 402 | msgstr "Хранить G-code на SD карте принтера" 403 | 404 | #: qml_qt5/DiscoverOctoPrintAction.qml:576 qml/DiscoverOctoPrintAction.qml:564 405 | #, fuzzy 406 | msgctxt "@label" 407 | msgid "" 408 | "Note: Transferring files to the printer SD card takes very long. Using this " 409 | "option is not recommended." 410 | msgstr "" 411 | "Обратите внимание: Передача файлов на SD карту принтера занимает много " 412 | "времени. Использование этой опции не рекомендовано." 413 | 414 | #: qml_qt5/DiscoverOctoPrintAction.qml:581 qml/DiscoverOctoPrintAction.qml:569 415 | #, fuzzy 416 | msgctxt "@label" 417 | msgid "Confirm print job options before sending" 418 | msgstr "Соединиться с принтером перед отправкой задания" 419 | 420 | #: qml_qt5/DiscoverOctoPrintAction.qml:591 qml/DiscoverOctoPrintAction.qml:579 421 | msgctxt "@label" 422 | msgid "Show webcam image" 423 | msgstr "Показывать видео с камеры" 424 | 425 | #: qml_qt5/DiscoverOctoPrintAction.qml:602 qml/DiscoverOctoPrintAction.qml:590 426 | #, fuzzy 427 | msgctxt "@label" 428 | msgid "Set G-code flavor to \"Marlin\"" 429 | msgstr "Установить “Marlin” в качестве Gcode flavor" 430 | 431 | #: qml_qt5/DiscoverOctoPrintAction.qml:608 qml/DiscoverOctoPrintAction.qml:596 432 | #, fuzzy 433 | msgctxt "@label" 434 | msgid "" 435 | "Note: Printing UltiGCode using OctoPrint does not work. Setting G-code " 436 | "flavor to \"Marlin\" fixes this, but overrides material settings on your " 437 | "printer." 438 | msgstr "" 439 | "Обратите внимание: Печать UltiGCode используя OctoPrint не работает. " 440 | "Использование “Marlin” в качестве Gcode flavor исправляет это, но " 441 | "переписывает настройки материала на вашем принтере." 442 | 443 | #: qml_qt5/DiscoverOctoPrintAction.qml:622 qml/DiscoverOctoPrintAction.qml:609 444 | msgctxt "@action" 445 | msgid "Open in browser..." 446 | msgstr "Открыть в браузере…" 447 | 448 | #: qml_qt5/DiscoverOctoPrintAction.qml:634 qml/DiscoverOctoPrintAction.qml:621 449 | msgctxt "@action:button" 450 | msgid "Disconnect" 451 | msgstr "Отсоединиться" 452 | 453 | #: qml_qt5/DiscoverOctoPrintAction.qml:637 qml/DiscoverOctoPrintAction.qml:624 454 | msgctxt "@action:button" 455 | msgid "Connect" 456 | msgstr "Соединиться" 457 | 458 | #: qml_qt5/ManualInstanceDialog.qml:24 qml/ManualInstanceDialog.qml:24 459 | msgctxt "@title:window" 460 | msgid "Manually added OctoPrint instance" 461 | msgstr "Подключения OctoPrint, добавленные вручную" 462 | 463 | #: qml_qt5/ManualInstanceDialog.qml:162 qml/ManualInstanceDialog.qml:166 464 | msgctxt "@label" 465 | msgid "Instance Name" 466 | msgstr "Имя подключения" 467 | 468 | #: qml_qt5/ManualInstanceDialog.qml:179 qml/ManualInstanceDialog.qml:183 469 | msgctxt "@label" 470 | msgid "IP Address or Hostname" 471 | msgstr "IP адрес или имя хоста" 472 | 473 | #: qml_qt5/ManualInstanceDialog.qml:197 qml/ManualInstanceDialog.qml:201 474 | msgctxt "@label" 475 | msgid "Port Number" 476 | msgstr "Номер порта" 477 | 478 | #: qml_qt5/ManualInstanceDialog.qml:225 qml_qt5/UploadOptions.qml:37 479 | #: qml/ManualInstanceDialog.qml:229 qml/UploadOptions.qml:39 480 | msgctxt "@label" 481 | msgid "Path" 482 | msgstr "Путь" 483 | 484 | #: qml_qt5/ManualInstanceDialog.qml:262 qml/ManualInstanceDialog.qml:266 485 | msgctxt "@label" 486 | msgid "" 487 | "In order to use HTTPS or a HTTP username and password, you need to configure " 488 | "a reverse proxy or another service." 489 | msgstr "" 490 | "Для того чтобы использовать HTTPS или имя пользователя и пароль HTTP, " 491 | "необходимо настроить обратный прокси или иной сервис." 492 | 493 | #: qml_qt5/ManualInstanceDialog.qml:267 qml/ManualInstanceDialog.qml:271 494 | msgctxt "@label" 495 | msgid "Use HTTPS" 496 | msgstr "Использовать HTTPS" 497 | 498 | #: qml_qt5/ManualInstanceDialog.qml:291 qml/ManualInstanceDialog.qml:295 499 | #, fuzzy 500 | msgctxt "@label" 501 | msgid "HTTP username" 502 | msgstr "HTTP имя пользователя" 503 | 504 | #: qml_qt5/ManualInstanceDialog.qml:304 qml/ManualInstanceDialog.qml:308 505 | msgctxt "@label" 506 | msgid "HTTP password" 507 | msgstr "HTTP пароль" 508 | 509 | #: qml_qt5/ManualInstanceDialog.qml:327 qml/ManualInstanceDialog.qml:331 510 | msgctxt "@action:button" 511 | msgid "Ok" 512 | msgstr "Ок" 513 | 514 | #: qml_qt5/UploadOptions.qml:14 qml/UploadOptions.qml:14 515 | #, fuzzy 516 | msgctxt "@action:button" 517 | msgid "Upload to OctoPrint Options" 518 | msgstr "Сохранено на OctoPrint" 519 | 520 | #: qml_qt5/UploadOptions.qml:56 qml/UploadOptions.qml:58 521 | msgctxt "@label" 522 | msgid "Filename" 523 | msgstr "" 524 | 525 | #: qml_qt5/UploadOptions.qml:78 qml/UploadOptions.qml:80 526 | msgctxt "@label" 527 | msgid "A file extenstion will be added automatically." 528 | msgstr "" 529 | 530 | #: qml_qt5/UploadOptions.qml:111 qml/UploadOptions.qml:113 531 | msgctxt "@action:button" 532 | msgid "OK" 533 | msgstr "" 534 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OctoPrint connection", 3 | "author": "fieldOfView", 4 | "version": "3.7.3", 5 | "description": "Enables networked printing and monitoring with OctoPrint", 6 | "api": 5, 7 | "supported_sdk_versions": ["5.0.0", "6.0.0", "7.0.0", "8.0.0"], 8 | "i18n-catalog": "octoprint" 9 | } 10 | -------------------------------------------------------------------------------- /qml/DiscoverOctoPrintAction.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import QtQuick 2.1 5 | import QtQuick.Controls 2.0 6 | 7 | import UM 1.5 as UM 8 | import Cura 1.0 as Cura 9 | 10 | 11 | Cura.MachineAction 12 | { 13 | id: base 14 | 15 | readonly property string defaultHTTP: "80" 16 | readonly property string defaultHTTPS: "443" 17 | 18 | anchors.fill: parent; 19 | property var selectedInstance: null 20 | property string activeMachineId: 21 | { 22 | if (Cura.MachineManager.activeMachineId != undefined) 23 | { 24 | return Cura.MachineManager.activeMachineId; 25 | } 26 | else if (Cura.MachineManager.activeMachine !== null) 27 | { 28 | return Cura.MachineManager.activeMachine.id; 29 | } 30 | 31 | CuraApplication.log("There does not seem to be an active machine"); 32 | return ""; 33 | } 34 | 35 | onVisibleChanged: 36 | { 37 | if(!visible) 38 | { 39 | manager.cancelApiKeyRequest(); 40 | } 41 | } 42 | 43 | function boolCheck(value) //Hack to ensure a good match between python and qml. 44 | { 45 | if(value == "True") 46 | { 47 | return true 48 | }else if(value == "False" || value == undefined) 49 | { 50 | return false 51 | } 52 | else 53 | { 54 | return value 55 | } 56 | } 57 | 58 | Column 59 | { 60 | anchors.fill: parent; 61 | id: discoverOctoPrintAction 62 | 63 | spacing: UM.Theme.getSize("default_margin").height 64 | width: parent.width 65 | 66 | UM.I18nCatalog { id: catalog; name:"octoprint" } 67 | 68 | Item 69 | { 70 | width: parent.width 71 | height: pageTitle.height 72 | 73 | UM.Label 74 | { 75 | id: pageTitle 76 | text: catalog.i18nc("@title", "Connect to OctoPrint") 77 | font: UM.Theme.getFont("large_bold") 78 | } 79 | 80 | UM.Label 81 | { 82 | id: pluginVersion 83 | anchors.bottom: pageTitle.bottom 84 | anchors.right: parent.right 85 | text: manager.pluginVersion 86 | font.pointSize: 8 87 | } 88 | } 89 | 90 | UM.Label 91 | { 92 | id: pageDescription 93 | width: parent.width 94 | text: catalog.i18nc("@label", "Select your OctoPrint instance from the list below.") 95 | } 96 | 97 | Row 98 | { 99 | spacing: UM.Theme.getSize("default_lining").width 100 | 101 | Cura.SecondaryButton 102 | { 103 | id: addButton 104 | text: catalog.i18nc("@action:button", "Add"); 105 | onClicked: 106 | { 107 | manualInstanceDialog.showDialog("", "", base.defaultHTTP, "/", false, "", ""); 108 | } 109 | } 110 | 111 | Cura.SecondaryButton 112 | { 113 | id: editButton 114 | text: catalog.i18nc("@action:button", "Edit") 115 | enabled: base.selectedInstance != null && base.selectedInstance.getProperty("manual") == "true" 116 | onClicked: 117 | { 118 | manualInstanceDialog.showDialog( 119 | base.selectedInstance.name, base.selectedInstance.ipAddress, 120 | base.selectedInstance.port, base.selectedInstance.path, 121 | base.selectedInstance.getProperty("useHttps") == "true", 122 | base.selectedInstance.getProperty("userName"), base.selectedInstance.getProperty("password") 123 | ); 124 | } 125 | } 126 | 127 | Cura.SecondaryButton 128 | { 129 | id: removeButton 130 | text: catalog.i18nc("@action:button", "Remove") 131 | enabled: base.selectedInstance != null && base.selectedInstance.getProperty("manual") == "true" 132 | onClicked: manager.removeManualInstance(base.selectedInstance.name) 133 | } 134 | 135 | Cura.SecondaryButton 136 | { 137 | id: rediscoverButton 138 | text: catalog.i18nc("@action:button", "Refresh") 139 | enabled: useZeroconf.checked 140 | onClicked: manager.startDiscovery() 141 | } 142 | } 143 | 144 | Row 145 | { 146 | width: parent.width 147 | spacing: UM.Theme.getSize("default_margin").width 148 | 149 | Rectangle 150 | { 151 | width: Math.floor(parent.width * 0.4) 152 | height: base.height - (parent.y + UM.Theme.getSize("default_margin").height) 153 | 154 | color: UM.Theme.getColor("main_background") 155 | border.width: UM.Theme.getSize("default_lining").width 156 | border.color: UM.Theme.getColor("thick_lining") 157 | 158 | ListView 159 | { 160 | id: listview 161 | 162 | clip: true 163 | ScrollBar.vertical: UM.ScrollBar {} 164 | 165 | anchors.fill: parent 166 | anchors.margins: UM.Theme.getSize("default_lining").width 167 | 168 | model: manager.discoveredInstances 169 | onModelChanged: 170 | { 171 | var selectedId = manager.instanceId; 172 | for(var i = 0; i < model.length; i++) { 173 | if(model[i].getId() == selectedId) 174 | { 175 | currentIndex = i; 176 | return 177 | } 178 | } 179 | currentIndex = -1; 180 | } 181 | 182 | currentIndex: activeIndex 183 | onCurrentIndexChanged: 184 | { 185 | base.selectedInstance = listview.model[currentIndex]; 186 | apiCheckDelay.throttledCheck(); 187 | } 188 | 189 | Component.onCompleted: manager.startDiscovery() 190 | 191 | delegate: Rectangle 192 | { 193 | height: childrenRect.height 194 | color: ListView.isCurrentItem ? UM.Theme.getColor("text_selection") : UM.Theme.getColor("main_background") 195 | width: parent.width 196 | UM.Label 197 | { 198 | anchors.left: parent.left 199 | anchors.leftMargin: UM.Theme.getSize("default_margin").width 200 | anchors.right: parent.right 201 | text: listview.model[index].name 202 | elide: Text.ElideRight 203 | font.italic: listview.model[index].key == manager.instanceId 204 | wrapMode: Text.NoWrap 205 | } 206 | 207 | MouseArea 208 | { 209 | anchors.fill: parent; 210 | onClicked: 211 | { 212 | if(!parent.ListView.isCurrentItem) 213 | { 214 | parent.ListView.view.currentIndex = index; 215 | } 216 | } 217 | } 218 | } 219 | } 220 | 221 | Item 222 | { 223 | id: objectListFooter 224 | 225 | width: parent.width 226 | anchors.bottom: parent.bottom 227 | 228 | UM.CheckBox 229 | { 230 | id: useZeroconf 231 | text: catalog.i18nc("@label", "Automatically discover local OctoPrint instances") 232 | checked: boolCheck(UM.Preferences.getValue("octoprint/use_zeroconf")) 233 | onClicked: 234 | { 235 | if(checked != boolCheck(UM.Preferences.getValue("octoprint/use_zeroconf"))) 236 | { 237 | UM.Preferences.setValue("octoprint/use_zeroconf", checked); 238 | manager.startDiscovery(); 239 | } 240 | } 241 | } 242 | } 243 | } 244 | 245 | Column 246 | { 247 | width: Math.floor(parent.width * 0.6) 248 | spacing: UM.Theme.getSize("default_margin").height 249 | UM.Label 250 | { 251 | visible: base.selectedInstance != null 252 | width: parent.width 253 | text: base.selectedInstance ? base.selectedInstance.name : "" 254 | font: UM.Theme.getFont("large_bold") 255 | elide: Text.ElideRight 256 | } 257 | Grid 258 | { 259 | visible: base.selectedInstance != null 260 | width: parent.width 261 | columns: 2 262 | rowSpacing: UM.Theme.getSize("default_lining").height 263 | verticalItemAlignment: Grid.AlignVCenter 264 | UM.Label 265 | { 266 | width: Math.floor(parent.width * 0.2) 267 | text: catalog.i18nc("@label", "Address") 268 | } 269 | UM.Label 270 | { 271 | width: Math.floor(parent.width * 0.75) 272 | wrapMode: Text.WordWrap 273 | text: base.selectedInstance ? "%1:%2".arg(base.selectedInstance.ipAddress).arg(String(base.selectedInstance.port)) : "" 274 | } 275 | UM.Label 276 | { 277 | width: Math.floor(parent.width * 0.2) 278 | text: catalog.i18nc("@label", "Version") 279 | } 280 | UM.Label 281 | { 282 | width: Math.floor(parent.width * 0.75) 283 | text: base.selectedInstance ? base.selectedInstance.octoPrintVersion : "" 284 | } 285 | UM.Label 286 | { 287 | width: Math.floor(parent.width * 0.2) 288 | text: catalog.i18nc("@label", "API Key") 289 | } 290 | Row 291 | { 292 | spacing: UM.Theme.getSize("default_margin").width 293 | Cura.TextField 294 | { 295 | id: apiKey 296 | width: Math.floor(parent.parent.width * (requestApiKey.visible ? 0.5 : 0.8) - UM.Theme.getSize("default_margin").width) 297 | leftPadding: 0 298 | rightPadding: 0 299 | echoMode: activeFocus ? TextInput.Normal : TextInput.Password 300 | onTextChanged: apiCheckDelay.throttledCheck() 301 | } 302 | 303 | Cura.SecondaryButton 304 | { 305 | id: requestApiKey 306 | visible: manager.instanceSupportsAppKeys 307 | enabled: !manager.instanceApiKeyAccepted 308 | text: catalog.i18nc("@action", "Request...") 309 | onClicked: 310 | { 311 | manager.requestApiKey(base.selectedInstance.getId()); 312 | } 313 | } 314 | 315 | } 316 | UM.Label 317 | { 318 | width: Math.floor(parent.width * 0.2) 319 | text: catalog.i18nc("@label", "Username") 320 | } 321 | UM.Label 322 | { 323 | width: Math.floor(parent.width * 0.75) 324 | text: base.selectedInstance ? base.selectedInstance.octoPrintUserName : "" 325 | } 326 | 327 | Connections 328 | { 329 | target: base 330 | function onSelectedInstanceChanged() 331 | { 332 | if(base.selectedInstance) 333 | { 334 | manager.probeAppKeySupport(base.selectedInstance.getId()); 335 | apiCheckDelay.lastKey = "\0"; 336 | apiKey.text = manager.getApiKey(base.selectedInstance.getId()); 337 | apiKey.select(0,0); 338 | } 339 | } 340 | } 341 | Connections 342 | { 343 | target: manager 344 | function onAppKeyReceived() 345 | { 346 | apiCheckDelay.lastKey = "\0"; 347 | apiKey.text = manager.getApiKey(base.selectedInstance.getId()) 348 | apiKey.select(0,0); 349 | } 350 | } 351 | Timer 352 | { 353 | id: apiCheckDelay 354 | interval: 500 355 | 356 | property bool checkOnTrigger: false 357 | property string lastKey: "\0" 358 | 359 | function throttledCheck() 360 | { 361 | checkOnTrigger = true; 362 | restart(); 363 | } 364 | function check() 365 | { 366 | if(apiKey.text != lastKey) 367 | { 368 | lastKey = apiKey.text; 369 | manager.testApiKey(base.selectedInstance.getId(), apiKey.text); 370 | checkOnTrigger = false; 371 | restart(); 372 | } 373 | } 374 | onTriggered: 375 | { 376 | if(checkOnTrigger) 377 | { 378 | check(); 379 | } 380 | } 381 | } 382 | } 383 | 384 | UM.Label 385 | { 386 | visible: base.selectedInstance != null && text != "" 387 | text: 388 | { 389 | var result = "" 390 | if (apiKey.text == "") 391 | { 392 | result = catalog.i18nc("@label", "Please enter the API key to access OctoPrint."); 393 | } 394 | else 395 | { 396 | if(manager.instanceInError) 397 | { 398 | return catalog.i18nc("@label", "OctoPrint is not available.") 399 | } 400 | if(manager.instanceResponded) 401 | { 402 | if(manager.instanceApiKeyAccepted) 403 | { 404 | return ""; 405 | } 406 | else 407 | { 408 | result = catalog.i18nc("@label", "The API key is invalid."); 409 | } 410 | } 411 | else 412 | { 413 | return catalog.i18nc("@label", "Checking the API key...") 414 | } 415 | } 416 | result += " " + catalog.i18nc("@label", "You can get the API key through the OctoPrint web page."); 417 | return result; 418 | } 419 | width: parent.width - UM.Theme.getSize("default_margin").width 420 | } 421 | 422 | Column 423 | { 424 | visible: base.selectedInstance != null 425 | width: parent.width 426 | spacing: UM.Theme.getSize("default_lining").height 427 | 428 | UM.CheckBox 429 | { 430 | id: autoPrintCheckBox 431 | text: catalog.i18nc("@label", "Start print job after uploading") 432 | enabled: manager.instanceApiKeyAccepted 433 | checked: Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_auto_print") != "false" 434 | onClicked: 435 | { 436 | manager.setContainerMetaDataEntry(activeMachineId, "octoprint_auto_print", String(checked)) 437 | } 438 | } 439 | UM.CheckBox 440 | { 441 | id: autoSelectCheckBox 442 | text: catalog.i18nc("@label", "Select print job after uploading") 443 | enabled: manager.instanceApiKeyAccepted && !autoPrintCheckBox.checked 444 | checked: Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_auto_select") == "true" || (!enabled && autoPrintCheckBox.checked) 445 | onClicked: 446 | { 447 | manager.setContainerMetaDataEntry(activeMachineId, "octoprint_auto_select", String(checked)) 448 | } 449 | } 450 | Row 451 | { 452 | spacing: UM.Theme.getSize("default_margin").width 453 | 454 | UM.CheckBox 455 | { 456 | id: autoPowerControlCheckBox 457 | text: catalog.i18nc("@label", "Turn on printer with") 458 | visible: autoPowerControlPlugs.visible 459 | enabled: autoPrintCheckBox.checked || autoSelectCheckBox.checked 460 | anchors.verticalCenter: autoPowerControlPlugs.verticalCenter 461 | checked: manager.instanceApiKeyAccepted && Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_power_control") == "true" 462 | onClicked: 463 | { 464 | manager.setContainerMetaDataEntry(activeMachineId, "octoprint_power_control", String(checked)) 465 | } 466 | } 467 | Connections 468 | { 469 | target: manager 470 | function onInstanceAvailablePowerPluginsChanged() 471 | { 472 | autoPowerControlPlugsModel.populateModel() 473 | } 474 | } 475 | 476 | Cura.ComboBox 477 | { 478 | id: autoPowerControlPlugs 479 | visible: manager.instanceApiKeyAccepted && model.count > 0 480 | enabled: autoPrintCheckBox.checked 481 | property bool populatingModel: false 482 | textRole: "text" 483 | model: ListModel 484 | { 485 | id: autoPowerControlPlugsModel 486 | 487 | Component.onCompleted: populateModel() 488 | 489 | function populateModel() 490 | { 491 | autoPowerControlPlugs.populatingModel = true; 492 | clear(); 493 | 494 | var current_index = -1; 495 | 496 | var power_plugs = manager.instanceAvailablePowerPlugins; 497 | var current_key = Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_power_plug"); 498 | if (current_key == "" && power_plugs.length > 0) 499 | { 500 | current_key = power_plugs[0].key; 501 | manager.setContainerMetaDataEntry(activeMachineId, "octoprint_power_plug", current_key); 502 | } 503 | 504 | for(var i in power_plugs) 505 | { 506 | append({ 507 | key: power_plugs[i].key, 508 | text: power_plugs[i].text 509 | }); 510 | if(power_plugs[i].key == current_key) 511 | { 512 | current_index = i; 513 | } 514 | } 515 | if(current_index == -1 && current_key != "") 516 | { 517 | append({ 518 | key: current_key, 519 | text: catalog.i18nc("@label", "Unknown plug") 520 | }); 521 | current_index = count - 1; 522 | } 523 | 524 | autoPowerControlPlugs.currentIndex = current_index; 525 | autoPowerControlPlugs.populatingModel = false; 526 | } 527 | } 528 | onActivated: 529 | { 530 | if(!populatingModel && model.get(index)) 531 | { 532 | manager.setContainerMetaDataEntry(activeMachineId, "octoprint_power_plug", model.get(index).key); 533 | } 534 | } 535 | 536 | } 537 | } 538 | UM.CheckBox 539 | { 540 | id: autoConnectCheckBox 541 | text: catalog.i18nc("@label", "Connect to printer before sending print job") 542 | enabled: manager.instanceApiKeyAccepted && autoPrintCheckBox.checked && !autoPowerControlCheckBox.checked 543 | checked: enabled && Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_auto_connect") == "true" 544 | onClicked: 545 | { 546 | manager.setContainerMetaDataEntry(activeMachineId, "octoprint_auto_connect", String(checked)) 547 | } 548 | } 549 | UM.CheckBox 550 | { 551 | id: storeOnSdCheckBox 552 | text: catalog.i18nc("@label", "Store G-code on the SD card of the printer") 553 | enabled: manager.instanceSupportsSd 554 | checked: manager.instanceApiKeyAccepted && Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_store_sd") == "true" 555 | onClicked: 556 | { 557 | manager.setContainerMetaDataEntry(activeMachineId, "octoprint_store_sd", String(checked)) 558 | } 559 | } 560 | UM.Label 561 | { 562 | visible: storeOnSdCheckBox.checked 563 | width: parent.width 564 | text: catalog.i18nc("@label", "Note: Transferring files to the printer SD card takes very long. Using this option is not recommended.") 565 | } 566 | UM.CheckBox 567 | { 568 | id: confirmUploadOptionsCheckBox 569 | text: catalog.i18nc("@label", "Confirm print job options before sending") 570 | checked: Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_confirm_upload_options") == "true" 571 | onClicked: 572 | { 573 | manager.setContainerMetaDataEntry(activeMachineId, "octoprint_confirm_upload_options", String(checked)) 574 | } 575 | } 576 | UM.CheckBox 577 | { 578 | id: showCameraCheckBox 579 | text: catalog.i18nc("@label", "Show webcam image") 580 | enabled: manager.instanceSupportsCamera 581 | checked: manager.instanceApiKeyAccepted && Cura.ContainerManager.getContainerMetaDataEntry(activeMachineId, "octoprint_show_camera") != "false" 582 | onClicked: 583 | { 584 | manager.setContainerMetaDataEntry(activeMachineId, "octoprint_show_camera", String(checked)) 585 | } 586 | } 587 | UM.CheckBox 588 | { 589 | id: fixGcodeFlavor 590 | text: catalog.i18nc("@label", "Set G-code flavor to \"Marlin\"") 591 | checked: true 592 | visible: machineGCodeFlavorProvider.properties.value == "UltiGCode" 593 | } 594 | UM.Label 595 | { 596 | text: catalog.i18nc("@label", "Note: Printing UltiGCode using OctoPrint does not work. Setting G-code flavor to \"Marlin\" fixes this, but overrides material settings on your printer.") 597 | width: parent.width - UM.Theme.getSize("default_margin").width 598 | visible: fixGcodeFlavor.visible 599 | } 600 | } 601 | 602 | Flow 603 | { 604 | visible: base.selectedInstance != null 605 | spacing: UM.Theme.getSize("default_margin").width 606 | 607 | Cura.SecondaryButton 608 | { 609 | text: catalog.i18nc("@action", "Open in browser...") 610 | onClicked: manager.openWebPage(base.selectedInstance.baseURL) 611 | } 612 | 613 | Cura.SecondaryButton 614 | { 615 | text: 616 | { 617 | if (base.selectedInstance !== null) 618 | { 619 | if (base.selectedInstance.getId() == manager.instanceId && manager.instanceApiKeyAccepted) 620 | { 621 | return catalog.i18nc("@action:button", "Disconnect"); 622 | } 623 | } 624 | return catalog.i18nc("@action:button", "Connect") 625 | } 626 | enabled: base.selectedInstance !== null && apiKey.text != "" && manager.instanceApiKeyAccepted 627 | onClicked: 628 | { 629 | if(base.selectedInstance.getId() == manager.instanceId && manager.instanceApiKeyAccepted) { 630 | manager.setInstanceId("") 631 | } 632 | else 633 | { 634 | manager.setInstanceId(base.selectedInstance.getId()) 635 | manager.setApiKey(apiKey.text) 636 | 637 | if(fixGcodeFlavor.visible) 638 | { 639 | manager.applyGcodeFlavorFix(fixGcodeFlavor.checked) 640 | } 641 | } 642 | completed() 643 | } 644 | } 645 | } 646 | } 647 | } 648 | } 649 | 650 | UM.SettingPropertyProvider 651 | { 652 | id: machineGCodeFlavorProvider 653 | 654 | containerStackId: activeMachineId 655 | key: "machine_gcode_flavor" 656 | watchedProperties: [ "value" ] 657 | storeIndex: 4 658 | } 659 | 660 | ManualInstanceDialog 661 | { 662 | id: manualInstanceDialog 663 | } 664 | } -------------------------------------------------------------------------------- /qml/ManualInstanceDialog.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import QtQuick 2.15 5 | import QtQuick.Controls 2.0 6 | 7 | import UM 1.5 as UM 8 | import Cura 1.0 as Cura 9 | 10 | 11 | UM.Dialog 12 | { 13 | id: manualInstanceDialog 14 | property string previousName 15 | property string previousAddress 16 | property alias nameText: nameField.text 17 | property alias addressText: addressField.text 18 | property alias portText: portField.text 19 | property alias pathText: pathField.text 20 | property alias userNameText: userNameField.text 21 | property alias passwordText: passwordField.text 22 | property alias httpsChecked: httpsCheckbox.checked 23 | 24 | title: catalog.i18nc("@title:window", "Manually added OctoPrint instance") 25 | 26 | buttonSpacing: UM.Theme.getSize("default_margin").width 27 | minimumWidth: 400 * screenScaleFactor 28 | minimumHeight: 300 * screenScaleFactor 29 | width: minimumWidth 30 | height: minimumHeight 31 | 32 | property int firstColumnWidth: Math.floor(width * 0.4) - 2 * margin 33 | property int secondColumnWidth: Math.floor(width * 0.6) - 2 * margin 34 | 35 | signal showDialog(string name, string address, string port, string path_, bool useHttps, string userName, string password) 36 | onShowDialog: function(name, address, port, path_, useHttps, userName, password) 37 | { 38 | previousName = name; 39 | nameText = name; 40 | addressText = address; 41 | previousAddress = address; 42 | portText = port; 43 | pathText = path_; 44 | httpsChecked = useHttps; 45 | userNameText = userName; 46 | passwordText = password; 47 | 48 | manualInstanceDialog.show(); 49 | if (nameText != "") 50 | { 51 | nameField.forceActiveFocus(); 52 | } 53 | else 54 | { 55 | addressField.forceActiveFocus(); 56 | } 57 | } 58 | 59 | onAccepted: 60 | { 61 | if(previousName != nameText) 62 | { 63 | manager.removeManualInstance(previousName); 64 | } 65 | if(portText == "") 66 | { 67 | portText = (!httpsChecked) ? base.defaultHTTP : base.defaultHTTPS; // default http or https port 68 | } 69 | if(pathText.substr(0,1) != "/") 70 | { 71 | pathText = "/" + pathText; // ensure absolute path 72 | } 73 | manager.setManualInstance( 74 | nameText, 75 | addressText, 76 | parseInt(portText), 77 | pathText, 78 | httpsChecked, 79 | userNameText, 80 | passwordText 81 | ); 82 | } 83 | 84 | function parseAddressField() 85 | { 86 | var useAddressForName = false 87 | if (nameText == manualInstanceDialog.previousAddress || nameText == "") 88 | { 89 | useAddressForName = true 90 | } 91 | manualInstanceDialog.previousAddress = addressText 92 | 93 | var index = addressText.indexOf("://") 94 | if(index >= 0) 95 | { 96 | var protocol = addressText.substr(0,index) 97 | if(protocol.toLowerCase() == "http" && httpsChecked) { 98 | httpsChecked = false 99 | if(portField.text == defaultHTTPS) 100 | { 101 | portField.text = defaultHTTP 102 | } 103 | } 104 | else if(protocol.toLowerCase() == "https" && !httpsChecked) { 105 | httpsChecked = true 106 | if(portField.text == defaultHTTP) 107 | { 108 | portField.text = defaultHTTPS 109 | } 110 | } 111 | addressText = addressText.substr(index + 3) 112 | } 113 | 114 | index = addressText.indexOf("@") 115 | if(index >= 0) 116 | { 117 | var auth = addressText.substr(0,index).split(":") 118 | userNameText = auth[0] 119 | if(auth.length>1) 120 | { 121 | passwordText = auth[1] 122 | } 123 | addressText = addressText.substr(index+1) 124 | } 125 | 126 | index = addressText.indexOf("/") 127 | if(index >= 0) 128 | { 129 | pathField.text = addressText.substr(index) 130 | addressText = addressText.substr(0,index) 131 | } 132 | 133 | index = addressText.indexOf(":") 134 | if(index >= 0) 135 | { 136 | var port = parseInt(addressText.substr(index+1)) 137 | if (!isNaN(port)) { 138 | portField.text = port.toString() 139 | } 140 | addressText = addressText.substr(0,index) 141 | } 142 | 143 | if(useAddressForName) 144 | { 145 | nameText = addressText 146 | } 147 | } 148 | 149 | Grid 150 | { 151 | Timer 152 | { 153 | id: parseAddressFieldTimer 154 | interval: 1000 155 | onTriggered: manualInstanceDialog.parseAddressField() 156 | } 157 | 158 | columns: 2 159 | width: parent.width 160 | verticalItemAlignment: Grid.AlignVCenter 161 | rowSpacing: UM.Theme.getSize("default_lining").height 162 | columnSpacing: UM.Theme.getSize("default_margin").width 163 | 164 | UM.Label 165 | { 166 | text: catalog.i18nc("@label","Instance Name") 167 | width: manualInstanceDialog.firstColumnWidth 168 | } 169 | 170 | Cura.TextField 171 | { 172 | id: nameField 173 | maximumLength: 20 174 | width: manualInstanceDialog.secondColumnWidth 175 | validator: RegularExpressionValidator 176 | { 177 | regularExpression: /[a-zA-Z0-9\.\-\_\:\[\]]*/ 178 | } 179 | } 180 | 181 | UM.Label 182 | { 183 | text: catalog.i18nc("@label","IP Address or Hostname") 184 | width: manualInstanceDialog.firstColumnWidth 185 | } 186 | 187 | Cura.TextField 188 | { 189 | id: addressField 190 | maximumLength: 253 191 | width: manualInstanceDialog.secondColumnWidth 192 | validator: RegularExpressionValidator 193 | { 194 | regularExpression: /[a-zA-Z0-9\.\-\_\:\/\@]*/ 195 | } 196 | onTextChanged: parseAddressFieldTimer.restart() 197 | } 198 | 199 | UM.Label 200 | { 201 | text: catalog.i18nc("@label","Port Number") 202 | width: manualInstanceDialog.firstColumnWidth 203 | } 204 | 205 | Cura.TextField 206 | { 207 | id: portField 208 | maximumLength: 5 209 | width: manualInstanceDialog.secondColumnWidth 210 | validator: RegularExpressionValidator 211 | { 212 | regularExpression: /[0-9]*/ 213 | } 214 | onTextChanged: 215 | { 216 | if(httpsChecked && text == base.defaultHTTP) 217 | { 218 | httpsChecked = false 219 | } 220 | else if(!httpsChecked && text == base.defaultHTTPS) 221 | { 222 | httpsChecked = true 223 | } 224 | } 225 | } 226 | 227 | UM.Label 228 | { 229 | text: catalog.i18nc("@label","Path") 230 | width: manualInstanceDialog.firstColumnWidth 231 | } 232 | 233 | Cura.TextField 234 | { 235 | id: pathField 236 | maximumLength: 30 237 | width: manualInstanceDialog.secondColumnWidth 238 | validator: RegularExpressionValidator 239 | { 240 | regularExpression: /[a-zA-Z0-9\.\-\_\/]*/ 241 | } 242 | } 243 | 244 | Item 245 | { 246 | width: 1 247 | height: UM.Theme.getSize("default_margin").height 248 | } 249 | 250 | Item 251 | { 252 | width: 1 253 | height: UM.Theme.getSize("default_margin").height 254 | } 255 | 256 | Item 257 | { 258 | width: 1 259 | height: 1 260 | } 261 | 262 | UM.Label 263 | { 264 | wrapMode: Text.WordWrap 265 | width: manualInstanceDialog.secondColumnWidth 266 | text: catalog.i18nc("@label","In order to use HTTPS or a HTTP username and password, you need to configure a reverse proxy or another service.") 267 | } 268 | 269 | UM.Label 270 | { 271 | text: catalog.i18nc("@label","Use HTTPS") 272 | width: manualInstanceDialog.firstColumnWidth 273 | } 274 | 275 | UM.CheckBox 276 | { 277 | id: httpsCheckbox 278 | width: height 279 | height: userNameField.height 280 | onClicked: 281 | { 282 | if(checked && portField.text == base.defaultHTTP) 283 | { 284 | portField.text = base.defaultHTTPS 285 | } 286 | else if(!checked && portField.text == base.defaultHTTPS) 287 | { 288 | portField.text = base.defaultHTTP 289 | } 290 | } 291 | } 292 | 293 | UM.Label 294 | { 295 | text: catalog.i18nc("@label","HTTP username") 296 | width: manualInstanceDialog.firstColumnWidth 297 | } 298 | 299 | Cura.TextField 300 | { 301 | id: userNameField 302 | maximumLength: 64 303 | width: manualInstanceDialog.secondColumnWidth 304 | } 305 | 306 | UM.Label 307 | { 308 | text: catalog.i18nc("@label","HTTP password") 309 | width: manualInstanceDialog.firstColumnWidth 310 | } 311 | 312 | Cura.TextField 313 | { 314 | id: passwordField 315 | maximumLength: 64 316 | width: manualInstanceDialog.secondColumnWidth 317 | echoMode: TextInput.PasswordEchoOnEdit 318 | } 319 | } 320 | 321 | rightButtons: [ 322 | Cura.SecondaryButton { 323 | text: catalog.i18nc("@action:button","Cancel") 324 | onClicked: 325 | { 326 | manualInstanceDialog.reject() 327 | manualInstanceDialog.hide() 328 | } 329 | }, 330 | Cura.PrimaryButton { 331 | text: catalog.i18nc("@action:button", "Ok") 332 | onClicked: 333 | { 334 | if (parseAddressFieldTimer.running) 335 | { 336 | parseAddressFieldTimer.stop() 337 | manualInstanceDialog.parseAddressField() 338 | } 339 | manualInstanceDialog.accept() 340 | manualInstanceDialog.hide() 341 | } 342 | enabled: manualInstanceDialog.nameText.trim() != "" && manualInstanceDialog.addressText.trim() != "" 343 | } 344 | ] 345 | } -------------------------------------------------------------------------------- /qml/MonitorItem.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import QtQuick 2.10 5 | import QtQuick.Controls 2.3 6 | 7 | import UM 1.5 as UM 8 | import Cura 1.1 as Cura 9 | import OctoPrintPlugin 1.0 as OctoPrintPlugin 10 | 11 | Component 12 | { 13 | id: monitorItem 14 | 15 | Item 16 | { 17 | property var webcamsModel: OutputDevice != null ? OutputDevice.webcamsModel : null 18 | property int activeIndex: 0 19 | 20 | OctoPrintPlugin.NetworkMJPGImage 21 | { 22 | id: cameraImage 23 | visible: OutputDevice != null ? OutputDevice.showCamera : false 24 | 25 | source: (OutputDevice != null && activeIndex < webcamsModel.count) ? webcamsModel.items[activeIndex].stream_url : "" 26 | rotation: (OutputDevice != null && activeIndex < webcamsModel.count) ? webcamsModel.items[activeIndex].rotation : 0 27 | mirror: (OutputDevice != null && activeIndex < webcamsModel.count) ? webcamsModel.items[activeIndex].mirror : false 28 | 29 | property real maximumWidthMinusSidebar: maximumWidth - sidebar.width - 2 * UM.Theme.getSize("default_margin").width 30 | property real maximumZoom: 2 31 | property bool rotatedImage: (rotation / 90) % 2 32 | property bool proportionalHeight: 33 | { 34 | if (imageHeight == 0 || maximumHeight == 0) 35 | { 36 | return true; 37 | } 38 | if (!rotatedImage) 39 | { 40 | return (imageWidth / imageHeight) > (maximumWidthMinusSidebar / maximumHeight); 41 | } 42 | else 43 | { 44 | return (imageWidth / imageHeight) > (maximumHeight / maximumWidthMinusSidebar); 45 | } 46 | } 47 | property real _width: 48 | { 49 | if (!rotatedImage) 50 | { 51 | return Math.min(maximumWidthMinusSidebar, imageWidth * screenScaleFactor * maximumZoom); 52 | } 53 | else 54 | { 55 | return Math.min(maximumHeight, imageWidth * screenScaleFactor * maximumZoom); 56 | } 57 | } 58 | property real _height: 59 | { 60 | if (!rotatedImage) 61 | { 62 | return Math.min(maximumHeight, imageHeight * screenScaleFactor * maximumZoom); 63 | } 64 | else 65 | { 66 | return Math.min(maximumWidth, imageHeight * screenScaleFactor * maximumZoom); 67 | } 68 | } 69 | width: proportionalHeight ? _width : imageWidth * _height / imageHeight 70 | height: !proportionalHeight ? _height : imageHeight * _width / imageWidth 71 | anchors.horizontalCenter: horizontalCenterItem.horizontalCenter 72 | anchors.verticalCenter: parent.verticalCenter 73 | 74 | Component.onCompleted: 75 | { 76 | if (visible) 77 | { 78 | start(); 79 | } 80 | } 81 | onVisibleChanged: 82 | { 83 | if (visible) 84 | { 85 | start(); 86 | } else 87 | { 88 | stop(); 89 | } 90 | } 91 | } 92 | 93 | Row 94 | { 95 | id: webcamSelectorContainer 96 | spacing: Math.round(UM.Theme.getSize("default_margin").width / 2) 97 | visible: (webcamsModel != null) ? webcamsModel.count > 1 : false 98 | 99 | anchors 100 | { 101 | horizontalCenter: cameraImage.horizontalCenter 102 | top: cameraImage.top 103 | topMargin: UM.Theme.getSize("default_margin").height 104 | } 105 | 106 | Repeater 107 | { 108 | id: webcamSelector 109 | model: webcamsModel 110 | 111 | delegate: Cura.ActionButton 112 | { 113 | id: control 114 | text: model.name.toUpperCase() 115 | checkable: true 116 | checked: cameraImage.source == model.stream_url 117 | 118 | anchors.verticalCenter: parent.verticalCenter 119 | ButtonGroup.group: webcamSelectorGroup 120 | height: UM.Theme.getSize("setting_control").height 121 | 122 | onClicked: activeIndex = index 123 | 124 | background: Rectangle 125 | { 126 | implicitHeight: control.height 127 | radius: UM.Theme.getSize("action_button_radius").width 128 | 129 | color: (control.checked) ? UM.Theme.getColor("main_window_header_button_background_active") : UM.Theme.getColor("main_window_header_button_background_hovered") 130 | opacity: (control.checked || control.hovered) ? 1 : 0.5 131 | } 132 | 133 | contentItem: UM.Label 134 | { 135 | anchors.horizontalCenter: parent.horizontalCenter 136 | anchors.verticalCenter: parent.verticalCenter 137 | height: control.height 138 | 139 | text: control.text 140 | font: UM.Theme.getFont("medium") 141 | color: (control.checked) ? UM.Theme.getColor("main_window_header_button_text_active") : (control.hovered) ? UM.Theme.getColor("main_window_header_button_text_hovered") : UM.Theme.getColor("main_window_header_button_text_inactive") 142 | } 143 | } 144 | } 145 | 146 | ButtonGroup { id: webcamSelectorGroup } 147 | } 148 | 149 | Item 150 | { 151 | id: horizontalCenterItem 152 | anchors.left: parent.left 153 | anchors.right: sidebar.left 154 | } 155 | 156 | Cura.RoundedRectangle 157 | { 158 | id: sidebarBackground 159 | 160 | width: UM.Theme.getSize("print_setup_widget").width 161 | anchors 162 | { 163 | right: parent.right 164 | top: parent.top 165 | topMargin: UM.Theme.getSize("default_margin").height 166 | bottom: actionsPanel.top 167 | bottomMargin: UM.Theme.getSize("default_margin").height 168 | } 169 | 170 | border.width: UM.Theme.getSize("default_lining").width 171 | border.color: UM.Theme.getColor("lining") 172 | color: UM.Theme.getColor("main_background") 173 | 174 | cornerSide: Cura.RoundedRectangle.Direction.Left 175 | radius: UM.Theme.getSize("default_radius").width 176 | } 177 | 178 | Cura.PrintMonitor { 179 | id: sidebar 180 | 181 | width: UM.Theme.getSize("print_setup_widget").width - UM.Theme.getSize("default_margin").height * 2 182 | anchors 183 | { 184 | top: parent.top 185 | bottom: actionsPanel.top 186 | leftMargin: UM.Theme.getSize("default_margin").width 187 | right: parent.right 188 | rightMargin: UM.Theme.getSize("default_margin").width 189 | } 190 | } 191 | 192 | Cura.RoundedRectangle 193 | { 194 | id: actionsPanel 195 | 196 | border.width: UM.Theme.getSize("default_lining").width 197 | border.color: UM.Theme.getColor("lining") 198 | color: UM.Theme.getColor("main_background") 199 | 200 | cornerSide: Cura.RoundedRectangle.Direction.Left 201 | radius: UM.Theme.getSize("default_radius").width 202 | 203 | anchors.bottom: parent.bottom 204 | anchors.right: parent.right 205 | 206 | anchors.bottomMargin: UM.Theme.getSize("default_margin").width 207 | anchors.topMargin: UM.Theme.getSize("default_margin").height 208 | 209 | width: UM.Theme.getSize("print_setup_widget").width 210 | height: monitorButton.height 211 | 212 | // MonitorButton is actually the bottom footer panel. 213 | Cura.MonitorButton 214 | { 215 | id: monitorButton 216 | width: parent.width 217 | anchors.top: parent.top 218 | } 219 | } 220 | } 221 | } -------------------------------------------------------------------------------- /qml/OctoPrintComponents.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import UM 1.2 as UM 5 | import Cura 1.0 as Cura 6 | 7 | import QtQuick 2.2 8 | import QtQuick.Controls 2.0 9 | 10 | Item 11 | { 12 | id: base 13 | 14 | property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 15 | property bool octoPrintConnected: printerConnected && Cura.MachineManager.printerOutputDevices[0].toString().indexOf("OctoPrintOutputDevice") == 0 16 | 17 | Cura.SecondaryButton 18 | { 19 | objectName: "openOctoPrintButton" 20 | height: UM.Theme.getSize("save_button_save_to_button").height 21 | tooltip: catalog.i18nc("@info:tooltip", "Open the OctoPrint web interface") 22 | text: catalog.i18nc("@action:button", "OctoPrint...") 23 | onClicked: manager.openWebPage(Cura.MachineManager.printerOutputDevices[0].baseURL) 24 | visible: octoPrintConnected 25 | } 26 | 27 | UM.I18nCatalog{id: catalog; name:"octoprint"} 28 | } -------------------------------------------------------------------------------- /qml/UploadOptions.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import QtQuick 2.15 5 | import QtQuick.Controls 2.0 6 | 7 | import UM 1.5 as UM 8 | import Cura 1.0 as Cura 9 | 10 | UM.Dialog 11 | { 12 | id: uploadOptions 13 | 14 | title: catalog.i18nc("@action:button", "Upload to OctoPrint Options") 15 | 16 | minimumWidth: screenScaleFactor * 400 17 | minimumHeight: screenScaleFactor * 150 18 | 19 | buttonSpacing: UM.Theme.getSize("default_margin").width 20 | 21 | onAccepted: manager.acceptOptionsDialog() 22 | 23 | Column { 24 | anchors.fill: parent 25 | 26 | UM.I18nCatalog{id: catalog; name:"octoprint"} 27 | 28 | Grid 29 | { 30 | columns: 2 31 | width: parent.width 32 | verticalItemAlignment: Grid.AlignVCenter 33 | rowSpacing: UM.Theme.getSize("default_lining").height 34 | columnSpacing: UM.Theme.getSize("default_margin").width 35 | 36 | UM.Label 37 | { 38 | id: pathLabel 39 | text: catalog.i18nc("@label", "Path") 40 | } 41 | 42 | Cura.TextField { 43 | id: pathField 44 | text: manager.filePath 45 | maximumLength: 256 46 | width: parent.width - Math.max(pathLabel.width, fileLabel.width) - UM.Theme.getSize("default_margin").width 47 | horizontalAlignment: TextInput.AlignLeft 48 | validator: RegularExpressionValidator 49 | { 50 | regularExpression: /.*/ 51 | } 52 | onTextChanged: manager.filePath = text 53 | } 54 | 55 | UM.Label 56 | { 57 | id: fileLabel 58 | text: catalog.i18nc("@label", "Filename") 59 | } 60 | 61 | Cura.TextField { 62 | id: nameField 63 | text: manager.fileName 64 | maximumLength: 100 65 | width: parent.width - Math.max(pathLabel.width, fileLabel.width) - UM.Theme.getSize("default_margin").width 66 | horizontalAlignment: TextInput.AlignLeft 67 | validator: RegularExpressionValidator 68 | { 69 | regularExpression: /[^\/]*/ 70 | } 71 | onTextChanged: manager.fileName = text 72 | } 73 | Item 74 | { 75 | width: 1 76 | height: UM.Theme.getSize("default_margin").height 77 | } 78 | UM.Label 79 | { 80 | text: catalog.i18nc("@label", "A file extenstion will be added automatically.") 81 | } 82 | } 83 | 84 | Item 85 | { 86 | width: 1 87 | height: UM.Theme.getSize("default_margin").height 88 | } 89 | 90 | UM.CheckBox 91 | { 92 | id: autoPrintCheckBox 93 | text: catalog.i18nc("@label", "Start print job after uploading") 94 | checked: manager.autoPrint 95 | onClicked: manager.autoPrint = checked 96 | } 97 | UM.CheckBox 98 | { 99 | id: autoSelectCheckBox 100 | text: catalog.i18nc("@label", "Select print job after uploading") 101 | enabled: !autoPrintCheckBox.checked 102 | checked: autoPrintCheckBox.checked || manager.autoSelect 103 | onClicked: manager.autoSelect = checked 104 | } 105 | } 106 | 107 | rightButtons: [ 108 | Cura.SecondaryButton { 109 | text: catalog.i18nc("@action:button", "Cancel") 110 | onClicked: uploadOptions.reject() 111 | }, 112 | Cura.PrimaryButton { 113 | text: catalog.i18nc("@action:button", "OK") 114 | onClicked: uploadOptions.accept() 115 | } 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /qml_qt5/ManualInstanceDialog.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import UM 1.2 as UM 5 | import Cura 1.0 as Cura 6 | 7 | import QtQuick 2.2 8 | import QtQuick.Controls 1.1 9 | 10 | 11 | UM.Dialog 12 | { 13 | id: manualInstanceDialog 14 | property string previousName 15 | property string previousAddress 16 | property alias nameText: nameField.text 17 | property alias addressText: addressField.text 18 | property alias portText: portField.text 19 | property alias pathText: pathField.text 20 | property alias userNameText: userNameField.text 21 | property alias passwordText: passwordField.text 22 | property alias httpsChecked: httpsCheckbox.checked 23 | 24 | title: catalog.i18nc("@title:window", "Manually added OctoPrint instance") 25 | 26 | minimumWidth: 400 * screenScaleFactor 27 | minimumHeight: 280 * screenScaleFactor 28 | width: minimumWidth 29 | height: minimumHeight 30 | 31 | signal showDialog(string name, string address, string port, string path_, bool useHttps, string userName, string password) 32 | onShowDialog: 33 | { 34 | previousName = name; 35 | nameText = name; 36 | addressText = address; 37 | previousAddress = address; 38 | portText = port; 39 | pathText = path_; 40 | httpsChecked = useHttps; 41 | userNameText = userName; 42 | passwordText = password; 43 | 44 | manualInstanceDialog.show(); 45 | if (nameText != "") 46 | { 47 | nameField.forceActiveFocus(); 48 | } 49 | else 50 | { 51 | addressField.forceActiveFocus(); 52 | } 53 | } 54 | 55 | onAccepted: 56 | { 57 | if(previousName != nameText) 58 | { 59 | manager.removeManualInstance(previousName); 60 | } 61 | if(portText == "") 62 | { 63 | portText = (!httpsChecked) ? base.defaultHTTP : base.defaultHTTPS; // default http or https port 64 | } 65 | if(pathText.substr(0,1) != "/") 66 | { 67 | pathText = "/" + pathText; // ensure absolute path 68 | } 69 | manager.setManualInstance( 70 | nameText, 71 | addressText, 72 | parseInt(portText), 73 | pathText, 74 | httpsChecked, 75 | userNameText, 76 | passwordText 77 | ); 78 | } 79 | 80 | function parseAddressField() 81 | { 82 | var useAddressForName = false 83 | if (nameText == manualInstanceDialog.previousAddress || nameText == "") 84 | { 85 | useAddressForName = true 86 | } 87 | manualInstanceDialog.previousAddress = addressText 88 | 89 | var index = addressText.indexOf("://") 90 | if(index >= 0) 91 | { 92 | var protocol = addressText.substr(0,index) 93 | if(protocol.toLowerCase() == "http" && httpsChecked) { 94 | httpsChecked = false 95 | if(portField.text == defaultHTTPS) 96 | { 97 | portField.text = defaultHTTP 98 | } 99 | } 100 | else if(protocol.toLowerCase() == "https" && !httpsChecked) { 101 | httpsChecked = true 102 | if(portField.text == defaultHTTP) 103 | { 104 | portField.text = defaultHTTPS 105 | } 106 | } 107 | addressText = addressText.substr(index + 3) 108 | } 109 | 110 | index = addressText.indexOf("@") 111 | if(index >= 0) 112 | { 113 | var auth = addressText.substr(0,index).split(":") 114 | userNameText = auth[0] 115 | if(auth.length>1) 116 | { 117 | passwordText = auth[1] 118 | } 119 | addressText = addressText.substr(index+1) 120 | } 121 | 122 | index = addressText.indexOf("/") 123 | if(index >= 0) 124 | { 125 | pathField.text = addressText.substr(index) 126 | addressText = addressText.substr(0,index) 127 | } 128 | 129 | index = addressText.indexOf(":") 130 | if(index >= 0) 131 | { 132 | var port = parseInt(addressText.substr(index+1)) 133 | if (!isNaN(port)) { 134 | portField.text = port.toString() 135 | } 136 | addressText = addressText.substr(0,index) 137 | } 138 | 139 | if(useAddressForName) 140 | { 141 | nameText = addressText 142 | } 143 | } 144 | 145 | Grid 146 | { 147 | Timer 148 | { 149 | id: parseAddressFieldTimer 150 | interval: 1000 151 | onTriggered: manualInstanceDialog.parseAddressField() 152 | } 153 | 154 | columns: 2 155 | width: parent.width 156 | verticalItemAlignment: Grid.AlignVCenter 157 | rowSpacing: UM.Theme.getSize("default_lining").height 158 | columnSpacing: UM.Theme.getSize("default_margin").width 159 | 160 | Label 161 | { 162 | text: catalog.i18nc("@label","Instance Name") 163 | width: Math.floor(parent.width * 0.4) 164 | } 165 | 166 | TextField 167 | { 168 | id: nameField 169 | maximumLength: 20 170 | width: Math.floor(parent.width * 0.6) 171 | validator: RegExpValidator 172 | { 173 | regExp: /[a-zA-Z0-9\.\-\_\:\[\]]*/ 174 | } 175 | } 176 | 177 | Label 178 | { 179 | text: catalog.i18nc("@label","IP Address or Hostname") 180 | width: Math.floor(parent.width * 0.4) 181 | } 182 | 183 | TextField 184 | { 185 | id: addressField 186 | maximumLength: 253 187 | width: Math.floor(parent.width * 0.6) 188 | validator: RegExpValidator 189 | { 190 | regExp: /[a-zA-Z0-9\.\-\_\:\/\@]*/ 191 | } 192 | onTextChanged: parseAddressFieldTimer.restart() 193 | } 194 | 195 | Label 196 | { 197 | text: catalog.i18nc("@label","Port Number") 198 | width: Math.floor(parent.width * 0.4) 199 | } 200 | 201 | TextField 202 | { 203 | id: portField 204 | maximumLength: 5 205 | width: Math.floor(parent.width * 0.6) 206 | validator: RegExpValidator 207 | { 208 | regExp: /[0-9]*/ 209 | } 210 | onTextChanged: 211 | { 212 | if(httpsChecked && text == base.defaultHTTP) 213 | { 214 | httpsChecked = false 215 | } 216 | else if(!httpsChecked && text == base.defaultHTTPS) 217 | { 218 | httpsChecked = true 219 | } 220 | } 221 | } 222 | 223 | Label 224 | { 225 | text: catalog.i18nc("@label","Path") 226 | width: Math.floor(parent.width * 0.4) 227 | } 228 | 229 | TextField 230 | { 231 | id: pathField 232 | maximumLength: 30 233 | width: Math.floor(parent.width * 0.6) 234 | validator: RegExpValidator 235 | { 236 | regExp: /[a-zA-Z0-9\.\-\_\/]*/ 237 | } 238 | } 239 | 240 | Item 241 | { 242 | width: 1 243 | height: UM.Theme.getSize("default_margin").height 244 | } 245 | 246 | Item 247 | { 248 | width: 1 249 | height: UM.Theme.getSize("default_margin").height 250 | } 251 | 252 | Item 253 | { 254 | width: 1 255 | height: 1 256 | } 257 | 258 | Label 259 | { 260 | wrapMode: Text.WordWrap 261 | width: Math.floor(parent.width * 0.6) 262 | text: catalog.i18nc("@label","In order to use HTTPS or a HTTP username and password, you need to configure a reverse proxy or another service.") 263 | } 264 | 265 | Label 266 | { 267 | text: catalog.i18nc("@label","Use HTTPS") 268 | width: Math.floor(parent.width * 0.4) 269 | } 270 | 271 | CheckBox 272 | { 273 | id: httpsCheckbox 274 | width: height 275 | height: userNameField.height 276 | onClicked: 277 | { 278 | if(checked && portField.text == base.defaultHTTP) 279 | { 280 | portField.text = base.defaultHTTPS 281 | } 282 | else if(!checked && portField.text == base.defaultHTTPS) 283 | { 284 | portField.text = base.defaultHTTP 285 | } 286 | } 287 | } 288 | 289 | Label 290 | { 291 | text: catalog.i18nc("@label","HTTP username") 292 | width: Math.floor(parent.width * 0.4) 293 | } 294 | 295 | TextField 296 | { 297 | id: userNameField 298 | maximumLength: 64 299 | width: Math.floor(parent.width * 0.6) 300 | } 301 | 302 | Label 303 | { 304 | text: catalog.i18nc("@label","HTTP password") 305 | width: Math.floor(parent.width * 0.4) 306 | } 307 | 308 | TextField 309 | { 310 | id: passwordField 311 | maximumLength: 64 312 | width: Math.floor(parent.width * 0.6) 313 | echoMode: TextInput.PasswordEchoOnEdit 314 | } 315 | } 316 | 317 | rightButtons: [ 318 | Button { 319 | text: catalog.i18nc("@action:button","Cancel") 320 | onClicked: 321 | { 322 | manualInstanceDialog.reject() 323 | manualInstanceDialog.hide() 324 | } 325 | }, 326 | Button { 327 | text: catalog.i18nc("@action:button", "Ok") 328 | onClicked: 329 | { 330 | if (parseAddressFieldTimer.running) 331 | { 332 | parseAddressFieldTimer.stop() 333 | manualInstanceDialog.parseAddressField() 334 | } 335 | manualInstanceDialog.accept() 336 | manualInstanceDialog.hide() 337 | } 338 | enabled: manualInstanceDialog.nameText.trim() != "" && manualInstanceDialog.addressText.trim() != "" 339 | isDefault: true 340 | } 341 | ] 342 | } -------------------------------------------------------------------------------- /qml_qt5/MonitorItem3x.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import QtQuick 2.7 5 | import QtQuick.Controls 2.3 6 | 7 | import UM 1.3 as UM 8 | import OctoPrintPlugin 1.0 as OctoPrintPlugin 9 | 10 | Component 11 | { 12 | Item 13 | { 14 | property var webcamsModel: OutputDevice != null ? OutputDevice.webcamsModel : null 15 | property int activeIndex: 0 16 | 17 | OctoPrintPlugin.NetworkMJPGImage 18 | { 19 | id: cameraImage 20 | visible: OutputDevice != null ? OutputDevice.showCamera : false 21 | 22 | source: (OutputDevice != null && activeIndex in webcamsModel.items) ? webcamsModel.items[activeIndex].stream_url : "" 23 | rotation: (OutputDevice != null && activeIndex in webcamsModel.items) ? webcamsModel.items[activeIndex].rotation : 0 24 | mirror: (OutputDevice != null && activeIndex in webcamsModel.items) ? webcamsModel.items[activeIndex].mirror : false 25 | 26 | property real maximumZoom: 2 27 | property bool rotatedImage: (rotation / 90) % 2 28 | property bool proportionalHeight: 29 | { 30 | if (imageHeight == 0 || maximumHeight == 0) 31 | { 32 | return true; 33 | } 34 | if (!rotatedImage) 35 | { 36 | return (imageWidth / imageHeight) > (maximumWidth / maximumHeight); 37 | } 38 | else 39 | { 40 | return (imageWidth / imageHeight) > (maximumHeight / maximumWidth); 41 | } 42 | } 43 | property real _width: 44 | { 45 | if (!rotatedImage) 46 | { 47 | return Math.min(maximumWidth, imageWidth * screenScaleFactor * maximumZoom); 48 | } 49 | else 50 | { 51 | return Math.min(maximumHeight, imageWidth * screenScaleFactor * maximumZoom); 52 | } 53 | } 54 | property real _height: 55 | { 56 | if (!rotatedImage) 57 | { 58 | return Math.min(maximumHeight, imageHeight * screenScaleFactor * maximumZoom); 59 | } 60 | else 61 | { 62 | return Math.min(maximumWidth, imageHeight * screenScaleFactor * maximumZoom); 63 | } 64 | } 65 | width: proportionalHeight ? _width : imageWidth * _height / imageHeight 66 | height: !proportionalHeight ? _height : imageHeight * _width / imageWidth 67 | anchors.horizontalCenter: parent.horizontalCenter 68 | anchors.verticalCenter: parent.verticalCenter 69 | 70 | Component.onCompleted: 71 | { 72 | if (visible) 73 | { 74 | start(); 75 | } 76 | } 77 | onVisibleChanged: 78 | { 79 | if (visible) 80 | { 81 | start(); 82 | } else 83 | { 84 | stop(); 85 | } 86 | } 87 | } 88 | 89 | Row 90 | { 91 | id: webcamSelectorContainer 92 | spacing: Math.round(UM.Theme.getSize("default_margin").width / 2) 93 | visible: (webcamsModel != null) ? webcamsModel.rowCount() > 1 : false 94 | 95 | anchors 96 | { 97 | horizontalCenter: cameraImage.horizontalCenter 98 | top: cameraImage.top 99 | topMargin: UM.Theme.getSize("default_margin").height 100 | } 101 | 102 | Repeater 103 | { 104 | id: webcamSelector 105 | model: webcamsModel 106 | 107 | delegate: Button 108 | { 109 | id: control 110 | text: model != null ? model.name : "" 111 | checkable: true 112 | checked: cameraImage.source == model.stream_url 113 | 114 | anchors.verticalCenter: parent.verticalCenter 115 | ButtonGroup.group: webcamSelectorGroup 116 | height: UM.Theme.getSize("sidebar_header_mode_toggle").height 117 | 118 | background: Rectangle 119 | { 120 | color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") : control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button") 121 | border.width: control.checked ? UM.Theme.getSize("default_lining").width * 2 : UM.Theme.getSize("default_lining").width 122 | border.color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_border") : control.hovered ? UM.Theme.getColor("action_button_hovered_border"): UM.Theme.getColor("action_button_border") 123 | } 124 | 125 | contentItem: Label 126 | { 127 | text: control.text 128 | font: UM.Theme.getFont("default") 129 | horizontalAlignment: Text.AlignHCenter 130 | verticalAlignment: Text.AlignVCenter 131 | renderType: Text.NativeRendering 132 | elide: Text.ElideRight 133 | color: (control.pressed) ? UM.Theme.getColor("action_button_active_text") : (control.hovered) ? UM.Theme.getColor("action_button_hovered_text") : UM.Theme.getColor("action_button_text"); 134 | } 135 | 136 | onClicked: activeIndex = index 137 | } 138 | } 139 | 140 | ButtonGroup { id: webcamSelectorGroup } 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /qml_qt5/MonitorItem4x.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import QtQuick 2.7 5 | import QtQuick.Controls 2.3 6 | 7 | import UM 1.3 as UM 8 | import Cura 1.0 as Cura 9 | import OctoPrintPlugin 1.0 as OctoPrintPlugin 10 | 11 | Component 12 | { 13 | id: monitorItem 14 | 15 | Item 16 | { 17 | property var webcamsModel: OutputDevice != null ? OutputDevice.webcamsModel : null 18 | property int activeIndex: 0 19 | 20 | OctoPrintPlugin.NetworkMJPGImage 21 | { 22 | id: cameraImage 23 | visible: OutputDevice != null ? OutputDevice.showCamera : false 24 | 25 | source: (OutputDevice != null && activeIndex < webcamsModel.count) ? webcamsModel.items[activeIndex].stream_url : "" 26 | rotation: (OutputDevice != null && activeIndex < webcamsModel.count) ? webcamsModel.items[activeIndex].rotation : 0 27 | mirror: (OutputDevice != null && activeIndex < webcamsModel.count) ? webcamsModel.items[activeIndex].mirror : false 28 | 29 | property real maximumWidthMinusSidebar: maximumWidth - sidebar.width - 2 * UM.Theme.getSize("default_margin").width 30 | property real maximumZoom: 2 31 | property bool rotatedImage: (rotation / 90) % 2 32 | property bool proportionalHeight: 33 | { 34 | if (imageHeight == 0 || maximumHeight == 0) 35 | { 36 | return true; 37 | } 38 | if (!rotatedImage) 39 | { 40 | return (imageWidth / imageHeight) > (maximumWidthMinusSidebar / maximumHeight); 41 | } 42 | else 43 | { 44 | return (imageWidth / imageHeight) > (maximumHeight / maximumWidthMinusSidebar); 45 | } 46 | } 47 | property real _width: 48 | { 49 | if (!rotatedImage) 50 | { 51 | return Math.min(maximumWidthMinusSidebar, imageWidth * screenScaleFactor * maximumZoom); 52 | } 53 | else 54 | { 55 | return Math.min(maximumHeight, imageWidth * screenScaleFactor * maximumZoom); 56 | } 57 | } 58 | property real _height: 59 | { 60 | if (!rotatedImage) 61 | { 62 | return Math.min(maximumHeight, imageHeight * screenScaleFactor * maximumZoom); 63 | } 64 | else 65 | { 66 | return Math.min(maximumWidth, imageHeight * screenScaleFactor * maximumZoom); 67 | } 68 | } 69 | width: proportionalHeight ? _width : imageWidth * _height / imageHeight 70 | height: !proportionalHeight ? _height : imageHeight * _width / imageWidth 71 | anchors.horizontalCenter: horizontalCenterItem.horizontalCenter 72 | anchors.verticalCenter: parent.verticalCenter 73 | 74 | Component.onCompleted: 75 | { 76 | if (visible) 77 | { 78 | start(); 79 | } 80 | } 81 | onVisibleChanged: 82 | { 83 | if (visible) 84 | { 85 | start(); 86 | } else 87 | { 88 | stop(); 89 | } 90 | } 91 | } 92 | 93 | Row 94 | { 95 | id: webcamSelectorContainer 96 | spacing: Math.round(UM.Theme.getSize("default_margin").width / 2) 97 | visible: (webcamsModel != null) ? webcamsModel.count > 1 : false 98 | 99 | anchors 100 | { 101 | horizontalCenter: cameraImage.horizontalCenter 102 | top: cameraImage.top 103 | topMargin: UM.Theme.getSize("default_margin").height 104 | } 105 | 106 | Repeater 107 | { 108 | id: webcamSelector 109 | model: webcamsModel 110 | 111 | delegate: Button 112 | { 113 | id: control 114 | text: model.name.toUpperCase() 115 | checkable: true 116 | checked: cameraImage.source == model.stream_url 117 | 118 | anchors.verticalCenter: parent.verticalCenter 119 | ButtonGroup.group: webcamSelectorGroup 120 | height: UM.Theme.getSize("main_window_header_button").height 121 | 122 | onClicked: activeIndex = index 123 | 124 | background: Rectangle 125 | { 126 | id: backgroundRectangle 127 | implicitHeight: control.height 128 | radius: UM.Theme.getSize("action_button_radius").width 129 | 130 | color: (control.checked) ? UM.Theme.getColor("main_window_header_button_background_active") : UM.Theme.getColor("main_window_header_button_background_hovered") 131 | opacity: (control.checked || control.hovered) ? 1 : 0.5 132 | } 133 | 134 | contentItem: Label 135 | { 136 | id: contents 137 | anchors.horizontalCenter: parent.horizontalCenter 138 | anchors.verticalCenter: parent.verticalCenter 139 | height: control.height 140 | leftPadding: UM.Theme.getSize("default_margin").width 141 | rightPadding: leftPadding 142 | 143 | text: control.text 144 | font: UM.Theme.getFont("medium") 145 | color: (control.checked) ? UM.Theme.getColor("main_window_header_button_text_active") : (control.hovered) ? UM.Theme.getColor("main_window_header_button_text_hovered") : UM.Theme.getColor("main_window_header_button_text_inactive") 146 | } 147 | } 148 | } 149 | 150 | ButtonGroup { id: webcamSelectorGroup } 151 | } 152 | 153 | Item 154 | { 155 | id: horizontalCenterItem 156 | anchors.left: parent.left 157 | anchors.right: sidebar.left 158 | } 159 | 160 | Cura.RoundedRectangle 161 | { 162 | id: sidebarBackground 163 | 164 | width: UM.Theme.getSize("print_setup_widget").width 165 | anchors 166 | { 167 | right: parent.right 168 | top: parent.top 169 | topMargin: UM.Theme.getSize("default_margin").height 170 | bottom: actionsPanel.top 171 | bottomMargin: UM.Theme.getSize("default_margin").height 172 | } 173 | 174 | border.width: UM.Theme.getSize("default_lining").width 175 | border.color: UM.Theme.getColor("lining") 176 | color: UM.Theme.getColor("main_background") 177 | 178 | cornerSide: Cura.RoundedRectangle.Direction.Left 179 | radius: UM.Theme.getSize("default_radius").width 180 | } 181 | 182 | Cura.PrintMonitor { 183 | id: sidebar 184 | 185 | width: UM.Theme.getSize("print_setup_widget").width - UM.Theme.getSize("default_margin").height * 2 186 | anchors 187 | { 188 | top: parent.top 189 | bottom: actionsPanel.top 190 | leftMargin: UM.Theme.getSize("default_margin").width 191 | right: parent.right 192 | rightMargin: UM.Theme.getSize("default_margin").width 193 | } 194 | } 195 | 196 | Cura.RoundedRectangle 197 | { 198 | id: actionsPanel 199 | 200 | border.width: UM.Theme.getSize("default_lining").width 201 | border.color: UM.Theme.getColor("lining") 202 | color: UM.Theme.getColor("main_background") 203 | 204 | cornerSide: Cura.RoundedRectangle.Direction.Left 205 | radius: UM.Theme.getSize("default_radius").width 206 | 207 | anchors.bottom: parent.bottom 208 | anchors.right: parent.right 209 | 210 | anchors.bottomMargin: UM.Theme.getSize("default_margin").width 211 | anchors.topMargin: UM.Theme.getSize("default_margin").height 212 | 213 | width: UM.Theme.getSize("print_setup_widget").width 214 | height: monitorButton.height 215 | 216 | // MonitorButton is actually the bottom footer panel. 217 | Cura.MonitorButton 218 | { 219 | id: monitorButton 220 | width: parent.width 221 | anchors.top: parent.top 222 | } 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /qml_qt5/OctoPrintComponents.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import UM 1.2 as UM 5 | import Cura 1.0 as Cura 6 | 7 | import QtQuick 2.2 8 | import QtQuick.Controls 1.1 9 | import QtQuick.Layouts 1.1 10 | import QtQuick.Window 2.1 11 | 12 | Item 13 | { 14 | id: base 15 | 16 | property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 17 | property bool octoPrintConnected: printerConnected && Cura.MachineManager.printerOutputDevices[0].toString().indexOf("OctoPrintOutputDevice") == 0 18 | 19 | Button 20 | { 21 | objectName: "openOctoPrintButton" 22 | height: UM.Theme.getSize("save_button_save_to_button").height 23 | tooltip: catalog.i18nc("@info:tooltip", "Open the OctoPrint web interface") 24 | text: catalog.i18nc("@action:button", "OctoPrint...") 25 | style: 26 | { 27 | if(UM.Theme.styles.hasOwnProperty("print_setup_action_button")) { 28 | return UM.Theme.styles.print_setup_action_button 29 | } 30 | else 31 | { 32 | return UM.Theme.styles.sidebar_action_button 33 | } 34 | } 35 | onClicked: manager.openWebPage(Cura.MachineManager.printerOutputDevices[0].baseURL) 36 | visible: octoPrintConnected 37 | } 38 | 39 | UM.I18nCatalog{id: catalog; name:"octoprint"} 40 | } -------------------------------------------------------------------------------- /qml_qt5/UploadOptions.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Aldo Hoeben / fieldOfView 2 | // OctoPrintPlugin is released under the terms of the AGPLv3 or higher. 3 | 4 | import UM 1.2 as UM 5 | import Cura 1.0 as Cura 6 | 7 | import QtQuick 2.2 8 | import QtQuick.Controls 1.1 9 | 10 | UM.Dialog 11 | { 12 | id: uploadOptions 13 | 14 | title: catalog.i18nc("@action:button", "Upload to OctoPrint Options") 15 | 16 | minimumWidth: screenScaleFactor * 400 17 | minimumHeight: screenScaleFactor * 150 18 | 19 | onAccepted: manager.acceptOptionsDialog() 20 | 21 | Column { 22 | anchors.fill: parent 23 | 24 | UM.I18nCatalog{id: catalog; name:"octoprint"} 25 | 26 | Grid 27 | { 28 | columns: 2 29 | width: parent.width 30 | verticalItemAlignment: Grid.AlignVCenter 31 | rowSpacing: UM.Theme.getSize("default_lining").height 32 | columnSpacing: UM.Theme.getSize("default_margin").width 33 | 34 | Label 35 | { 36 | id: pathLabel 37 | text: catalog.i18nc("@label", "Path") 38 | } 39 | 40 | TextField { 41 | id: pathField 42 | text: manager.filePath 43 | maximumLength: 256 44 | width: parent.width - Math.max(pathLabel.width, fileLabel.width) - UM.Theme.getSize("default_margin").width 45 | horizontalAlignment: TextInput.AlignLeft 46 | validator: RegExpValidator 47 | { 48 | regExp: /.*/ 49 | } 50 | onTextChanged: manager.filePath = text 51 | } 52 | 53 | Label 54 | { 55 | id: fileLabel 56 | text: catalog.i18nc("@label", "Filename") 57 | } 58 | 59 | TextField { 60 | id: nameField 61 | text: manager.fileName 62 | maximumLength: 100 63 | width: parent.width - Math.max(pathLabel.width, fileLabel.width) - UM.Theme.getSize("default_margin").width 64 | horizontalAlignment: TextInput.AlignLeft 65 | validator: RegExpValidator 66 | { 67 | regExp: /[^\/]*/ 68 | } 69 | onTextChanged: manager.fileName = text 70 | } 71 | Item 72 | { 73 | width: 1 74 | height: UM.Theme.getSize("default_margin").height 75 | } 76 | Label 77 | { 78 | text: catalog.i18nc("@label", "A file extenstion will be added automatically.") 79 | } 80 | } 81 | 82 | Item 83 | { 84 | width: 1 85 | height: UM.Theme.getSize("default_margin").height 86 | } 87 | 88 | CheckBox 89 | { 90 | id: autoPrintCheckBox 91 | text: catalog.i18nc("@label", "Start print job after uploading") 92 | checked: manager.autoPrint 93 | onClicked: manager.autoPrint = checked 94 | } 95 | CheckBox 96 | { 97 | id: autoSelectCheckBox 98 | text: catalog.i18nc("@label", "Select print job after uploading") 99 | enabled: !autoPrintCheckBox.checked 100 | checked: autoPrintCheckBox.checked || manager.autoSelect 101 | onClicked: manager.autoSelect = checked 102 | } 103 | } 104 | 105 | rightButtons: [ 106 | Button { 107 | text: catalog.i18nc("@action:button", "Cancel") 108 | onClicked: uploadOptions.reject() 109 | }, 110 | Button { 111 | text: catalog.i18nc("@action:button", "OK") 112 | onClicked: uploadOptions.accept() 113 | isDefault: true 114 | } 115 | ] 116 | } 117 | --------------------------------------------------------------------------------