diff --git a/.gitignore b/.gitignore
index 98eaa6f414..60b59e6829 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ LC_MESSAGES
.cache
*.qmlc
.mypy_cache
+.pytest_cache
#MacOS
.DS_Store
@@ -25,6 +26,7 @@ LC_MESSAGES
*.lprof
*~
*.qm
+.directory
.idea
cura.desktop
@@ -40,7 +42,6 @@ plugins/cura-siemensnx-plugin
plugins/CuraBlenderPlugin
plugins/CuraCloudPlugin
plugins/CuraDrivePlugin
-plugins/CuraDrive
plugins/CuraLiveScriptingPlugin
plugins/CuraOpenSCADPlugin
plugins/CuraPrintProfileCreator
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9e9bf4b538..be6c9d938e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,6 +17,8 @@ if(CURA_DEBUGMODE)
set(_cura_debugmode "ON")
endif()
+set(CURA_APP_NAME "cura" CACHE STRING "Short name of Cura, used for configuration folder")
+set(CURA_APP_DISPLAY_NAME "Ultimaker Cura" CACHE STRING "Display name of Cura")
set(CURA_VERSION "master" CACHE STRING "Version name of Cura")
set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
set(CURA_SDK_VERSION "" CACHE STRING "SDK version of Cura")
diff --git a/Jenkinsfile b/Jenkinsfile
index de62b7ed5a..a345ebbd05 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,8 +1,11 @@
-parallel_nodes(['linux && cura', 'windows && cura']) {
- timeout(time: 2, unit: "HOURS") {
+parallel_nodes(['linux && cura', 'windows && cura'])
+{
+ timeout(time: 2, unit: "HOURS")
+ {
// Prepare building
- stage('Prepare') {
+ stage('Prepare')
+ {
// Ensure we start with a clean build directory.
step([$class: 'WsCleanup'])
@@ -11,13 +14,17 @@ parallel_nodes(['linux && cura', 'windows && cura']) {
}
// If any error occurs during building, we want to catch it and continue with the "finale" stage.
- catchError {
+ catchError
+ {
// Building and testing should happen in a subdirectory.
- dir('build') {
+ dir('build')
+ {
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
- stage('Build') {
+ stage('Build')
+ {
def branch = env.BRANCH_NAME
- if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}")) {
+ if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}"))
+ {
branch = "master"
}
@@ -27,18 +34,37 @@ parallel_nodes(['linux && cura', 'windows && cura']) {
}
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
- stage('Unit Test') {
- try {
- make('test')
- } catch(e) {
- currentBuild.result = "UNSTABLE"
+ stage('Unit Test')
+ {
+ if (isUnix())
+ {
+ // For Linux
+ try {
+ sh 'make CTEST_OUTPUT_ON_FAILURE=TRUE test'
+ } catch(e)
+ {
+ currentBuild.result = "UNSTABLE"
+ }
+ }
+ else
+ {
+ // For Windows
+ try
+ {
+ // This also does code style checks.
+ bat 'ctest -V'
+ } catch(e)
+ {
+ currentBuild.result = "UNSTABLE"
+ }
}
}
}
}
// Perform any post-build actions like notification and publishing of unit tests.
- stage('Finalize') {
+ stage('Finalize')
+ {
// Publish the test results to Jenkins.
junit allowEmptyResults: true, testResults: 'build/junit*.xml'
diff --git a/README.md b/README.md
index 70466e9c22..93abcc0c61 100644
--- a/README.md
+++ b/README.md
@@ -20,8 +20,9 @@ Dependencies
------------
* [Uranium](https://github.com/Ultimaker/Uranium) Cura is built on top of the Uranium framework.
* [CuraEngine](https://github.com/Ultimaker/CuraEngine) This will be needed at runtime to perform the actual slicing.
+* [fdm_materials](https://github.com/Ultimaker/fdm_materials) Required to load a printer that has swappable material profiles.
* [PySerial](https://github.com/pyserial/pyserial) Only required for USB printing support.
-* [python-zeroconf](https://github.com/jstasiak/python-zeroconf) Only required to detect mDNS-enabled printers
+* [python-zeroconf](https://github.com/jstasiak/python-zeroconf) Only required to detect mDNS-enabled printers.
Build scripts
-------------
diff --git a/cmake/CuraTests.cmake b/cmake/CuraTests.cmake
index 801f054bc3..b6d04de036 100644
--- a/cmake/CuraTests.cmake
+++ b/cmake/CuraTests.cmake
@@ -6,6 +6,8 @@ include(CMakeParseArguments)
find_package(PythonInterp 3.5.0 REQUIRED)
+add_custom_target(test-verbose COMMAND ${CMAKE_CTEST_COMMAND} --verbose)
+
function(cura_add_test)
set(_single_args NAME DIRECTORY PYTHONPATH)
cmake_parse_arguments("" "" "${_single_args}" "" ${ARGN})
@@ -34,7 +36,7 @@ function(cura_add_test)
if (NOT ${test_exists})
add_test(
NAME ${_NAME}
- COMMAND ${PYTHON_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
+ COMMAND ${PYTHON_EXECUTABLE} -m pytest --verbose --full-trace --capture=no --no-print-log --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
@@ -57,5 +59,13 @@ endforeach()
#Add code style test.
add_test(
NAME "code-style"
- COMMAND ${PYTHON_EXECUTABLE} run_mypy.py WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ COMMAND ${PYTHON_EXECUTABLE} run_mypy.py
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
+
+#Add test for whether the shortcut alt-keys are unique in every translation.
+add_test(
+ NAME "shortcut-keys"
+ COMMAND ${PYTHON_EXECUTABLE} scripts/check_shortcut_keys.py
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
\ No newline at end of file
diff --git a/cura.desktop.in b/cura.desktop.in
index fe61b47217..b0195015a5 100644
--- a/cura.desktop.in
+++ b/cura.desktop.in
@@ -13,6 +13,6 @@ TryExec=@CMAKE_INSTALL_FULL_BINDIR@/cura
Icon=cura-icon
Terminal=false
Type=Application
-MimeType=application/sla;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;
+MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;text/x-gcode;
Categories=Graphics;
Keywords=3D;Printing;Slicer;
diff --git a/cura.sharedmimeinfo b/cura.sharedmimeinfo
index 9629aef5df..ed9099d425 100644
--- a/cura.sharedmimeinfo
+++ b/cura.sharedmimeinfo
@@ -6,7 +6,7 @@
-
+ Computer-aided design and manufacturing format
@@ -19,4 +19,12 @@
+
+
+ Gcode file
+
+
+
+
+
\ No newline at end of file
diff --git a/cura/API/Account.py b/cura/API/Account.py
new file mode 100644
index 0000000000..30401454b3
--- /dev/null
+++ b/cura/API/Account.py
@@ -0,0 +1,126 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional, Dict, TYPE_CHECKING
+
+from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
+
+from UM.i18n import i18nCatalog
+from UM.Message import Message
+from cura import UltimakerCloudAuthentication
+
+from cura.OAuth2.AuthorizationService import AuthorizationService
+from cura.OAuth2.Models import OAuth2Settings
+
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
+
+i18n_catalog = i18nCatalog("cura")
+
+
+## The account API provides a version-proof bridge to use Ultimaker Accounts
+#
+# Usage:
+# ``from cura.API import CuraAPI
+# api = CuraAPI()
+# api.account.login()
+# api.account.logout()
+# api.account.userProfile # Who is logged in``
+#
+class Account(QObject):
+ # Signal emitted when user logged in or out.
+ loginStateChanged = pyqtSignal(bool)
+
+ def __init__(self, application: "CuraApplication", parent = None) -> None:
+ super().__init__(parent)
+ self._application = application
+
+ self._error_message = None # type: Optional[Message]
+ self._logged_in = False
+
+ self._callback_port = 32118
+ self._oauth_root = UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
+
+ self._oauth_settings = OAuth2Settings(
+ OAUTH_SERVER_URL= self._oauth_root,
+ CALLBACK_PORT=self._callback_port,
+ CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port),
+ CLIENT_ID="um----------------------------ultimaker_cura",
+ CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download "
+ "packages.rating.read packages.rating.write connect.cluster.read connect.cluster.write "
+ "cura.printjob.read cura.printjob.write cura.mesh.read cura.mesh.write",
+ AUTH_DATA_PREFERENCE_KEY="general/ultimaker_auth_data",
+ AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root),
+ AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root)
+ )
+
+ self._authorization_service = AuthorizationService(self._oauth_settings)
+
+ def initialize(self) -> None:
+ self._authorization_service.initialize(self._application.getPreferences())
+ self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
+ self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
+ self._authorization_service.loadAuthDataFromPreferences()
+
+ ## Returns a boolean indicating whether the given authentication is applied against staging or not.
+ @property
+ def is_staging(self) -> bool:
+ return "staging" in self._oauth_root
+
+ @pyqtProperty(bool, notify=loginStateChanged)
+ def isLoggedIn(self) -> bool:
+ return self._logged_in
+
+ def _onLoginStateChanged(self, logged_in: bool = False, error_message: Optional[str] = None) -> None:
+ if error_message:
+ if self._error_message:
+ self._error_message.hide()
+ self._error_message = Message(error_message, title = i18n_catalog.i18nc("@info:title", "Login failed"))
+ self._error_message.show()
+ self._logged_in = False
+ self.loginStateChanged.emit(False)
+ return
+
+ if self._logged_in != logged_in:
+ self._logged_in = logged_in
+ self.loginStateChanged.emit(logged_in)
+
+ @pyqtSlot()
+ def login(self) -> None:
+ if self._logged_in:
+ # Nothing to do, user already logged in.
+ return
+ self._authorization_service.startAuthorizationFlow()
+
+ @pyqtProperty(str, notify=loginStateChanged)
+ def userName(self):
+ user_profile = self._authorization_service.getUserProfile()
+ if not user_profile:
+ return None
+ return user_profile.username
+
+ @pyqtProperty(str, notify = loginStateChanged)
+ def profileImageUrl(self):
+ user_profile = self._authorization_service.getUserProfile()
+ if not user_profile:
+ return None
+ return user_profile.profile_image_url
+
+ @pyqtProperty(str, notify=loginStateChanged)
+ def accessToken(self) -> Optional[str]:
+ return self._authorization_service.getAccessToken()
+
+ # Get the profile of the logged in user
+ # @returns None if no user is logged in, a dict containing user_id, username and profile_image_url
+ @pyqtProperty("QVariantMap", notify = loginStateChanged)
+ def userProfile(self) -> Optional[Dict[str, Optional[str]]]:
+ user_profile = self._authorization_service.getUserProfile()
+ if not user_profile:
+ return None
+ return user_profile.__dict__
+
+ @pyqtSlot()
+ def logout(self) -> None:
+ if not self._logged_in:
+ return # Nothing to do, user isn't logged in.
+
+ self._authorization_service.deleteAuthData()
diff --git a/cura/API/Backups.py b/cura/API/Backups.py
index f31933c844..ef74e74be0 100644
--- a/cura/API/Backups.py
+++ b/cura/API/Backups.py
@@ -1,9 +1,12 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import Tuple, Optional
+from typing import Tuple, Optional, TYPE_CHECKING, Dict, Any
from cura.Backups.BackupsManager import BackupsManager
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
+
## The back-ups API provides a version-proof bridge between Cura's
# BackupManager and plug-ins that hook into it.
@@ -13,19 +16,20 @@ from cura.Backups.BackupsManager import BackupsManager
# api = CuraAPI()
# api.backups.createBackup()
# api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})``
-
class Backups:
- manager = BackupsManager() # Re-used instance of the backups manager.
+
+ def __init__(self, application: "CuraApplication") -> None:
+ self.manager = BackupsManager(application)
## Create a new back-up using the BackupsManager.
# \return Tuple containing a ZIP file with the back-up data and a dict
# with metadata about the back-up.
- def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]:
+ def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, Any]]]:
return self.manager.createBackup()
## Restore a back-up using the BackupsManager.
# \param zip_file A ZIP file containing the actual back-up data.
# \param meta_data Some metadata needed for restoring a back-up, like the
# Cura version number.
- def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
+ def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, Any]) -> None:
return self.manager.restoreBackup(zip_file, meta_data)
diff --git a/cura/API/Interface/Settings.py b/cura/API/Interface/Settings.py
index 2889db7022..371c40c14c 100644
--- a/cura/API/Interface/Settings.py
+++ b/cura/API/Interface/Settings.py
@@ -1,7 +1,11 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from cura.CuraApplication import CuraApplication
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
+
## The Interface.Settings API provides a version-proof bridge between Cura's
# (currently) sidebar UI and plug-ins that hook into it.
@@ -19,8 +23,9 @@ from cura.CuraApplication import CuraApplication
# api.interface.settings.addContextMenuItem(data)``
class Settings:
- # Re-used instance of Cura:
- application = CuraApplication.getInstance() # type: CuraApplication
+
+ def __init__(self, application: "CuraApplication") -> None:
+ self.application = application
## Add items to the sidebar context menu.
# \param menu_item dict containing the menu item to add.
@@ -30,4 +35,4 @@ class Settings:
## Get all custom items currently added to the sidebar context menu.
# \return List containing all custom context menu items.
def getContextMenuItems(self) -> list:
- return self.application.getSidebarCustomMenuItems()
\ No newline at end of file
+ return self.application.getSidebarCustomMenuItems()
diff --git a/cura/API/Interface/__init__.py b/cura/API/Interface/__init__.py
index b38118949b..cec174bf0a 100644
--- a/cura/API/Interface/__init__.py
+++ b/cura/API/Interface/__init__.py
@@ -1,9 +1,14 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from UM.PluginRegistry import PluginRegistry
+from typing import TYPE_CHECKING
+
from cura.API.Interface.Settings import Settings
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
+
+
## The Interface class serves as a common root for the specific API
# methods for each interface element.
#
@@ -17,8 +22,6 @@ from cura.API.Interface.Settings import Settings
class Interface:
- # For now we use the same API version to be consistent.
- VERSION = PluginRegistry.APIVersion
-
- # API methods specific to the settings portion of the UI
- settings = Settings()
+ def __init__(self, application: "CuraApplication") -> None:
+ # API methods specific to the settings portion of the UI
+ self.settings = Settings(application)
diff --git a/cura/API/__init__.py b/cura/API/__init__.py
index 64d636903d..b3e702263a 100644
--- a/cura/API/__init__.py
+++ b/cura/API/__init__.py
@@ -1,8 +1,16 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from UM.PluginRegistry import PluginRegistry
+from typing import Optional, TYPE_CHECKING
+
+from PyQt5.QtCore import QObject, pyqtProperty
+
from cura.API.Backups import Backups
from cura.API.Interface import Interface
+from cura.API.Account import Account
+
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
+
## The official Cura API that plug-ins can use to interact with Cura.
#
@@ -10,14 +18,46 @@ from cura.API.Interface import Interface
# this API provides a version-safe interface with proper deprecation warnings
# etc. Usage of any other methods than the ones provided in this API can cause
# plug-ins to be unstable.
-
-class CuraAPI:
+class CuraAPI(QObject):
# For now we use the same API version to be consistent.
- VERSION = PluginRegistry.APIVersion
+ __instance = None # type: "CuraAPI"
+ _application = None # type: CuraApplication
- # Backups API
- backups = Backups()
+ # This is done to ensure that the first time an instance is created, it's forced that the application is set.
+ # The main reason for this is that we want to prevent consumers of API to have a dependency on CuraApplication.
+ # Since the API is intended to be used by plugins, the cura application should have already created this.
+ def __new__(cls, application: Optional["CuraApplication"] = None):
+ if cls.__instance is None:
+ if application is None:
+ raise Exception("Upon first time creation, the application must be set.")
+ cls.__instance = super(CuraAPI, cls).__new__(cls)
+ cls._application = application
+ return cls.__instance
- # Interface API
- interface = Interface()
+ def __init__(self, application: Optional["CuraApplication"] = None) -> None:
+ super().__init__(parent = CuraAPI._application)
+
+ # Accounts API
+ self._account = Account(self._application)
+
+ # Backups API
+ self._backups = Backups(self._application)
+
+ # Interface API
+ self._interface = Interface(self._application)
+
+ def initialize(self) -> None:
+ self._account.initialize()
+
+ @pyqtProperty(QObject, constant = True)
+ def account(self) -> "Account":
+ return self._account
+
+ @property
+ def backups(self) -> "Backups":
+ return self._backups
+
+ @property
+ def interface(self) -> "Interface":
+ return self._interface
diff --git a/cura/ApplicationMetadata.py b/cura/ApplicationMetadata.py
new file mode 100644
index 0000000000..faa3364e08
--- /dev/null
+++ b/cura/ApplicationMetadata.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+# ---------
+# General constants used in Cura
+# ---------
+DEFAULT_CURA_APP_NAME = "cura"
+DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
+DEFAULT_CURA_VERSION = "master"
+DEFAULT_CURA_BUILD_TYPE = ""
+DEFAULT_CURA_DEBUG_MODE = False
+DEFAULT_CURA_SDK_VERSION = "6.0.0"
+
+try:
+ from cura.CuraVersion import CuraAppName # type: ignore
+ if CuraAppName == "":
+ CuraAppName = DEFAULT_CURA_APP_NAME
+except ImportError:
+ CuraAppName = DEFAULT_CURA_APP_NAME
+
+try:
+ from cura.CuraVersion import CuraAppDisplayName # type: ignore
+ if CuraAppDisplayName == "":
+ CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
+except ImportError:
+ CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
+
+try:
+ from cura.CuraVersion import CuraVersion # type: ignore
+ if CuraVersion == "":
+ CuraVersion = DEFAULT_CURA_VERSION
+except ImportError:
+ CuraVersion = DEFAULT_CURA_VERSION # [CodeStyle: Reflecting imported value]
+
+try:
+ from cura.CuraVersion import CuraBuildType # type: ignore
+except ImportError:
+ CuraBuildType = DEFAULT_CURA_BUILD_TYPE
+
+try:
+ from cura.CuraVersion import CuraDebugMode # type: ignore
+except ImportError:
+ CuraDebugMode = DEFAULT_CURA_DEBUG_MODE
+
+try:
+ from cura.CuraVersion import CuraSDKVersion # type: ignore
+ if CuraSDKVersion == "":
+ CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
+except ImportError:
+ CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
diff --git a/cura/Arranging/Arrange.py b/cura/Arranging/Arrange.py
index 5657ee991a..32796005c8 100644
--- a/cura/Arranging/Arrange.py
+++ b/cura/Arranging/Arrange.py
@@ -66,6 +66,11 @@ class Arrange:
continue
vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
points = copy.deepcopy(vertices._points)
+
+ # After scaling (like up to 0.1 mm) the node might not have points
+ if len(points) == 0:
+ continue
+
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr)
diff --git a/cura/Arranging/ArrangeObjectsJob.py b/cura/Arranging/ArrangeObjectsJob.py
index ce11556b5b..aef051c838 100644
--- a/cura/Arranging/ArrangeObjectsJob.py
+++ b/cura/Arranging/ArrangeObjectsJob.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
@@ -39,10 +39,17 @@ class ArrangeObjectsJob(Job):
arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes, min_offset = self._min_offset)
+ # Build set to exclude children (those get arranged together with the parents).
+ included_as_child = set()
+ for node in self._nodes:
+ included_as_child.update(node.getAllChildren())
+
# Collect nodes to be placed
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
for node in self._nodes:
- offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset)
+ if node in included_as_child:
+ continue
+ offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset, include_children = True)
if offset_shape_arr is None:
Logger.log("w", "Node [%s] could not be converted to an array for arranging...", str(node))
continue
diff --git a/cura/Arranging/ShapeArray.py b/cura/Arranging/ShapeArray.py
index ab785cc3e1..64b78d6f17 100644
--- a/cura/Arranging/ShapeArray.py
+++ b/cura/Arranging/ShapeArray.py
@@ -42,7 +42,7 @@ class ShapeArray:
# \param min_offset offset for the offset ShapeArray
# \param scale scale the coordinates
@classmethod
- def fromNode(cls, node, min_offset, scale = 0.5):
+ def fromNode(cls, node, min_offset, scale = 0.5, include_children = False):
transform = node._transformation
transform_x = transform._data[0][3]
transform_y = transform._data[2][3]
@@ -52,6 +52,21 @@ class ShapeArray:
return None, None
# For one_at_a_time printing you need the convex hull head.
hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts
+ if hull_head_verts is None:
+ hull_head_verts = Polygon()
+
+ # If the child-nodes are included, adjust convex hulls as well:
+ if include_children:
+ children = node.getAllChildren()
+ if not children is None:
+ for child in children:
+ # 'Inefficient' combination of convex hulls through known code rather than mess it up:
+ child_hull = child.callDecoration("getConvexHull")
+ if not child_hull is None:
+ hull_verts = hull_verts.unionConvexHulls(child_hull)
+ child_hull_head = child.callDecoration("getConvexHullHead") or child_hull
+ if not child_hull_head is None:
+ hull_head_verts = hull_head_verts.unionConvexHulls(child_hull_head)
offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
offset_points = copy.deepcopy(offset_verts._points) # x, y
diff --git a/cura/Backups/Backup.py b/cura/Backups/Backup.py
index cc47df770e..714d6527fe 100644
--- a/cura/Backups/Backup.py
+++ b/cura/Backups/Backup.py
@@ -4,18 +4,18 @@
import io
import os
import re
-
import shutil
-
-from typing import Dict, Optional
from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
+from typing import Dict, Optional, TYPE_CHECKING
from UM import i18nCatalog
from UM.Logger import Logger
from UM.Message import Message
from UM.Platform import Platform
from UM.Resources import Resources
-from cura.CuraApplication import CuraApplication
+
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
## The back-up class holds all data about a back-up.
@@ -29,28 +29,30 @@ class Backup:
# Re-use translation catalog.
catalog = i18nCatalog("cura")
- def __init__(self, zip_file: bytes = None, meta_data: Dict[str, str] = None) -> None:
+ def __init__(self, application: "CuraApplication", zip_file: bytes = None, meta_data: Dict[str, str] = None) -> None:
+ self._application = application
self.zip_file = zip_file # type: Optional[bytes]
self.meta_data = meta_data # type: Optional[Dict[str, str]]
## Create a back-up from the current user config folder.
def makeFromCurrent(self) -> None:
- cura_release = CuraApplication.getInstance().getVersion()
+ cura_release = self._application.getVersion()
version_data_dir = Resources.getDataStoragePath()
Logger.log("d", "Creating backup for Cura %s, using folder %s", cura_release, version_data_dir)
# Ensure all current settings are saved.
- CuraApplication.getInstance().saveSettings()
+ self._application.saveSettings()
# We copy the preferences file to the user data directory in Linux as it's in a different location there.
# When restoring a backup on Linux, we move it back.
- if Platform.isLinux():
- preferences_file_name = CuraApplication.getInstance().getApplicationName()
+ if Platform.isLinux(): #TODO: This should check for the config directory not being the same as the data directory, rather than hard-coding that to Linux systems.
+ preferences_file_name = self._application.getApplicationName()
preferences_file = Resources.getPath(Resources.Preferences, "{}.cfg".format(preferences_file_name))
backup_preferences_file = os.path.join(version_data_dir, "{}.cfg".format(preferences_file_name))
- Logger.log("d", "Copying preferences file from %s to %s", preferences_file, backup_preferences_file)
- shutil.copyfile(preferences_file, backup_preferences_file)
+ if os.path.exists(preferences_file) and (not os.path.exists(backup_preferences_file) or not os.path.samefile(preferences_file, backup_preferences_file)):
+ Logger.log("d", "Copying preferences file from %s to %s", preferences_file, backup_preferences_file)
+ shutil.copyfile(preferences_file, backup_preferences_file)
# Create an empty buffer and write the archive to it.
buffer = io.BytesIO()
@@ -58,7 +60,7 @@ class Backup:
if archive is None:
return
files = archive.namelist()
-
+
# Count the metadata items. We do this in a rather naive way at the moment.
machine_count = len([s for s in files if "machine_instances/" in s]) - 1
material_count = len([s for s in files if "materials/" in s]) - 1
@@ -112,7 +114,7 @@ class Backup:
"Tried to restore a Cura backup without having proper data or meta data."))
return False
- current_version = CuraApplication.getInstance().getVersion()
+ current_version = self._application.getVersion()
version_to_restore = self.meta_data.get("cura_release", "master")
if current_version != version_to_restore:
# Cannot restore version older or newer than current because settings might have changed.
@@ -128,7 +130,7 @@ class Backup:
# Under Linux, preferences are stored elsewhere, so we copy the file to there.
if Platform.isLinux():
- preferences_file_name = CuraApplication.getInstance().getApplicationName()
+ preferences_file_name = self._application.getApplicationName()
preferences_file = Resources.getPath(Resources.Preferences, "{}.cfg".format(preferences_file_name))
backup_preferences_file = os.path.join(version_data_dir, "{}.cfg".format(preferences_file_name))
Logger.log("d", "Moving preferences file from %s to %s", backup_preferences_file, preferences_file)
diff --git a/cura/Backups/BackupsManager.py b/cura/Backups/BackupsManager.py
index 67e2a222f1..a0d3881209 100644
--- a/cura/Backups/BackupsManager.py
+++ b/cura/Backups/BackupsManager.py
@@ -1,11 +1,13 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import Dict, Optional, Tuple
+from typing import Dict, Optional, Tuple, TYPE_CHECKING
from UM.Logger import Logger
from cura.Backups.Backup import Backup
-from cura.CuraApplication import CuraApplication
+
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
## The BackupsManager is responsible for managing the creating and restoring of
@@ -13,15 +15,15 @@ from cura.CuraApplication import CuraApplication
#
# Back-ups themselves are represented in a different class.
class BackupsManager:
- def __init__(self):
- self._application = CuraApplication.getInstance()
+ def __init__(self, application: "CuraApplication") -> None:
+ self._application = application
## Get a back-up of the current configuration.
# \return A tuple containing a ZipFile (the actual back-up) and a dict
# containing some metadata (like version).
def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, str]]]:
self._disableAutoSave()
- backup = Backup()
+ backup = Backup(self._application)
backup.makeFromCurrent()
self._enableAutoSave()
# We don't return a Backup here because we want plugins only to interact with our API and not full objects.
@@ -39,7 +41,7 @@ class BackupsManager:
self._disableAutoSave()
- backup = Backup(zip_file = zip_file, meta_data = meta_data)
+ backup = Backup(self._application, zip_file = zip_file, meta_data = meta_data)
restored = backup.restore()
if restored:
# At this point, Cura will need to restart for the changes to take effect.
diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py
index b029665abd..f8f691a850 100755
--- a/cura/BuildVolume.py
+++ b/cura/BuildVolume.py
@@ -1,6 +1,6 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-
+from UM.Scene.Camera import Camera
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Settings.ExtruderManager import ExtruderManager
from UM.Application import Application #To modify the maximum zoom level.
@@ -28,7 +28,7 @@ import copy
from typing import List, Optional
-# Setting for clearance around the prime
+# Radius of disallowed area in mm around prime. I.e. how much distance to keep from prime position.
PRIME_CLEARANCE = 6.5
@@ -83,7 +83,14 @@ class BuildVolume(SceneNode):
" with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
self._global_container_stack = None
+
+ self._stack_change_timer = QTimer()
+ self._stack_change_timer.setInterval(100)
+ self._stack_change_timer.setSingleShot(True)
+ self._stack_change_timer.timeout.connect(self._onStackChangeTimerFinished)
+
self._application.globalContainerStackChanged.connect(self._onStackChanged)
+
self._onStackChanged()
self._engine_ready = False
@@ -122,7 +129,9 @@ class BuildVolume(SceneNode):
def _onSceneChanged(self, source):
if self._global_container_stack:
- self._scene_change_timer.start()
+ # Ignore anything that is not something we can slice in the first place!
+ if source.callDecoration("isSliceable"):
+ self._scene_change_timer.start()
def _onSceneChangeTimerFinished(self):
root = self._application.getController().getScene().getRoot()
@@ -139,7 +148,7 @@ class BuildVolume(SceneNode):
if active_extruder_changed is not None:
node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild)
node.decoratorsChanged.disconnect(self._updateNodeListeners)
- self._updateDisallowedAreasAndRebuild() # make sure we didn't miss anything before we updated the node listeners
+ self.rebuild()
self._scene_objects = new_scene_objects
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
@@ -491,7 +500,9 @@ class BuildVolume(SceneNode):
def _updateRaftThickness(self):
old_raft_thickness = self._raft_thickness
- self._adhesion_type = self._global_container_stack.getProperty("adhesion_type", "value")
+ if self._global_container_stack.extruders:
+ # This might be called before the extruder stacks have initialised, in which case getting the adhesion_type fails
+ self._adhesion_type = self._global_container_stack.getProperty("adhesion_type", "value")
self._raft_thickness = 0.0
if self._adhesion_type == "raft":
self._raft_thickness = (
@@ -524,11 +535,14 @@ class BuildVolume(SceneNode):
if extra_z != self._extra_z_clearance:
self._extra_z_clearance = extra_z
- ## Update the build volume visualization
def _onStackChanged(self):
+ self._stack_change_timer.start()
+
+ ## Update the build volume visualization
+ def _onStackChangeTimerFinished(self):
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
- extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
+ extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
for extruder in extruders:
extruder.propertyChanged.disconnect(self._onSettingPropertyChanged)
@@ -536,7 +550,7 @@ class BuildVolume(SceneNode):
if self._global_container_stack:
self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged)
- extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
+ extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
for extruder in extruders:
extruder.propertyChanged.connect(self._onSettingPropertyChanged)
@@ -653,6 +667,7 @@ class BuildVolume(SceneNode):
# ``_updateDisallowedAreas`` method itself shouldn't call ``rebuild``,
# since there may be other changes before it needs to be rebuilt, which
# would hit performance.
+
def _updateDisallowedAreasAndRebuild(self):
self._updateDisallowedAreas()
self._updateRaftThickness()
@@ -720,21 +735,27 @@ class BuildVolume(SceneNode):
# Add prime tower location as disallowed area.
if len(used_extruders) > 1: #No prime tower in single-extrusion.
- prime_tower_collision = False
- prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
- for extruder_id in prime_tower_areas:
- for prime_tower_area in prime_tower_areas[extruder_id]:
- for area in result_areas[extruder_id]:
- if prime_tower_area.intersectsPolygon(area) is not None:
- prime_tower_collision = True
+
+ if len([x for x in used_extruders if x.isEnabled == True]) > 1: #No prime tower if only one extruder is enabled
+ prime_tower_collision = False
+ prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
+ for extruder_id in prime_tower_areas:
+ for i_area, prime_tower_area in enumerate(prime_tower_areas[extruder_id]):
+ for area in result_areas[extruder_id]:
+ if prime_tower_area.intersectsPolygon(area) is not None:
+ prime_tower_collision = True
+ break
+ if prime_tower_collision: #Already found a collision.
break
- if prime_tower_collision: #Already found a collision.
- break
- if not prime_tower_collision:
- result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
- result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
- else:
- self._error_areas.extend(prime_tower_areas[extruder_id])
+ if (ExtruderManager.getInstance().getResolveOrValue("prime_tower_brim_enable") and
+ ExtruderManager.getInstance().getResolveOrValue("adhesion_type") != "raft"):
+ prime_tower_areas[extruder_id][i_area] = prime_tower_area.getMinkowskiHull(
+ Polygon.approximatedCircle(disallowed_border_size))
+ if not prime_tower_collision:
+ result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
+ result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
+ else:
+ self._error_areas.extend(prime_tower_areas[extruder_id])
self._has_errors = len(self._error_areas) > 0
@@ -769,6 +790,16 @@ class BuildVolume(SceneNode):
prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
prime_tower_y = prime_tower_y + machine_depth / 2
+ if (ExtruderManager.getInstance().getResolveOrValue("prime_tower_brim_enable") and
+ ExtruderManager.getInstance().getResolveOrValue("adhesion_type") != "raft"):
+ brim_size = (
+ extruder.getProperty("brim_line_count", "value") *
+ extruder.getProperty("skirt_brim_line_width", "value") / 100.0 *
+ extruder.getProperty("initial_layer_line_width_factor", "value")
+ )
+ prime_tower_x -= brim_size
+ prime_tower_y += brim_size
+
if self._global_container_stack.getProperty("prime_tower_circular", "value"):
radius = prime_tower_size / 2
prime_tower_area = Polygon.approximatedCircle(radius)
@@ -1008,7 +1039,9 @@ class BuildVolume(SceneNode):
# We don't create an additional line for the extruder we're printing the skirt with.
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
- elif adhesion_type == "brim":
+ elif (adhesion_type == "brim" or
+ (self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and
+ self._global_container_stack.getProperty("adhesion_type", "value") != "raft")):
brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value")
bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
@@ -1027,6 +1060,12 @@ class BuildVolume(SceneNode):
else:
raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?")
+ max_length_available = 0.5 * min(
+ self._global_container_stack.getProperty("machine_width", "value"),
+ self._global_container_stack.getProperty("machine_depth", "value")
+ )
+ bed_adhesion_size = min(bed_adhesion_size, max_length_available)
+
support_expansion = 0
support_enabled = self._global_container_stack.getProperty("support_enable", "value")
support_offset = self._global_container_stack.getProperty("support_offset", "value")
@@ -1061,7 +1100,7 @@ class BuildVolume(SceneNode):
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
- _tower_settings = ["prime_tower_enable", "prime_tower_circular", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
+ _tower_settings = ["prime_tower_enable", "prime_tower_circular", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y", "prime_tower_brim_enable"]
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts", "travel_avoid_supports"]
_extruder_settings = ["support_enable", "support_bottom_enable", "support_roof_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "brim_line_count", "adhesion_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
diff --git a/cura/CameraImageProvider.py b/cura/CameraImageProvider.py
deleted file mode 100644
index ff5c51f24b..0000000000
--- a/cura/CameraImageProvider.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from PyQt5.QtGui import QImage
-from PyQt5.QtQuick import QQuickImageProvider
-from PyQt5.QtCore import QSize
-
-from UM.Application import Application
-
-
-class CameraImageProvider(QQuickImageProvider):
- def __init__(self):
- super().__init__(QQuickImageProvider.Image)
-
- ## Request a new image.
- def requestImage(self, id, size):
- for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
- try:
- image = output_device.activePrinter.camera.getImage()
- if image.isNull():
- image = QImage()
-
- return image, QSize(15, 15)
- except AttributeError:
- pass
- return QImage(), QSize(15, 15)
diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py
index 46544ca0ef..d43743bc37 100644
--- a/cura/CrashHandler.py
+++ b/cura/CrashHandler.py
@@ -36,18 +36,14 @@ else:
except ImportError:
CuraDebugMode = False # [CodeStyle: Reflecting imported value]
-# List of exceptions that should be considered "fatal" and abort the program.
-# These are primarily some exception types that we simply cannot really recover from
-# (MemoryError and SystemError) and exceptions that indicate grave errors in the
-# code that cause the Python interpreter to fail (SyntaxError, ImportError).
-fatal_exception_types = [
- MemoryError,
- SyntaxError,
- ImportError,
- SystemError,
+# List of exceptions that should not be considered "fatal" and abort the program.
+# These are primarily some exception types that we simply skip
+skip_exception_types = [
+ SystemExit,
+ KeyboardInterrupt,
+ GeneratorExit
]
-
class CrashHandler:
crash_url = "https://stats.ultimaker.com/api/cura"
@@ -70,7 +66,7 @@ class CrashHandler:
# If Cura has fully started, we only show fatal errors.
# If Cura has not fully started yet, we always show the early crash dialog. Otherwise, Cura will just crash
# without any information.
- if has_started and exception_type not in fatal_exception_types:
+ if has_started and exception_type in skip_exception_types:
return
if not has_started:
@@ -387,7 +383,7 @@ class CrashHandler:
Application.getInstance().callLater(self._show)
def _show(self):
- # When the exception is not in the fatal_exception_types list, the dialog is not created, so we don't need to show it
+ # When the exception is in the skip_exception_types list, the dialog is not created, so we don't need to show it
if self.dialog:
self.dialog.exec_()
os._exit(1)
diff --git a/cura/CuraActions.py b/cura/CuraActions.py
index 93a18318df..91e0966fed 100644
--- a/cura/CuraActions.py
+++ b/cura/CuraActions.py
@@ -3,7 +3,7 @@
from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtGui import QDesktopServices
-from typing import List, TYPE_CHECKING
+from typing import List, TYPE_CHECKING, cast
from UM.Event import CallFunctionEvent
from UM.FlameProfiler import pyqtSlot
@@ -36,12 +36,12 @@ class CuraActions(QObject):
# Starting a web browser from a signal handler connected to a menu will crash on windows.
# So instead, defer the call to the next run of the event loop, since that does work.
# Note that weirdly enough, only signal handlers that open a web browser fail like that.
- event = CallFunctionEvent(self._openUrl, [QUrl("http://ultimaker.com/en/support/software")], {})
+ event = CallFunctionEvent(self._openUrl, [QUrl("https://ultimaker.com/en/resources/manuals/software")], {})
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
@pyqtSlot()
def openBugReportPage(self) -> None:
- event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {})
+ event = CallFunctionEvent(self._openUrl, [QUrl("https://github.com/Ultimaker/Cura/issues")], {})
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
## Reset camera position and direction to default
@@ -61,8 +61,10 @@ class CuraActions(QObject):
operation = GroupedOperation()
for node in Selection.getAllSelectedObjects():
current_node = node
- while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
- current_node = current_node.getParent()
+ parent_node = current_node.getParent()
+ while parent_node and parent_node.callDecoration("isGroup"):
+ current_node = parent_node
+ parent_node = current_node.getParent()
# This was formerly done with SetTransformOperation but because of
# unpredictable matrix deconstruction it was possible that mirrors
@@ -150,13 +152,13 @@ class CuraActions(QObject):
root = cura.CuraApplication.CuraApplication.getInstance().getController().getScene().getRoot()
- nodes_to_change = []
+ nodes_to_change = [] # type: List[SceneNode]
for node in Selection.getAllSelectedObjects():
parent_node = node # Find the parent node to change instead
while parent_node.getParent() != root:
- parent_node = parent_node.getParent()
+ parent_node = cast(SceneNode, parent_node.getParent())
- for single_node in BreadthFirstIterator(parent_node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
+ for single_node in BreadthFirstIterator(parent_node): # type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
nodes_to_change.append(single_node)
if not nodes_to_change:
diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py
index 4dee5ae813..4d3d2434ff 100755
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -1,10 +1,10 @@
-# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
import sys
import time
-from typing import cast, TYPE_CHECKING
+from typing import cast, TYPE_CHECKING, Optional, Callable, List
import numpy
@@ -13,6 +13,8 @@ from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
+from UM.Application import Application
+from UM.PluginError import PluginNotFoundError
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Camera import Camera
from UM.Math.Vector import Vector
@@ -36,18 +38,18 @@ from UM.Settings.Validator import Validator
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Workspace.WorkspaceReader import WorkspaceReader
-from UM.Decorators import deprecated
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
-from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.SetTransformOperation import SetTransformOperation
+from cura.API import CuraAPI
from cura.Arranging.Arrange import Arrange
from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
from cura.Arranging.ShapeArray import ShapeArray
from cura.MultiplyObjectsJob import MultiplyObjectsJob
+from cura.GlobalStacksModel import GlobalStacksModel
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
from cura.Operations.SetParentOperation import SetParentOperation
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
@@ -60,6 +62,7 @@ from cura.Scene.CuraSceneController import CuraSceneController
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
+from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from cura.Settings.MachineNameValidator import MachineNameValidator
from cura.Machines.Models.BuildPlateModel import BuildPlateModel
@@ -67,9 +70,9 @@ from cura.Machines.Models.NozzleModel import NozzleModel
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
-from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
+from cura.Machines.Models.FavoriteMaterialsModel import FavoriteMaterialsModel
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
-from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
+from cura.Machines.Models.MaterialBrandsModel import MaterialBrandsModel
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
from cura.Machines.Models.MachineManagementModel import MachineManagementModel
@@ -92,7 +95,7 @@ from . import PrintInformation
from . import CuraActions
from cura.Scene import ZOffsetDecorator
from . import CuraSplashScreen
-from . import CameraImageProvider
+from . import PrintJobPreviewImageProvider
from . import MachineActionManager
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
@@ -105,32 +108,33 @@ from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisi
from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
import cura.Settings.cura_empty_instance_containers
+from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions
from cura.ObjectsModel import ObjectsModel
-from UM.FlameProfiler import pyqtSlot
+from cura.PrinterOutputDevice import PrinterOutputDevice
+from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
+from cura import ApplicationMetadata, UltimakerCloudAuthentication
+
+from UM.FlameProfiler import pyqtSlot
+from UM.Decorators import override
if TYPE_CHECKING:
- from plugins.SliceInfoPlugin.SliceInfo import SliceInfo
+ from cura.Machines.MaterialManager import MaterialManager
+ from cura.Machines.QualityManager import QualityManager
+ from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer
+ from cura.Settings.GlobalStack import GlobalStack
numpy.seterr(all = "ignore")
-try:
- from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode, CuraSDKVersion
-except ImportError:
- CuraVersion = "master" # [CodeStyle: Reflecting imported value]
- CuraBuildType = ""
- CuraDebugMode = False
- CuraSDKVersion = ""
-
class CuraApplication(QtApplication):
# SettingVersion represents the set of settings available in the machine/extruder definitions.
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
# changes of the settings.
- SettingVersion = 5
+ SettingVersion = 7
Created = False
@@ -150,15 +154,19 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes)
def __init__(self, *args, **kwargs):
- super().__init__(name = "cura",
- version = CuraVersion,
- buildtype = CuraBuildType,
- is_debug_mode = CuraDebugMode,
+ super().__init__(name = ApplicationMetadata.CuraAppName,
+ app_display_name = ApplicationMetadata.CuraAppDisplayName,
+ version = ApplicationMetadata.CuraVersion,
+ api_version = ApplicationMetadata.CuraSDKVersion,
+ buildtype = ApplicationMetadata.CuraBuildType,
+ is_debug_mode = ApplicationMetadata.CuraDebugMode,
tray_icon_name = "cura-icon-32.png",
**kwargs)
self.default_theme = "cura-light"
+ self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features"
+
self._boot_loading_time = time.time()
self._on_exit_callback_manager = OnExitCallbackManager(self)
@@ -166,20 +174,21 @@ class CuraApplication(QtApplication):
# Variables set from CLI
self._files_to_open = []
self._use_single_instance = False
- self._trigger_early_crash = False # For debug only
self._single_instance = None
+ self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions]
+
self._cura_package_manager = None
self._machine_action_manager = None
- self.empty_container = None
- self.empty_definition_changes_container = None
- self.empty_variant_container = None
- self.empty_material_container = None
- self.empty_quality_container = None
- self.empty_quality_changes_container = None
+ self.empty_container = None # type: EmptyInstanceContainer
+ self.empty_definition_changes_container = None # type: EmptyInstanceContainer
+ self.empty_variant_container = None # type: EmptyInstanceContainer
+ self.empty_material_container = None # type: EmptyInstanceContainer
+ self.empty_quality_container = None # type: EmptyInstanceContainer
+ self.empty_quality_changes_container = None # type: EmptyInstanceContainer
self._variant_manager = None
self._material_manager = None
@@ -189,6 +198,8 @@ class CuraApplication(QtApplication):
self._container_manager = None
self._object_manager = None
+ self._extruders_model = None
+ self._extruders_model_with_optional = None
self._build_plate_model = None
self._multi_build_plate_model = None
self._setting_visibility_presets_model = None
@@ -199,6 +210,7 @@ class CuraApplication(QtApplication):
self._quality_profile_drop_down_menu_model = None
self._custom_quality_profile_drop_down_menu_model = None
+ self._cura_API = CuraAPI(self)
self._physics = None
self._volume = None
@@ -215,7 +227,6 @@ class CuraApplication(QtApplication):
self._message_box_callback = None
self._message_box_callback_arguments = []
- self._preferred_mimetype = ""
self._i18n_catalog = None
self._currently_loading_files = []
@@ -238,9 +249,19 @@ class CuraApplication(QtApplication):
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
self._container_registry_class = CuraContainerRegistry
+ # Redefined here in order to please the typing.
+ self._container_registry = None # type: CuraContainerRegistry
from cura.CuraPackageManager import CuraPackageManager
self._package_manager_class = CuraPackageManager
+ @pyqtProperty(str, constant=True)
+ def ultimakerCloudApiRootUrl(self) -> str:
+ return UltimakerCloudAuthentication.CuraCloudAPIRoot
+
+ @pyqtProperty(str, constant = True)
+ def ultimakerCloudAccountRootUrl(self) -> str:
+ return UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
+
# Adds command line options to the command line parser. This should be called after the application is created and
# before the pre-start.
def addCommandLineOptions(self):
@@ -262,6 +283,9 @@ class CuraApplication(QtApplication):
help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog.")
self._cli_parser.add_argument("file", nargs = "*", help = "Files to load after starting the application.")
+ def getContainerRegistry(self) -> "CuraContainerRegistry":
+ return self._container_registry
+
def parseCliOptions(self):
super().parseCliOptions()
@@ -270,7 +294,10 @@ class CuraApplication(QtApplication):
sys.exit(0)
self._use_single_instance = self._cli_args.single_instance
- self._trigger_early_crash = self._cli_args.trigger_early_crash
+ # FOR TESTING ONLY
+ if self._cli_args.trigger_early_crash:
+ assert not "This crash is triggered by the trigger_early_crash command line argument."
+
for filename in self._cli_args.file:
self._files_to_open.append(os.path.abspath(filename))
@@ -280,7 +307,8 @@ class CuraApplication(QtApplication):
super().initialize()
self.__sendCommandToSingleInstance()
- self.__initializeSettingDefinitionsAndFunctions()
+ self._initializeSettingDefinitions()
+ self._initializeSettingFunctions()
self.__addAllResourcesAndContainerResources()
self.__addAllEmptyContainers()
self.__setLatestResouceVersionsForVersionUpgrade()
@@ -288,8 +316,6 @@ class CuraApplication(QtApplication):
self._machine_action_manager = MachineActionManager.MachineActionManager(self)
self._machine_action_manager.initialize()
- self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features"
-
def __sendCommandToSingleInstance(self):
self._single_instance = SingleInstance(self, self._files_to_open)
@@ -311,33 +337,44 @@ class CuraApplication(QtApplication):
resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
Resources.addSearchPath(resource_path)
- # Adds custom property types, settings types, and extra operators (functions) that need to be registered in
- # SettingDefinition and SettingFunction.
- def __initializeSettingDefinitionsAndFunctions(self):
+ @classmethod
+ def _initializeSettingDefinitions(cls):
# Need to do this before ContainerRegistry tries to load the machines
- SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
- SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True)
+ SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default=True,
+ read_only=True)
+ SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default=True,
+ read_only=True)
# this setting can be changed for each group in one-at-a-time mode
- SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True, read_only = True)
- SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True, read_only = True)
+ SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default=True,
+ read_only=True)
+ SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default=True,
+ read_only=True)
# From which stack the setting would inherit if not defined per object (handled in the engine)
# AND for settings which are not settable_per_mesh:
# which extruder is the only extruder this setting is obtained from
- SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1", depends_on = "value")
+ SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default="-1",
+ depends_on="value")
# For settings which are not settable_per_mesh and not settable_per_extruder:
# A function which determines the glabel/meshgroup value by looking at the values of the setting in all (used) extruders
- SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None, depends_on = "value")
+ SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default=None,
+ depends_on="value")
SettingDefinition.addSettingType("extruder", None, str, Validator)
SettingDefinition.addSettingType("optional_extruder", None, str, None)
SettingDefinition.addSettingType("[int]", None, str, None)
- SettingFunction.registerOperator("extruderValues", ExtruderManager.getExtruderValues)
- SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue)
- SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue)
- SettingFunction.registerOperator("defaultExtruderPosition", ExtruderManager.getDefaultExtruderPosition)
+
+ # Adds custom property types, settings types, and extra operators (functions) that need to be registered in
+ # SettingDefinition and SettingFunction.
+ def _initializeSettingFunctions(self):
+ self._cura_formula_functions = CuraFormulaFunctions(self)
+
+ SettingFunction.registerOperator("extruderValue", self._cura_formula_functions.getValueInExtruder)
+ SettingFunction.registerOperator("extruderValues", self._cura_formula_functions.getValuesInAllExtruders)
+ SettingFunction.registerOperator("resolveOrValue", self._cura_formula_functions.getResolveOrValue)
+ SettingFunction.registerOperator("defaultExtruderPosition", self._cura_formula_functions.getDefaultExtruderPosition)
# Adds all resources and container related resources.
def __addAllResourcesAndContainerResources(self) -> None:
@@ -368,7 +405,7 @@ class CuraApplication(QtApplication):
# Add empty variant, material and quality containers.
# Since they are empty, they should never be serialized and instead just programmatically created.
# We need them to simplify the switching between materials.
- self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container
+ self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container # type: EmptyInstanceContainer
self._container_registry.addContainer(
cura.Settings.cura_empty_instance_containers.empty_definition_changes_container)
@@ -403,40 +440,39 @@ class CuraApplication(QtApplication):
)
# Runs preparations that needs to be done before the starting process.
- def startSplashWindowPhase(self):
+ def startSplashWindowPhase(self) -> None:
super().startSplashWindowPhase()
- self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
+ if not self.getIsHeadLess():
+ self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
self.setRequiredPlugins([
# Misc.:
- "ConsoleLogger",
- "CuraEngineBackend",
- "UserAgreement",
- "FileLogger",
- "XmlMaterialProfile",
- "Toolbox",
- "PrepareStage",
- "MonitorStage",
- "LocalFileOutputDevice",
- "LocalContainerProvider",
+ "ConsoleLogger", #You want to be able to read the log if something goes wrong.
+ "CuraEngineBackend", #Cura is useless without this one since you can't slice.
+ "UserAgreement", #Our lawyers want every user to see this at least once.
+ "FileLogger", #You want to be able to read the log if something goes wrong.
+ "XmlMaterialProfile", #Cura crashes without this one.
+ "Toolbox", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
+ "PrepareStage", #Cura is useless without this one since you can't load models.
+ "PreviewStage", #This shows the list of the plugin views that are installed in Cura.
+ "MonitorStage", #Major part of Cura's functionality.
+ "LocalFileOutputDevice", #Major part of Cura's functionality.
+ "LocalContainerProvider", #Cura is useless without any profiles or setting definitions.
# Views:
- "SimpleView",
- "SimulationView",
- "SolidView",
+ "SimpleView", #Dependency of SolidView.
+ "SolidView", #Displays models. Cura is useless without it.
# Readers & Writers:
- "GCodeWriter",
- "STLReader",
+ "GCodeWriter", #Cura is useless if it can't write its output.
+ "STLReader", #Most common model format, so disabling this makes Cura 90% useless.
+ "3MFWriter", #Required for writing project files.
# Tools:
- "CameraTool",
- "MirrorTool",
- "RotateTool",
- "ScaleTool",
- "SelectionTool",
- "TranslateTool",
+ "CameraTool", #Needed to see the scene. Cura is useless without it.
+ "SelectionTool", #Dependency of the rest of the tools.
+ "TranslateTool", #You'll need this for almost every print.
])
self._i18n_catalog = i18nCatalog("cura")
@@ -473,7 +509,8 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
preferences.addPreference("cura/choice_on_open_project", "always_ask")
preferences.addPreference("cura/use_multi_build_plate", False)
-
+ preferences.addPreference("view/settings_list_height", 400)
+ preferences.addPreference("view/settings_visible", False)
preferences.addPreference("cura/currency", "€")
preferences.addPreference("cura/material_settings", "{}")
@@ -481,7 +518,11 @@ class CuraApplication(QtApplication):
preferences.addPreference("view/filter_current_build_plate", False)
preferences.addPreference("cura/sidebar_collapsed", False)
- self._need_to_show_user_agreement = not self.getPreferences().getValue("general/accepted_user_agreement")
+ preferences.addPreference("cura/favorite_materials", "")
+ preferences.addPreference("cura/expanded_brands", "")
+ preferences.addPreference("cura/expanded_types", "")
+
+ self._need_to_show_user_agreement = not preferences.getValue("general/accepted_user_agreement")
for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
@@ -495,26 +536,23 @@ class CuraApplication(QtApplication):
self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated)
- self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
- self._onGlobalContainerChanged()
-
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
CuraApplication.Created = True
def _onEngineCreated(self):
- self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
+ self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
@pyqtProperty(bool)
- def needToShowUserAgreement(self):
+ def needToShowUserAgreement(self) -> bool:
return self._need_to_show_user_agreement
- def setNeedToShowUserAgreement(self, set_value = True):
+ def setNeedToShowUserAgreement(self, set_value = True) -> None:
self._need_to_show_user_agreement = set_value
# DO NOT call this function to close the application, use checkAndExitApplication() instead which will perform
# pre-exit checks such as checking for in-progress USB printing, etc.
- def closeApplication(self):
+ def closeApplication(self) -> None:
Logger.log("i", "Close application")
main_window = self.getMainWindow()
if main_window is not None:
@@ -541,11 +579,11 @@ class CuraApplication(QtApplication):
showConfirmExitDialog = pyqtSignal(str, arguments = ["message"])
- def setConfirmExitDialogCallback(self, callback):
+ def setConfirmExitDialogCallback(self, callback: Callable) -> None:
self._confirm_exit_dialog_callback = callback
@pyqtSlot(bool)
- def callConfirmExitDialogCallback(self, yes_or_no: bool):
+ def callConfirmExitDialogCallback(self, yes_or_no: bool) -> None:
self._confirm_exit_dialog_callback(yes_or_no)
## Signal to connect preferences action in QML
@@ -553,9 +591,17 @@ class CuraApplication(QtApplication):
## Show the preferences window
@pyqtSlot()
- def showPreferences(self):
+ def showPreferences(self) -> None:
self.showPreferencesWindow.emit()
+ @override(Application)
+ def getGlobalContainerStack(self) -> Optional["GlobalStack"]:
+ return self._global_container_stack
+
+ @override(Application)
+ def setGlobalContainerStack(self, stack: "GlobalStack") -> None:
+ super().setGlobalContainerStack(stack)
+
## A reusable dialogbox
#
showMessageBox = pyqtSignal(str, str, str, str, int, int, arguments = ["title", "text", "informativeText", "detailedText", "buttons", "icon"])
@@ -567,7 +613,7 @@ class CuraApplication(QtApplication):
showDiscardOrKeepProfileChanges = pyqtSignal()
- def discardOrKeepProfileChanges(self):
+ def discardOrKeepProfileChanges(self) -> bool:
has_user_interaction = False
choice = self.getPreferences().getValue("cura/choice_on_profile_override")
if choice == "always_discard":
@@ -583,7 +629,7 @@ class CuraApplication(QtApplication):
return has_user_interaction
@pyqtSlot(str)
- def discardOrKeepProfileChangesClosed(self, option):
+ def discardOrKeepProfileChangesClosed(self, option: str) -> None:
global_stack = self.getGlobalContainerStack()
if option == "discard":
for extruder in global_stack.extruders.values():
@@ -603,9 +649,7 @@ class CuraApplication(QtApplication):
self._message_box_callback(button, *self._message_box_callback_arguments)
self._message_box_callback = None
self._message_box_callback_arguments = []
-
- showPrintMonitor = pyqtSignal(bool, arguments = ["show"])
-
+
def setSaveDataEnabled(self, enabled: bool) -> None:
self._save_data_enabled = enabled
@@ -631,12 +675,12 @@ class CuraApplication(QtApplication):
## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistry
- def _loadPlugins(self):
+ def _loadPlugins(self) -> None:
self._plugin_registry.addType("profile_reader", self._addProfileReader)
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
if Platform.isLinux():
- lib_suffixes = {"", "64", "32", "x32"} #A few common ones on different distributions.
+ lib_suffixes = {"", "64", "32", "x32"} # A few common ones on different distributions.
else:
lib_suffixes = {""}
for suffix in lib_suffixes:
@@ -668,11 +712,11 @@ class CuraApplication(QtApplication):
Logger.log("i", "Initializing quality manager")
from cura.Machines.QualityManager import QualityManager
- self._quality_manager = QualityManager(container_registry, parent = self)
+ self._quality_manager = QualityManager(self, parent = self)
self._quality_manager.initialize()
Logger.log("i", "Initializing machine manager")
- self._machine_manager = MachineManager(self)
+ self._machine_manager = MachineManager(self, parent = self)
Logger.log("i", "Initializing container manager")
self._container_manager = ContainerManager(self)
@@ -695,10 +739,11 @@ class CuraApplication(QtApplication):
self._print_information = PrintInformation.PrintInformation(self)
self._cura_actions = CuraActions.CuraActions(self)
- # Initialize setting visibility presets model
- self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self)
- default_visibility_profile = self._setting_visibility_presets_model.getItem(0)
- self.getPreferences().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
+ # Initialize setting visibility presets model.
+ self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self.getPreferences(), parent = self)
+
+ # Initialize Cura API
+ self._cura_API.initialize()
# Detect in which mode to run and execute that mode
if self._is_headless:
@@ -798,6 +843,11 @@ class CuraApplication(QtApplication):
def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
return self._setting_visibility_presets_model
+ def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions":
+ if self._cura_formula_functions is None:
+ self._cura_formula_functions = CuraFormulaFunctions(self)
+ return self._cura_formula_functions
+
def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
return self._machine_error_checker
@@ -806,20 +856,20 @@ class CuraApplication(QtApplication):
self._machine_manager = MachineManager(self)
return self._machine_manager
- def getExtruderManager(self, *args):
+ def getExtruderManager(self, *args) -> ExtruderManager:
if self._extruder_manager is None:
self._extruder_manager = ExtruderManager()
return self._extruder_manager
- def getVariantManager(self, *args):
+ def getVariantManager(self, *args) -> VariantManager:
return self._variant_manager
@pyqtSlot(result = QObject)
- def getMaterialManager(self, *args):
+ def getMaterialManager(self, *args) -> "MaterialManager":
return self._material_manager
@pyqtSlot(result = QObject)
- def getQualityManager(self, *args):
+ def getQualityManager(self, *args) -> "QualityManager":
return self._quality_manager
def getObjectsModel(self, *args):
@@ -828,23 +878,36 @@ class CuraApplication(QtApplication):
return self._object_manager
@pyqtSlot(result = QObject)
- def getMultiBuildPlateModel(self, *args):
+ def getExtrudersModel(self, *args) -> "ExtrudersModel":
+ if self._extruders_model is None:
+ self._extruders_model = ExtrudersModel(self)
+ return self._extruders_model
+
+ @pyqtSlot(result = QObject)
+ def getExtrudersModelWithOptional(self, *args) -> "ExtrudersModel":
+ if self._extruders_model_with_optional is None:
+ self._extruders_model_with_optional = ExtrudersModel(self)
+ self._extruders_model_with_optional.setAddOptionalExtruder(True)
+ return self._extruders_model_with_optional
+
+ @pyqtSlot(result = QObject)
+ def getMultiBuildPlateModel(self, *args) -> MultiBuildPlateModel:
if self._multi_build_plate_model is None:
self._multi_build_plate_model = MultiBuildPlateModel(self)
return self._multi_build_plate_model
@pyqtSlot(result = QObject)
- def getBuildPlateModel(self, *args):
+ def getBuildPlateModel(self, *args) -> BuildPlateModel:
if self._build_plate_model is None:
self._build_plate_model = BuildPlateModel(self)
return self._build_plate_model
- def getCuraSceneController(self, *args):
+ def getCuraSceneController(self, *args) -> CuraSceneController:
if self._cura_scene_controller is None:
self._cura_scene_controller = CuraSceneController.createCuraSceneController()
return self._cura_scene_controller
- def getSettingInheritanceManager(self, *args):
+ def getSettingInheritanceManager(self, *args) -> SettingInheritanceManager:
if self._setting_inheritance_manager is None:
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
return self._setting_inheritance_manager
@@ -887,6 +950,9 @@ class CuraApplication(QtApplication):
self._custom_quality_profile_drop_down_menu_model = CustomQualityProfilesDropDownMenuModel(self)
return self._custom_quality_profile_drop_down_menu_model
+ def getCuraAPI(self, *args, **kwargs) -> "CuraAPI":
+ return self._cura_API
+
## Registers objects for the QML engine to use.
#
# \param engine The QML engine.
@@ -898,7 +964,7 @@ class CuraApplication(QtApplication):
engine.rootContext().setContextProperty("CuraApplication", self)
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
- engine.rootContext().setContextProperty("CuraSDKVersion", CuraSDKVersion)
+ engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
@@ -909,15 +975,18 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
+ qmlRegisterType(NetworkMJPGImage, "Cura", 1, 0, "NetworkMJPGImage")
+
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel)
qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
+ qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel")
+ qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
- qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
- qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
+ qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel")
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
@@ -935,6 +1004,11 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance)
qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel")
+ qmlRegisterType(PrinterOutputDevice, "Cura", 1, 0, "PrinterOutputDevice")
+
+ from cura.API import CuraAPI
+ qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, "API", self.getCuraAPI)
+
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
qmlRegisterSingletonType(actions_url, "Cura", 1, 0, "Actions")
@@ -981,30 +1055,14 @@ class CuraApplication(QtApplication):
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
self._camera_animation.start()
- def _onGlobalContainerChanged(self):
- if self._global_container_stack is not None:
- machine_file_formats = [file_type.strip() for file_type in self._global_container_stack.getMetaDataEntry("file_formats").split(";")]
- new_preferred_mimetype = ""
- if machine_file_formats:
- new_preferred_mimetype = machine_file_formats[0]
-
- if new_preferred_mimetype != self._preferred_mimetype:
- self._preferred_mimetype = new_preferred_mimetype
- self.preferredOutputMimetypeChanged.emit()
-
requestAddPrinter = pyqtSignal()
activityChanged = pyqtSignal()
sceneBoundingBoxChanged = pyqtSignal()
- preferredOutputMimetypeChanged = pyqtSignal()
@pyqtProperty(bool, notify = activityChanged)
def platformActivity(self):
return self._platform_activity
- @pyqtProperty(str, notify=preferredOutputMimetypeChanged)
- def preferredOutputMimetype(self):
- return self._preferred_mimetype
-
@pyqtProperty(str, notify = sceneBoundingBoxChanged)
def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
@@ -1058,88 +1116,6 @@ class CuraApplication(QtApplication):
self._platform_activity = True if count > 0 else False
self.activityChanged.emit()
- # Remove all selected objects from the scene.
- @pyqtSlot()
- @deprecated("Moved to CuraActions", "2.6")
- def deleteSelection(self):
- if not self.getController().getToolsEnabled():
- return
- removed_group_nodes = []
- op = GroupedOperation()
- nodes = Selection.getAllSelectedObjects()
- for node in nodes:
- op.addOperation(RemoveSceneNodeOperation(node))
- group_node = node.getParent()
- if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes:
- remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes))
- if len(remaining_nodes_in_group) == 1:
- removed_group_nodes.append(group_node)
- op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
- op.addOperation(RemoveSceneNodeOperation(group_node))
- op.push()
-
- ## Remove an object from the scene.
- # Note that this only removes an object if it is selected.
- @pyqtSlot("quint64")
- @deprecated("Use deleteSelection instead", "2.6")
- def deleteObject(self, object_id):
- if not self.getController().getToolsEnabled():
- return
-
- node = self.getController().getScene().findObject(object_id)
-
- if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
- node = Selection.getSelectedObject(0)
-
- if node:
- op = GroupedOperation()
- op.addOperation(RemoveSceneNodeOperation(node))
-
- group_node = node.getParent()
- if group_node:
- # Note that at this point the node has not yet been deleted
- if len(group_node.getChildren()) <= 2 and group_node.callDecoration("isGroup"):
- op.addOperation(SetParentOperation(group_node.getChildren()[0], group_node.getParent()))
- op.addOperation(RemoveSceneNodeOperation(group_node))
-
- op.push()
-
- ## Create a number of copies of existing object.
- # \param object_id
- # \param count number of copies
- # \param min_offset minimum offset to other objects.
- @pyqtSlot("quint64", int)
- @deprecated("Use CuraActions::multiplySelection", "2.6")
- def multiplyObject(self, object_id, count, min_offset = 8):
- node = self.getController().getScene().findObject(object_id)
- if not node:
- node = Selection.getSelectedObject(0)
-
- while node.getParent() and node.getParent().callDecoration("isGroup"):
- node = node.getParent()
-
- job = MultiplyObjectsJob([node], count, min_offset)
- job.start()
- return
-
- ## Center object on platform.
- @pyqtSlot("quint64")
- @deprecated("Use CuraActions::centerSelection", "2.6")
- def centerObject(self, object_id):
- node = self.getController().getScene().findObject(object_id)
- if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
- node = Selection.getSelectedObject(0)
-
- if not node:
- return
-
- if node.getParent() and node.getParent().callDecoration("isGroup"):
- node = node.getParent()
-
- if node:
- op = SetTransformOperation(node, Vector())
- op.push()
-
## Select all nodes containing mesh data in the scene.
@pyqtSlot()
def selectAll(self):
@@ -1219,62 +1195,75 @@ class CuraApplication(QtApplication):
## Arrange all objects.
@pyqtSlot()
- def arrangeObjectsToAllBuildPlates(self):
- nodes = []
- for node in DepthFirstIterator(self.getController().getScene().getRoot()):
+ def arrangeObjectsToAllBuildPlates(self) -> None:
+ nodes_to_arrange = []
+ for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
if not isinstance(node, SceneNode):
continue
+
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
- if node.getParent() and node.getParent().callDecoration("isGroup"):
- continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
+
+ parent_node = node.getParent()
+ if parent_node and parent_node.callDecoration("isGroup"):
+ continue # Grouped nodes don't need resetting as their parent (the group) is reset)
+
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
+
+ bounding_box = node.getBoundingBox()
# Skip nodes that are too big
- if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
- nodes.append(node)
- job = ArrangeObjectsAllBuildPlatesJob(nodes)
+ if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
+ nodes_to_arrange.append(node)
+ job = ArrangeObjectsAllBuildPlatesJob(nodes_to_arrange)
job.start()
self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
# Single build plate
@pyqtSlot()
- def arrangeAll(self):
- nodes = []
+ def arrangeAll(self) -> None:
+ nodes_to_arrange = []
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
- for node in DepthFirstIterator(self.getController().getScene().getRoot()):
+ for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
if not isinstance(node, SceneNode):
continue
+
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
- if node.getParent() and node.getParent().callDecoration("isGroup"):
+
+ parent_node = node.getParent()
+ if parent_node and parent_node.callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
+
if not node.isSelectable():
continue # i.e. node with layer data
+
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
+
if node.callDecoration("getBuildPlateNumber") == active_build_plate:
# Skip nodes that are too big
- if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
- nodes.append(node)
- self.arrange(nodes, fixed_nodes = [])
+ bounding_box = node.getBoundingBox()
+ if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
+ nodes_to_arrange.append(node)
+ self.arrange(nodes_to_arrange, fixed_nodes = [])
## Arrange a set of nodes given a set of fixed nodes
# \param nodes nodes that we have to place
# \param fixed_nodes nodes that are placed in the arranger before finding spots for nodes
- def arrange(self, nodes, fixed_nodes):
+ def arrange(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode]) -> None:
min_offset = self.getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
job = ArrangeObjectsJob(nodes, fixed_nodes, min_offset = max(min_offset, 8))
job.start()
## Reload all mesh data on the screen from file.
@pyqtSlot()
- def reloadAll(self):
+ def reloadAll(self) -> None:
Logger.log("i", "Reloading all loaded mesh data.")
nodes = []
has_merged_nodes = False
- for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if not isinstance(node, CuraSceneNode) or not node.getMeshData() :
+ for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
+ if not isinstance(node, CuraSceneNode) or not node.getMeshData():
if node.getName() == "MergedMesh":
has_merged_nodes = True
continue
@@ -1288,7 +1277,7 @@ class CuraApplication(QtApplication):
file_name = node.getMeshData().getFileName()
if file_name:
job = ReadMeshJob(file_name)
- job._node = node
+ job._node = node # type: ignore
job.finished.connect(self._reloadMeshFinished)
if has_merged_nodes:
job.finished.connect(self.updateOriginOfMergedMeshes)
@@ -1297,20 +1286,8 @@ class CuraApplication(QtApplication):
else:
Logger.log("w", "Unable to reload data because we don't have a filename.")
-
- ## Get logging data of the backend engine
- # \returns \type{string} Logging data
- @pyqtSlot(result = str)
- def getEngineLog(self):
- log = ""
-
- for entry in self.getBackend().getLog():
- log += entry.decode()
-
- return log
-
@pyqtSlot("QStringList")
- def setExpandedCategories(self, categories):
+ def setExpandedCategories(self, categories: List[str]) -> None:
categories = list(set(categories))
categories.sort()
joined = ";".join(categories)
@@ -1321,7 +1298,7 @@ class CuraApplication(QtApplication):
expandedCategoriesChanged = pyqtSignal()
@pyqtProperty("QStringList", notify = expandedCategoriesChanged)
- def expandedCategories(self):
+ def expandedCategories(self) -> List[str]:
return self.getPreferences().getValue("cura/categories_expanded").split(";")
@pyqtSlot()
@@ -1371,13 +1348,12 @@ class CuraApplication(QtApplication):
## Updates origin position of all merged meshes
- # \param jobNode \type{Job} empty object which passed which is required by JobQueue
- def updateOriginOfMergedMeshes(self, jobNode):
+ def updateOriginOfMergedMeshes(self, _):
group_nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if isinstance(node, CuraSceneNode) and node.getName() == "MergedMesh":
- #checking by name might be not enough, the merged mesh should has "GroupDecorator" decorator
+ # Checking by name might be not enough, the merged mesh should has "GroupDecorator" decorator
for decorator in node.getDecorators():
if isinstance(decorator, GroupDecorator):
group_nodes.append(node)
@@ -1421,7 +1397,7 @@ class CuraApplication(QtApplication):
@pyqtSlot()
- def groupSelected(self):
+ def groupSelected(self) -> None:
# Create a group-node
group_node = CuraSceneNode()
group_decorator = GroupDecorator()
@@ -1437,7 +1413,8 @@ class CuraApplication(QtApplication):
# Remove nodes that are directly parented to another selected node from the selection so they remain parented
selected_nodes = Selection.getAllSelectedObjects().copy()
for node in selected_nodes:
- if node.getParent() in selected_nodes and not node.getParent().callDecoration("isGroup"):
+ parent = node.getParent()
+ if parent is not None and parent in selected_nodes and not parent.callDecoration("isGroup"):
Selection.remove(node)
# Move selected nodes into the group-node
@@ -1449,7 +1426,7 @@ class CuraApplication(QtApplication):
Selection.add(group_node)
@pyqtSlot()
- def ungroupSelected(self):
+ def ungroupSelected(self) -> None:
selected_objects = Selection.getAllSelectedObjects().copy()
for node in selected_objects:
if node.callDecoration("isGroup"):
@@ -1472,7 +1449,7 @@ class CuraApplication(QtApplication):
# Note: The group removes itself from the scene once all its children have left it,
# see GroupDecorator._onChildrenChanged
- def _createSplashScreen(self):
+ def _createSplashScreen(self) -> Optional[CuraSplashScreen.CuraSplashScreen]:
if self._is_headless:
return None
return CuraSplashScreen.CuraSplashScreen()
@@ -1590,6 +1567,11 @@ class CuraApplication(QtApplication):
job.start()
def _readMeshFinished(self, job):
+ global_container_stack = self.getGlobalContainerStack()
+ if not global_container_stack:
+ Logger.log("w", "Can't load meshes before a printer is added.")
+ return
+
nodes = job.getResult()
file_name = job.getFileName()
file_name_lower = file_name.lower()
@@ -1604,7 +1586,6 @@ class CuraApplication(QtApplication):
for node_ in DepthFirstIterator(root):
if node_.callDecoration("isSliceable") and node_.callDecoration("getBuildPlateNumber") == target_build_plate:
fixed_nodes.append(node_)
- global_container_stack = self.getGlobalContainerStack()
machine_width = global_container_stack.getProperty("machine_width", "value")
machine_depth = global_container_stack.getProperty("machine_depth", "value")
arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = fixed_nodes)
@@ -1634,7 +1615,9 @@ class CuraApplication(QtApplication):
is_non_sliceable = "." + file_extension in self._non_sliceable_extensions
if is_non_sliceable:
- self.callLater(lambda: self.getController().setActiveView("SimulationView"))
+ # Need to switch first to the preview stage and then to layer view
+ self.callLater(lambda: (self.getController().setActiveStage("PreviewStage"),
+ self.getController().setActiveView("SimulationView")))
block_slicing_decorator = BlockSlicingDecorator()
node.addDecorator(block_slicing_decorator)
@@ -1720,7 +1703,11 @@ class CuraApplication(QtApplication):
@pyqtSlot()
def showMoreInformationDialogForAnonymousDataCollection(self):
- cast(SliceInfo, self._plugin_registry.getPluginObject("SliceInfoPlugin")).showMoreInfoDialog()
+ try:
+ slice_info = self._plugin_registry.getPluginObject("SliceInfoPlugin")
+ slice_info.showMoreInfoDialog()
+ except PluginNotFoundError:
+ Logger.log("w", "Plugin SliceInfo was not found, so not able to show the info dialog.")
def addSidebarCustomMenuItem(self, menu_item: dict) -> None:
self._sidebar_custom_menu_items.append(menu_item)
diff --git a/cura/CuraVersion.py.in b/cura/CuraVersion.py.in
index 226b2183f2..1a500df248 100644
--- a/cura/CuraVersion.py.in
+++ b/cura/CuraVersion.py.in
@@ -1,9 +1,12 @@
-# Copyright (c) 2015 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+CuraAppName = "@CURA_APP_NAME@"
+CuraAppDisplayName = "@CURA_APP_DISPLAY_NAME@"
CuraVersion = "@CURA_VERSION@"
CuraBuildType = "@CURA_BUILDTYPE@"
CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
CuraSDKVersion = "@CURA_SDK_VERSION@"
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"
+CuraCloudAccountAPIRoot = "@CURA_CLOUD_ACCOUNT_API_ROOT@"
diff --git a/cura/CuraView.py b/cura/CuraView.py
new file mode 100644
index 0000000000..978c651b43
--- /dev/null
+++ b/cura/CuraView.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtProperty, QUrl
+
+from UM.View.View import View
+
+
+# Since Cura has a few pre-defined "space claims" for the locations of certain components, we've provided some structure
+# to indicate this.
+# MainComponent works in the same way the MainComponent of a stage.
+# the stageMenuComponent returns an item that should be used somehwere in the stage menu. It's up to the active stage
+# to actually do something with this.
+class CuraView(View):
+ def __init__(self, parent = None) -> None:
+ super().__init__(parent)
+
+ @pyqtProperty(QUrl, constant = True)
+ def mainComponent(self) -> QUrl:
+ return self.getDisplayComponent("main")
+
+ @pyqtProperty(QUrl, constant = True)
+ def stageMenuComponent(self) -> QUrl:
+ return self.getDisplayComponent("menu")
\ No newline at end of file
diff --git a/cura/GlobalStacksModel.py b/cura/GlobalStacksModel.py
new file mode 100644
index 0000000000..3c3321e5ca
--- /dev/null
+++ b/cura/GlobalStacksModel.py
@@ -0,0 +1,70 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+
+from UM.Qt.ListModel import ListModel
+
+from PyQt5.QtCore import pyqtProperty, Qt, QTimer
+
+from cura.PrinterOutputDevice import ConnectionType
+from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
+
+from cura.Settings.GlobalStack import GlobalStack
+
+
+class GlobalStacksModel(ListModel):
+ NameRole = Qt.UserRole + 1
+ IdRole = Qt.UserRole + 2
+ HasRemoteConnectionRole = Qt.UserRole + 3
+ ConnectionTypeRole = Qt.UserRole + 4
+ MetaDataRole = Qt.UserRole + 5
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+ self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.IdRole, "id")
+ self.addRoleName(self.HasRemoteConnectionRole, "hasRemoteConnection")
+ self.addRoleName(self.MetaDataRole, "metadata")
+ self._container_stacks = []
+
+ self._change_timer = QTimer()
+ self._change_timer.setInterval(200)
+ self._change_timer.setSingleShot(True)
+ self._change_timer.timeout.connect(self._update)
+
+ # Listen to changes
+ CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
+ CuraContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
+ CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
+ self._filter_dict = {}
+ self._updateDelayed()
+
+ ## Handler for container added/removed events from registry
+ def _onContainerChanged(self, container):
+ # We only need to update when the added / removed container GlobalStack
+ if isinstance(container, GlobalStack):
+ self._updateDelayed()
+
+ def _updateDelayed(self):
+ self._change_timer.start()
+
+ def _update(self) -> None:
+ items = []
+
+ container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
+
+ for container_stack in container_stacks:
+ has_remote_connection = False
+
+ for connection_type in container_stack.configuredConnectionTypes:
+ has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]
+
+ if container_stack.getMetaDataEntry("hidden", False) in ["True", True]:
+ continue
+
+ items.append({"name": container_stack.getMetaDataEntry("group_name", container_stack.getName()),
+ "id": container_stack.getId(),
+ "hasRemoteConnection": has_remote_connection,
+ "metadata": container_stack.getMetaData().copy()})
+ items.sort(key=lambda i: not i["hasRemoteConnection"])
+ self.setItems(items)
diff --git a/cura/LayerDataBuilder.py b/cura/LayerDataBuilder.py
index d6cc81a4e9..e8d1b8c59f 100755
--- a/cura/LayerDataBuilder.py
+++ b/cura/LayerDataBuilder.py
@@ -7,43 +7,36 @@ from UM.Mesh.MeshBuilder import MeshBuilder
from .LayerData import LayerData
import numpy
+from typing import Dict, Optional
## Builder class for constructing a LayerData object
class LayerDataBuilder(MeshBuilder):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- self._layers = {}
- self._element_counts = {}
+ self._layers = {} # type: Dict[int, Layer]
+ self._element_counts = {} # type: Dict[int, int]
- def addLayer(self, layer):
+ def addLayer(self, layer: int) -> None:
if layer not in self._layers:
self._layers[layer] = Layer(layer)
- def addPolygon(self, layer, polygon_type, data, line_width, line_thickness, line_feedrate):
- if layer not in self._layers:
- self.addLayer(layer)
+ def getLayer(self, layer: int) -> Optional[Layer]:
+ return self._layers.get(layer)
- p = LayerPolygon(self, polygon_type, data, line_width, line_thickness, line_feedrate)
- self._layers[layer].polygons.append(p)
-
- def getLayer(self, layer):
- if layer in self._layers:
- return self._layers[layer]
-
- def getLayers(self):
+ def getLayers(self) -> Dict[int, Layer]:
return self._layers
- def getElementCounts(self):
+ def getElementCounts(self) -> Dict[int, int]:
return self._element_counts
- def setLayerHeight(self, layer, height):
+ def setLayerHeight(self, layer: int, height: float) -> None:
if layer not in self._layers:
self.addLayer(layer)
self._layers[layer].setHeight(height)
- def setLayerThickness(self, layer, thickness):
+ def setLayerThickness(self, layer: int, thickness: float) -> None:
if layer not in self._layers:
self.addLayer(layer)
@@ -71,7 +64,7 @@ class LayerDataBuilder(MeshBuilder):
vertex_offset = 0
index_offset = 0
for layer, data in sorted(self._layers.items()):
- ( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices)
+ vertex_offset, index_offset = data.build(vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices)
self._element_counts[layer] = data.elementCount
self.addVertices(vertices)
diff --git a/cura/LayerDataDecorator.py b/cura/LayerDataDecorator.py
index c04479972a..ef82d8f5cc 100644
--- a/cura/LayerDataDecorator.py
+++ b/cura/LayerDataDecorator.py
@@ -1,13 +1,25 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Optional
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
+from cura.LayerData import LayerData
+
+
## Simple decorator to indicate a scene node holds layer data.
class LayerDataDecorator(SceneNodeDecorator):
def __init__(self):
super().__init__()
- self._layer_data = None
-
- def getLayerData(self):
+ self._layer_data = None # type: Optional[LayerData]
+
+ def getLayerData(self) -> Optional["LayerData"]:
return self._layer_data
-
- def setLayerData(self, layer_data):
- self._layer_data = layer_data
\ No newline at end of file
+
+ def setLayerData(self, layer_data: LayerData) -> None:
+ self._layer_data = layer_data
+
+ def __deepcopy__(self, memo) -> "LayerDataDecorator":
+ copied_decorator = LayerDataDecorator()
+ copied_decorator._layer_data = self._layer_data
+ return copied_decorator
diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py
index 9766e0c82a..072d5f94f5 100644
--- a/cura/LayerPolygon.py
+++ b/cura/LayerPolygon.py
@@ -2,9 +2,11 @@
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
-from typing import Any
+from typing import Any, Optional
import numpy
+from UM.Logger import Logger
+
class LayerPolygon:
NoneType = 0
@@ -18,22 +20,24 @@ class LayerPolygon:
MoveCombingType = 8
MoveRetractionType = 9
SupportInterfaceType = 10
- __number_of_types = 11
+ PrimeTower = 11
+ __number_of_types = 12
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
-
+
## LayerPolygon, used in ProcessSlicedLayersJob
- # \param extruder
+ # \param extruder The position of the extruder
# \param line_types array with line_types
# \param data new_points
# \param line_widths array with line widths
# \param line_thicknesses: array with type as index and thickness as value
# \param line_feedrates array with line feedrates
- def __init__(self, extruder, line_types, data, line_widths, line_thicknesses, line_feedrates):
+ def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
self._extruder = extruder
self._types = line_types
for i in range(len(self._types)):
- if self._types[i] >= self.__number_of_types: #Got faulty line data from the engine.
+ if self._types[i] >= self.__number_of_types: # Got faulty line data from the engine.
+ Logger.log("w", "Found an unknown line type: %s", i)
self._types[i] = self.NoneType
self._data = data
self._line_widths = line_widths
@@ -53,16 +57,16 @@ class LayerPolygon:
# Buffering the colors shouldn't be necessary as it is not
# re-used and can save alot of memory usage.
self._color_map = LayerPolygon.getColorMap()
- self._colors = self._color_map[self._types]
+ self._colors = self._color_map[self._types] # type: numpy.ndarray
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
# Should be generated in better way, not hardcoded.
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1], dtype=numpy.bool)
- self._build_cache_line_mesh_mask = None
- self._build_cache_needed_points = None
+ self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
+ self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
- def buildCache(self):
+ def buildCache(self) -> None:
# For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.
self._build_cache_line_mesh_mask = numpy.ones(self._jump_mask.shape, dtype=bool)
mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask)
@@ -90,10 +94,14 @@ class LayerPolygon:
# \param extruders : vertex numpy array to be filled
# \param line_types : vertex numpy array to be filled
# \param indices : index numpy array to be filled
- def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices):
+ def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray, colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray, extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None:
if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None:
self.buildCache()
-
+
+ if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None:
+ Logger.log("w", "Failed to build cache for layer polygon")
+ return
+
line_mesh_mask = self._build_cache_line_mesh_mask
needed_points_list = self._build_cache_needed_points
@@ -236,7 +244,8 @@ class LayerPolygon:
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
- theme.getColor("layerview_support_interface").getRgbF() # SupportInterfaceType
+ theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
+ theme.getColor("layerview_prime_tower").getRgbF()
])
return cls.__color_map
diff --git a/cura/MachineAction.py b/cura/MachineAction.py
index 969fef0edf..94b096f9c1 100644
--- a/cura/MachineAction.py
+++ b/cura/MachineAction.py
@@ -1,13 +1,13 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+import os
+
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
+from UM.Logger import Logger
from UM.PluginObject import PluginObject
from UM.PluginRegistry import PluginRegistry
-from UM.Application import Application
-
-import os
## Machine actions are actions that are added to a specific machine type. Examples of such actions are
@@ -19,7 +19,7 @@ class MachineAction(QObject, PluginObject):
## Create a new Machine action.
# \param key unique key of the machine action
# \param label Human readable label used to identify the machine action.
- def __init__(self, key, label = ""):
+ def __init__(self, key: str, label: str = "") -> None:
super().__init__()
self._key = key
self._label = label
@@ -30,14 +30,14 @@ class MachineAction(QObject, PluginObject):
labelChanged = pyqtSignal()
onFinished = pyqtSignal()
- def getKey(self):
+ def getKey(self) -> str:
return self._key
@pyqtProperty(str, notify = labelChanged)
- def label(self):
+ def label(self) -> str:
return self._label
- def setLabel(self, label):
+ def setLabel(self, label: str) -> None:
if self._label != label:
self._label = label
self.labelChanged.emit()
@@ -46,29 +46,35 @@ class MachineAction(QObject, PluginObject):
# This should not be re-implemented by child classes, instead re-implement _reset.
# /sa _reset
@pyqtSlot()
- def reset(self):
+ def reset(self) -> None:
self._finished = False
self._reset()
## Protected implementation of reset.
# /sa reset()
- def _reset(self):
+ def _reset(self) -> None:
pass
@pyqtSlot()
- def setFinished(self):
+ def setFinished(self) -> None:
self._finished = True
self._reset()
self.onFinished.emit()
@pyqtProperty(bool, notify = onFinished)
- def finished(self):
+ def finished(self) -> bool:
return self._finished
## Protected helper to create a view object based on provided QML.
- def _createViewFromQML(self):
- path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url)
- self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
+ def _createViewFromQML(self) -> None:
+ plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
+ if plugin_path is None:
+ Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
+ return
+ path = os.path.join(plugin_path, self._qml_url)
+
+ from cura.CuraApplication import CuraApplication
+ self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
@pyqtProperty(QObject, constant = True)
def displayItem(self):
diff --git a/cura/MachineActionManager.py b/cura/MachineActionManager.py
index 65eb33b54c..db0f7bfbff 100644
--- a/cura/MachineActionManager.py
+++ b/cura/MachineActionManager.py
@@ -1,12 +1,18 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import TYPE_CHECKING, Optional, List, Set, Dict
+
from PyQt5.QtCore import QObject
from UM.FlameProfiler import pyqtSlot
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type
-from UM.Settings.DefinitionContainer import DefinitionContainer
+
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
+ from cura.Settings.GlobalStack import GlobalStack
+ from .MachineAction import MachineAction
## Raised when trying to add an unknown machine action as a required action
@@ -20,46 +26,54 @@ class NotUniqueMachineActionError(Exception):
class MachineActionManager(QObject):
- def __init__(self, application, parent = None):
- super().__init__(parent)
+ def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
+ super().__init__(parent = parent)
self._application = application
+ self._container_registry = self._application.getContainerRegistry()
- self._machine_actions = {} # Dict of all known machine actions
- self._required_actions = {} # Dict of all required actions by definition ID
- self._supported_actions = {} # Dict of all supported actions by definition ID
- self._first_start_actions = {} # Dict of all actions that need to be done when first added by definition ID
+ # Keeps track of which machines have already been processed so we don't do that again.
+ self._definition_ids_with_default_actions_added = set() # type: Set[str]
+
+ # Dict of all known machine actions
+ self._machine_actions = {} # type: Dict[str, MachineAction]
+ # Dict of all required actions by definition ID
+ self._required_actions = {} # type: Dict[str, List[MachineAction]]
+ # Dict of all supported actions by definition ID
+ self._supported_actions = {} # type: Dict[str, List[MachineAction]]
+ # Dict of all actions that need to be done when first added by definition ID
+ self._first_start_actions = {} # type: Dict[str, List[MachineAction]]
def initialize(self):
- container_registry = self._application.getContainerRegistry()
-
# Add machine_action as plugin type
PluginRegistry.addType("machine_action", self.addMachineAction)
- # Ensure that all containers that were registered before creation of this registry are also handled.
- # This should not have any effect, but it makes it safer if we ever refactor the order of things.
- for container in container_registry.findDefinitionContainers():
- self._onContainerAdded(container)
+ # Adds all default machine actions that are defined in the machine definition for the given machine.
+ def addDefaultMachineActions(self, global_stack: "GlobalStack") -> None:
+ definition_id = global_stack.definition.getId()
- container_registry.containerAdded.connect(self._onContainerAdded)
+ if definition_id in self._definition_ids_with_default_actions_added:
+ Logger.log("i", "Default machine actions have been added for machine definition [%s], do nothing.",
+ definition_id)
+ return
- def _onContainerAdded(self, container):
- ## Ensure that the actions are added to this manager
- if isinstance(container, DefinitionContainer):
- supported_actions = container.getMetaDataEntry("supported_actions", [])
- for action in supported_actions:
- self.addSupportedAction(container.getId(), action)
+ supported_actions = global_stack.getMetaDataEntry("supported_actions", [])
+ for action_key in supported_actions:
+ self.addSupportedAction(definition_id, action_key)
- required_actions = container.getMetaDataEntry("required_actions", [])
- for action in required_actions:
- self.addRequiredAction(container.getId(), action)
+ required_actions = global_stack.getMetaDataEntry("required_actions", [])
+ for action_key in required_actions:
+ self.addRequiredAction(definition_id, action_key)
- first_start_actions = container.getMetaDataEntry("first_start_actions", [])
- for action in first_start_actions:
- self.addFirstStartAction(container.getId(), action)
+ first_start_actions = global_stack.getMetaDataEntry("first_start_actions", [])
+ for action_key in first_start_actions:
+ self.addFirstStartAction(definition_id, action_key)
+
+ self._definition_ids_with_default_actions_added.add(definition_id)
+ Logger.log("i", "Default machine actions added for machine definition [%s]", definition_id)
## Add a required action to a machine
# Raises an exception when the action is not recognised.
- def addRequiredAction(self, definition_id, action_key):
+ def addRequiredAction(self, definition_id: str, action_key: str) -> None:
if action_key in self._machine_actions:
if definition_id in self._required_actions:
if self._machine_actions[action_key] not in self._required_actions[definition_id]:
@@ -70,7 +84,7 @@ class MachineActionManager(QObject):
raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id))
## Add a supported action to a machine.
- def addSupportedAction(self, definition_id, action_key):
+ def addSupportedAction(self, definition_id: str, action_key: str) -> None:
if action_key in self._machine_actions:
if definition_id in self._supported_actions:
if self._machine_actions[action_key] not in self._supported_actions[definition_id]:
@@ -81,13 +95,10 @@ class MachineActionManager(QObject):
Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id)
## Add an action to the first start list of a machine.
- def addFirstStartAction(self, definition_id, action_key, index = None):
+ def addFirstStartAction(self, definition_id: str, action_key: str) -> None:
if action_key in self._machine_actions:
if definition_id in self._first_start_actions:
- if index is not None:
- self._first_start_actions[definition_id].insert(index, self._machine_actions[action_key])
- else:
- self._first_start_actions[definition_id].append(self._machine_actions[action_key])
+ self._first_start_actions[definition_id].append(self._machine_actions[action_key])
else:
self._first_start_actions[definition_id] = [self._machine_actions[action_key]]
else:
@@ -95,7 +106,7 @@ class MachineActionManager(QObject):
## Add a (unique) MachineAction
# if the Key of the action is not unique, an exception is raised.
- def addMachineAction(self, action):
+ def addMachineAction(self, action: "MachineAction") -> None:
if action.getKey() not in self._machine_actions:
self._machine_actions[action.getKey()] = action
else:
@@ -105,7 +116,7 @@ class MachineActionManager(QObject):
# \param definition_id The ID of the definition you want the supported actions of
# \returns set of supported actions.
@pyqtSlot(str, result = "QVariantList")
- def getSupportedActions(self, definition_id):
+ def getSupportedActions(self, definition_id: str) -> List["MachineAction"]:
if definition_id in self._supported_actions:
return list(self._supported_actions[definition_id])
else:
@@ -114,11 +125,11 @@ class MachineActionManager(QObject):
## Get all actions required by given machine
# \param definition_id The ID of the definition you want the required actions of
# \returns set of required actions.
- def getRequiredActions(self, definition_id):
+ def getRequiredActions(self, definition_id: str) -> List["MachineAction"]:
if definition_id in self._required_actions:
return self._required_actions[definition_id]
else:
- return set()
+ return list()
## Get all actions that need to be performed upon first start of a given machine.
# Note that contrary to required / supported actions a list is returned (as it could be required to run the same
@@ -126,7 +137,7 @@ class MachineActionManager(QObject):
# \param definition_id The ID of the definition that you want to get the "on added" actions for.
# \returns List of actions.
@pyqtSlot(str, result="QVariantList")
- def getFirstStartActions(self, definition_id):
+ def getFirstStartActions(self, definition_id: str) -> List["MachineAction"]:
if definition_id in self._first_start_actions:
return self._first_start_actions[definition_id]
else:
@@ -134,7 +145,7 @@ class MachineActionManager(QObject):
## Remove Machine action from manager
# \param action to remove
- def removeMachineAction(self, action):
+ def removeMachineAction(self, action: "MachineAction") -> None:
try:
del self._machine_actions[action.getKey()]
except KeyError:
@@ -143,7 +154,7 @@ class MachineActionManager(QObject):
## Get MachineAction by key
# \param key String of key to select
# \return Machine action if found, None otherwise
- def getMachineAction(self, key):
+ def getMachineAction(self, key: str) -> Optional["MachineAction"]:
if key in self._machine_actions:
return self._machine_actions[key]
else:
diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py
index 944579e354..eef1c63127 100644
--- a/cura/Machines/ContainerNode.py
+++ b/cura/Machines/ContainerNode.py
@@ -9,9 +9,6 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Logger import Logger
from UM.Settings.InstanceContainer import InstanceContainer
-if TYPE_CHECKING:
- from cura.Machines.QualityGroup import QualityGroup
-
##
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
@@ -24,29 +21,34 @@ if TYPE_CHECKING:
# This is used in Variant, Material, and Quality Managers.
#
class ContainerNode:
- __slots__ = ("metadata", "container", "children_map")
+ __slots__ = ("_metadata", "_container", "children_map")
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
- self.metadata = metadata
- self.container = None
- self.children_map = OrderedDict() #type: OrderedDict[str, Union[QualityGroup, ContainerNode]]
+ self._metadata = metadata
+ self._container = None # type: Optional[InstanceContainer]
+ self.children_map = OrderedDict() # type: ignore # This is because it's children are supposed to override it.
## Get an entry value from the metadata
def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
- if self.metadata is None:
+ if self._metadata is None:
return default
- return self.metadata.get(entry, default)
+ return self._metadata.get(entry, default)
+
+ def getMetadata(self) -> Dict[str, Any]:
+ if self._metadata is None:
+ return {}
+ return self._metadata
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
return self.children_map.get(child_key)
def getContainer(self) -> Optional["InstanceContainer"]:
- if self.metadata is None:
+ if self._metadata is None:
Logger.log("e", "Cannot get container for a ContainerNode without metadata.")
return None
- if self.container is None:
- container_id = self.metadata["id"]
+ if self._container is None:
+ container_id = self._metadata["id"]
from UM.Settings.ContainerRegistry import ContainerRegistry
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
if not container_list:
@@ -54,9 +56,9 @@ class ContainerNode:
error_message = ConfigurationErrorMessage.getInstance()
error_message.addFaultyContainers(container_id)
return None
- self.container = container_list[0]
+ self._container = container_list[0]
- return self.container
+ return self._container
def __str__(self) -> str:
return "%s[%s]" % (self.__class__.__name__, self.getMetaDataEntry("id"))
diff --git a/cura/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py
index 37de4f30ce..fb11123af6 100644
--- a/cura/Machines/MachineErrorChecker.py
+++ b/cura/Machines/MachineErrorChecker.py
@@ -50,7 +50,7 @@ class MachineErrorChecker(QObject):
self._error_check_timer.setInterval(100)
self._error_check_timer.setSingleShot(True)
- def initialize(self):
+ def initialize(self) -> None:
self._error_check_timer.timeout.connect(self._rescheduleCheck)
# Reconnect all signals when the active machine gets changed.
@@ -62,23 +62,23 @@ class MachineErrorChecker(QObject):
self._onMachineChanged()
- def _onMachineChanged(self):
+ def _onMachineChanged(self) -> None:
if self._global_stack:
- self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
+ self._global_stack.propertyChanged.disconnect(self.startErrorCheckPropertyChanged)
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
for extruder in self._global_stack.extruders.values():
- extruder.propertyChanged.disconnect(self.startErrorCheck)
+ extruder.propertyChanged.disconnect(self.startErrorCheckPropertyChanged)
extruder.containersChanged.disconnect(self.startErrorCheck)
self._global_stack = self._machine_manager.activeMachine
if self._global_stack:
- self._global_stack.propertyChanged.connect(self.startErrorCheck)
+ self._global_stack.propertyChanged.connect(self.startErrorCheckPropertyChanged)
self._global_stack.containersChanged.connect(self.startErrorCheck)
for extruder in self._global_stack.extruders.values():
- extruder.propertyChanged.connect(self.startErrorCheck)
+ extruder.propertyChanged.connect(self.startErrorCheckPropertyChanged)
extruder.containersChanged.connect(self.startErrorCheck)
hasErrorUpdated = pyqtSignal()
@@ -93,8 +93,15 @@ class MachineErrorChecker(QObject):
def needToWaitForResult(self) -> bool:
return self._need_to_check or self._check_in_progress
+ # Start the error check for property changed
+ # this is seperate from the startErrorCheck because it ignores a number property types
+ def startErrorCheckPropertyChanged(self, key, property_name):
+ if property_name != "value":
+ return
+ self.startErrorCheck()
+
# Starts the error check timer to schedule a new error check.
- def startErrorCheck(self, *args):
+ def startErrorCheck(self, *args) -> None:
if not self._check_in_progress:
self._need_to_check = True
self.needToWaitForResultChanged.emit()
@@ -103,7 +110,7 @@ class MachineErrorChecker(QObject):
# This function is called by the timer to reschedule a new error check.
# If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
# to notify the current check to stop and start a new one.
- def _rescheduleCheck(self):
+ def _rescheduleCheck(self) -> None:
if self._check_in_progress and not self._need_to_check:
self._need_to_check = True
self.needToWaitForResultChanged.emit()
@@ -128,7 +135,7 @@ class MachineErrorChecker(QObject):
self._start_time = time.time()
Logger.log("d", "New error check scheduled.")
- def _checkStack(self):
+ def _checkStack(self) -> None:
if self._need_to_check:
Logger.log("d", "Need to check for errors again. Discard the current progress and reschedule a check.")
self._check_in_progress = False
@@ -169,7 +176,7 @@ class MachineErrorChecker(QObject):
# Schedule the check for the next key
self._application.callLater(self._checkStack)
- def _setResult(self, result: bool):
+ def _setResult(self, result: bool) -> None:
if result != self._has_errors:
self._has_errors = result
self.hasErrorUpdated.emit()
diff --git a/cura/Machines/MaterialGroup.py b/cura/Machines/MaterialGroup.py
index 8a73796a7a..e05647e674 100644
--- a/cura/Machines/MaterialGroup.py
+++ b/cura/Machines/MaterialGroup.py
@@ -24,8 +24,8 @@ class MaterialGroup:
def __init__(self, name: str, root_material_node: "MaterialNode") -> None:
self.name = name
self.is_read_only = False
- self.root_material_node = root_material_node # type: MaterialNode
- self.derived_material_node_list = [] # type: List[MaterialNode]
+ self.root_material_node = root_material_node # type: MaterialNode
+ self.derived_material_node_list = [] # type: List[MaterialNode]
def __str__(self) -> str:
return "%s[%s]" % (self.__class__.__name__, self.name)
diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py
index d5a7d5d089..68e894642d 100644
--- a/cura/Machines/MaterialManager.py
+++ b/cura/Machines/MaterialManager.py
@@ -4,7 +4,7 @@
from collections import defaultdict, OrderedDict
import copy
import uuid
-from typing import Dict, Optional, TYPE_CHECKING
+from typing import Dict, Optional, TYPE_CHECKING, Any, Set, List, cast, Tuple
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
@@ -39,24 +39,34 @@ if TYPE_CHECKING:
class MaterialManager(QObject):
materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated.
+ favoritesUpdated = pyqtSignal() # Emitted whenever the favorites are changed
def __init__(self, container_registry, parent = None):
super().__init__(parent)
self._application = Application.getInstance()
self._container_registry = container_registry # type: ContainerRegistry
- self._fallback_materials_map = dict() # material_type -> generic material metadata
- self._material_group_map = dict() # root_material_id -> MaterialGroup
- self._diameter_machine_nozzle_buildplate_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
+ # Material_type -> generic material metadata
+ self._fallback_materials_map = dict() # type: Dict[str, Dict[str, Any]]
+
+ # Root_material_id -> MaterialGroup
+ self._material_group_map = dict() # type: Dict[str, MaterialGroup]
+
+ # Approximate diameter str
+ self._diameter_machine_nozzle_buildplate_material_map = dict() # type: Dict[str, Dict[str, MaterialNode]]
# We're using these two maps to convert between the specific diameter material id and the generic material id
# because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
# i.e. generic_pla -> generic_pla_175
- self._material_diameter_map = defaultdict(dict) # root_material_id -> approximate diameter str -> root_material_id for that diameter
- self._diameter_material_map = dict() # material id including diameter (generic_pla_175) -> material root id (generic_pla)
+ # root_material_id -> approximate diameter str -> root_material_id for that diameter
+ self._material_diameter_map = defaultdict(dict) # type: Dict[str, Dict[str, str]]
+
+ # Material id including diameter (generic_pla_175) -> material root id (generic_pla)
+ self._diameter_material_map = dict() # type: Dict[str, str]
# This is used in Legacy UM3 send material function and the material management page.
- self._guid_material_groups_map = defaultdict(list) # GUID -> a list of material_groups
+ # GUID -> a list of material_groups
+ self._guid_material_groups_map = defaultdict(list) # type: Dict[str, List[MaterialGroup]]
# The machine definition ID for the non-machine-specific materials.
# This is used as the last fallback option if the given machine-specific material(s) cannot be found.
@@ -75,13 +85,15 @@ class MaterialManager(QObject):
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
- def initialize(self):
+ self._favorites = set() # type: Set[str]
+
+ def initialize(self) -> None:
# Find all materials and put them in a matrix for quick search.
material_metadatas = {metadata["id"]: metadata for metadata in
self._container_registry.findContainersMetadata(type = "material") if
- metadata.get("GUID")}
+ metadata.get("GUID")} # type: Dict[str, Dict[str, Any]]
- self._material_group_map = dict()
+ self._material_group_map = dict() # type: Dict[str, MaterialGroup]
# Map #1
# root_material_id -> MaterialGroup
@@ -90,7 +102,7 @@ class MaterialManager(QObject):
if material_id == "empty_material":
continue
- root_material_id = material_metadata.get("base_file")
+ root_material_id = material_metadata.get("base_file", "")
if root_material_id not in self._material_group_map:
self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
@@ -106,26 +118,26 @@ class MaterialManager(QObject):
# Map #1.5
# GUID -> material group list
- self._guid_material_groups_map = defaultdict(list)
+ self._guid_material_groups_map = defaultdict(list) # type: Dict[str, List[MaterialGroup]]
for root_material_id, material_group in self._material_group_map.items():
- guid = material_group.root_material_node.metadata["GUID"]
+ guid = material_group.root_material_node.getMetaDataEntry("GUID", "")
self._guid_material_groups_map[guid].append(material_group)
# Map #2
# Lookup table for material type -> fallback material metadata, only for read-only materials
- grouped_by_type_dict = dict()
+ grouped_by_type_dict = dict() # type: Dict[str, Any]
material_types_without_fallback = set()
for root_material_id, material_node in self._material_group_map.items():
- material_type = material_node.root_material_node.metadata["material"]
+ material_type = material_node.root_material_node.getMetaDataEntry("material", "")
if material_type not in grouped_by_type_dict:
grouped_by_type_dict[material_type] = {"generic": None,
"others": []}
material_types_without_fallback.add(material_type)
- brand = material_node.root_material_node.metadata["brand"]
+ brand = material_node.root_material_node.getMetaDataEntry("brand", "")
if brand.lower() == "generic":
to_add = True
if material_type in grouped_by_type_dict:
- diameter = material_node.root_material_node.metadata.get("approximate_diameter")
+ diameter = material_node.root_material_node.getMetaDataEntry("approximate_diameter", "")
if diameter != self._default_approximate_diameter_for_quality_search:
to_add = False # don't add if it's not the default diameter
@@ -134,7 +146,7 @@ class MaterialManager(QObject):
# - if it's in the list, it means that is a new material without fallback
# - if it is not, then it is a custom material with a fallback material (parent)
if material_type in material_types_without_fallback:
- grouped_by_type_dict[material_type] = material_node.root_material_node.metadata
+ grouped_by_type_dict[material_type] = material_node.root_material_node._metadata
material_types_without_fallback.remove(material_type)
# Remove the materials that have no fallback materials
@@ -151,15 +163,15 @@ class MaterialManager(QObject):
self._diameter_material_map = dict()
# Group the material IDs by the same name, material, brand, and color but with different diameters.
- material_group_dict = dict()
+ material_group_dict = dict() # type: Dict[Tuple[Any], Dict[str, str]]
keys_to_fetch = ("name", "material", "brand", "color")
for root_material_id, machine_node in self._material_group_map.items():
- root_material_metadata = machine_node.root_material_node.metadata
+ root_material_metadata = machine_node.root_material_node._metadata
- key_data = []
+ key_data_list = [] # type: List[Any]
for key in keys_to_fetch:
- key_data.append(root_material_metadata.get(key))
- key_data = tuple(key_data)
+ key_data_list.append(machine_node.root_material_node.getMetaDataEntry(key))
+ key_data = cast(Tuple[Any], tuple(key_data_list)) # type: Tuple[Any]
# If the key_data doesn't exist, it doesn't matter if the material is read only...
if key_data not in material_group_dict:
@@ -168,8 +180,8 @@ class MaterialManager(QObject):
# ...but if key_data exists, we just overwrite it if the material is read only, otherwise we skip it
if not machine_node.is_read_only:
continue
- approximate_diameter = root_material_metadata.get("approximate_diameter")
- material_group_dict[key_data][approximate_diameter] = root_material_metadata["id"]
+ approximate_diameter = machine_node.root_material_node.getMetaDataEntry("approximate_diameter", "")
+ material_group_dict[key_data][approximate_diameter] = machine_node.root_material_node.getMetaDataEntry("id", "")
# Map [root_material_id][diameter] -> root_material_id for this diameter
for data_dict in material_group_dict.values():
@@ -188,13 +200,17 @@ class MaterialManager(QObject):
# Map #4
# "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
- self._diameter_machine_nozzle_buildplate_material_map = dict()
+ self._diameter_machine_nozzle_buildplate_material_map = dict() # type: Dict[str, Dict[str, MaterialNode]]
for material_metadata in material_metadatas.values():
self.__addMaterialMetadataIntoLookupTree(material_metadata)
+ favorites = self._application.getPreferences().getValue("cura/favorite_materials")
+ for item in favorites.split(";"):
+ self._favorites.add(item)
+
self.materialsUpdated.emit()
- def __addMaterialMetadataIntoLookupTree(self, material_metadata: dict) -> None:
+ def __addMaterialMetadataIntoLookupTree(self, material_metadata: Dict[str, Any]) -> None:
material_id = material_metadata["id"]
# We don't store empty material in the lookup tables
@@ -281,11 +297,15 @@ class MaterialManager(QObject):
return self._material_diameter_map.get(root_material_id, {}).get(approximate_diameter, root_material_id)
def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
- return self._diameter_material_map.get(root_material_id)
+ return self._diameter_material_map.get(root_material_id, "")
- def getMaterialGroupListByGUID(self, guid: str) -> Optional[list]:
+ def getMaterialGroupListByGUID(self, guid: str) -> Optional[List[MaterialGroup]]:
return self._guid_material_groups_map.get(guid)
+ # Returns a dict of all material groups organized by root_material_id.
+ def getAllMaterialGroups(self) -> Dict[str, "MaterialGroup"]:
+ return self._material_group_map
+
#
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
#
@@ -321,6 +341,7 @@ class MaterialManager(QObject):
machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
material_id_metadata_dict = dict() # type: Dict[str, MaterialNode]
+ excluded_materials = set()
for current_node in nodes_to_check:
if current_node is None:
continue
@@ -329,25 +350,27 @@ class MaterialManager(QObject):
# Do not exclude other materials that are of the same type.
for material_id, node in current_node.material_map.items():
if material_id in machine_exclude_materials:
- Logger.log("d", "Exclude material [%s] for machine [%s]",
- material_id, machine_definition.getId())
+ excluded_materials.add(material_id)
continue
if material_id not in material_id_metadata_dict:
material_id_metadata_dict[material_id] = node
+ if excluded_materials:
+ Logger.log("d", "Exclude materials {excluded_materials} for machine {machine_definition_id}".format(excluded_materials = ", ".join(excluded_materials), machine_definition_id = machine_definition_id))
+
return material_id_metadata_dict
#
# A convenience function to get available materials for the given machine with the extruder position.
#
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
- extruder_stack: "ExtruderStack") -> Optional[dict]:
+ extruder_stack: "ExtruderStack") -> Optional[Dict[str, MaterialNode]]:
buildplate_name = machine.getBuildplateName()
nozzle_name = None
if extruder_stack.variant.getId() != "empty_variant":
nozzle_name = extruder_stack.variant.getName()
- diameter = extruder_stack.approximateMaterialDiameter
+ diameter = extruder_stack.getApproximateMaterialDiameter()
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
return self.getAvailableMaterials(machine.definition, nozzle_name, buildplate_name, diameter)
@@ -359,7 +382,7 @@ class MaterialManager(QObject):
# 2. cannot find any material InstanceContainers with the given settings.
#
def getMaterialNode(self, machine_definition_id: str, nozzle_name: Optional[str],
- buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
+ buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["MaterialNode"]:
# round the diameter to get the approximate diameter
rounded_diameter = str(round(diameter))
if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
@@ -368,7 +391,7 @@ class MaterialManager(QObject):
return None
# If there are nozzle materials, get the nozzle-specific material
- machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
+ machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter] # type: Dict[str, MaterialNode]
machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
nozzle_node = None
buildplate_node = None
@@ -417,7 +440,7 @@ class MaterialManager(QObject):
# Look at the guid to material dictionary
root_material_id = None
for material_group in self._guid_material_groups_map[material_guid]:
- root_material_id = material_group.root_material_node.metadata["id"]
+ root_material_id = cast(str, material_group.root_material_node.getMetaDataEntry("id", ""))
break
if not root_material_id:
@@ -428,6 +451,28 @@ class MaterialManager(QObject):
material_diameter, root_material_id)
return node
+ # There are 2 ways to get fallback materials;
+ # - A fallback by type (@sa getFallbackMaterialIdByMaterialType), which adds the generic version of this material
+ # - A fallback by GUID; If a material has been duplicated, it should also check if the original materials do have
+ # a GUID. This should only be done if the material itself does not have a quality just yet.
+ def getFallBackMaterialIdsByMaterial(self, material: "InstanceContainer") -> List[str]:
+ results = [] # type: List[str]
+
+ material_groups = self.getMaterialGroupListByGUID(material.getMetaDataEntry("GUID"))
+ for material_group in material_groups: # type: ignore
+ if material_group.name != material.getId():
+ # If the material in the group is read only, put it at the front of the list (since that is the most
+ # likely one to get a result)
+ if material_group.is_read_only:
+ results.insert(0, material_group.name)
+ else:
+ results.append(material_group.name)
+
+ fallback = self.getFallbackMaterialIdByMaterialType(material.getMetaDataEntry("material"))
+ if fallback is not None:
+ results.append(fallback)
+ return results
+
#
# Used by QualityManager. Built-in quality profiles may be based on generic material IDs such as "generic_pla".
# For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use
@@ -460,12 +505,22 @@ class MaterialManager(QObject):
buildplate_name = global_stack.getBuildplateName()
machine_definition = global_stack.definition
- if extruder_definition is None:
- extruder_definition = global_stack.extruders[position].definition
- if extruder_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
- # At this point the extruder_definition is not None
- material_diameter = extruder_definition.getProperty("material_diameter", "value")
+ # The extruder-compatible material diameter in the extruder definition may not be the correct value because
+ # the user can change it in the definition_changes container.
+ if extruder_definition is None:
+ extruder_stack_or_definition = global_stack.extruders[position]
+ is_extruder_stack = True
+ else:
+ extruder_stack_or_definition = extruder_definition
+ is_extruder_stack = False
+
+ if extruder_stack_or_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
+ if is_extruder_stack:
+ material_diameter = extruder_stack_or_definition.getCompatibleMaterialDiameter()
+ else:
+ material_diameter = extruder_stack_or_definition.getProperty("material_diameter", "value")
+
if isinstance(material_diameter, SettingFunction):
material_diameter = material_diameter(global_stack)
approximate_material_diameter = str(round(material_diameter))
@@ -493,7 +548,7 @@ class MaterialManager(QObject):
# Sets the new name for the given material.
#
@pyqtSlot("QVariant", str)
- def setMaterialName(self, material_node: "MaterialNode", name: str):
+ def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
root_material_id = material_node.getMetaDataEntry("base_file")
if root_material_id is None:
return
@@ -511,7 +566,7 @@ class MaterialManager(QObject):
# Removes the given material.
#
@pyqtSlot("QVariant")
- def removeMaterial(self, material_node: "MaterialNode"):
+ def removeMaterial(self, material_node: "MaterialNode") -> None:
root_material_id = material_node.getMetaDataEntry("base_file")
if root_material_id is not None:
self.removeMaterialByRootId(root_material_id)
@@ -521,8 +576,8 @@ class MaterialManager(QObject):
# Returns the root material ID of the duplicated material if successful.
#
@pyqtSlot("QVariant", result = str)
- def duplicateMaterial(self, material_node, new_base_id = None, new_metadata = None) -> Optional[str]:
- root_material_id = material_node.metadata["base_file"]
+ def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
+ root_material_id = cast(str, material_node.getMetaDataEntry("base_file", ""))
material_group = self.getMaterialGroup(root_material_id)
if not material_group:
@@ -573,11 +628,16 @@ class MaterialManager(QObject):
for container_to_add in new_containers:
container_to_add.setDirty(True)
self._container_registry.addContainer(container_to_add)
+
+ # if the duplicated material was favorite then the new material should also be added to favorite.
+ if root_material_id in self.getFavorites():
+ self.addFavorite(new_base_id)
+
return new_base_id
#
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
- #
+ # Returns the ID of the newly created material.
@pyqtSlot(result = str)
def createMaterial(self) -> str:
from UM.i18n import i18nCatalog
@@ -588,8 +648,10 @@ class MaterialManager(QObject):
machine_manager = self._application.getMachineManager()
extruder_stack = machine_manager.activeStack
+ machine_definition = self._application.getGlobalContainerStack().definition
+ root_material_id = machine_definition.getMetaDataEntry("preferred_material", default = "generic_pla")
+
approximate_diameter = str(extruder_stack.approximateMaterialDiameter)
- root_material_id = "generic_pla"
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
material_group = self.getMaterialGroup(root_material_id)
@@ -608,3 +670,29 @@ class MaterialManager(QObject):
new_base_id = new_id,
new_metadata = new_metadata)
return new_id
+
+ @pyqtSlot(str)
+ def addFavorite(self, root_material_id: str) -> None:
+ self._favorites.add(root_material_id)
+ self.materialsUpdated.emit()
+
+ # Ensure all settings are saved.
+ self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
+ self._application.saveSettings()
+
+ @pyqtSlot(str)
+ def removeFavorite(self, root_material_id: str) -> None:
+ try:
+ self._favorites.remove(root_material_id)
+ except KeyError:
+ Logger.log("w", "Could not delete material %s from favorites as it was already deleted", root_material_id)
+ return
+ self.materialsUpdated.emit()
+
+ # Ensure all settings are saved.
+ self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
+ self._application.saveSettings()
+
+ @pyqtSlot()
+ def getFavorites(self):
+ return self._favorites
diff --git a/cura/Machines/MaterialNode.py b/cura/Machines/MaterialNode.py
index 04423d7b2c..a4dcb0564f 100644
--- a/cura/Machines/MaterialNode.py
+++ b/cura/Machines/MaterialNode.py
@@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import Optional, Dict
-
+from typing import Optional, Dict, Any
+from collections import OrderedDict
from .ContainerNode import ContainerNode
@@ -14,6 +14,12 @@ from .ContainerNode import ContainerNode
class MaterialNode(ContainerNode):
__slots__ = ("material_map", "children_map")
- def __init__(self, metadata: Optional[dict] = None) -> None:
+ def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
super().__init__(metadata = metadata)
self.material_map = {} # type: Dict[str, MaterialNode] # material_root_id -> material_node
+
+ # We overide this as we want to indicate that MaterialNodes can only contain other material nodes.
+ self.children_map = OrderedDict() # type: OrderedDict[str, "MaterialNode"]
+
+ def getChildNode(self, child_key: str) -> Optional["MaterialNode"]:
+ return self.children_map.get(child_key)
\ No newline at end of file
diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py
index 4759c8b5b0..e936877923 100644
--- a/cura/Machines/Models/BaseMaterialsModel.py
+++ b/cura/Machines/Models/BaseMaterialsModel.py
@@ -1,46 +1,66 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional, Dict, Set
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
-
-from UM.Application import Application
from UM.Qt.ListModel import ListModel
-#
-# This is the base model class for GenericMaterialsModel and BrandMaterialsModel
-# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
-# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
-# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
-#
+## This is the base model class for GenericMaterialsModel and MaterialBrandsModel.
+# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
+# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
+# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
+from cura.Machines.MaterialNode import MaterialNode
+
+
class BaseMaterialsModel(ListModel):
- RootMaterialIdRole = Qt.UserRole + 1
- IdRole = Qt.UserRole + 2
- NameRole = Qt.UserRole + 3
- BrandRole = Qt.UserRole + 4
- MaterialRole = Qt.UserRole + 5
- ColorRole = Qt.UserRole + 6
- ContainerNodeRole = Qt.UserRole + 7
extruderPositionChanged = pyqtSignal()
+ enabledChanged = pyqtSignal()
def __init__(self, parent = None):
super().__init__(parent)
- self._application = Application.getInstance()
- self._machine_manager = self._application.getMachineManager()
+ from cura.CuraApplication import CuraApplication
- self.addRoleName(self.RootMaterialIdRole, "root_material_id")
- self.addRoleName(self.IdRole, "id")
- self.addRoleName(self.NameRole, "name")
- self.addRoleName(self.BrandRole, "brand")
- self.addRoleName(self.MaterialRole, "material")
- self.addRoleName(self.ColorRole, "color_name")
- self.addRoleName(self.ContainerNodeRole, "container_node")
+ self._application = CuraApplication.getInstance()
+
+ # Make these managers available to all material models
+ self._container_registry = self._application.getInstance().getContainerRegistry()
+ self._machine_manager = self._application.getMachineManager()
+ self._material_manager = self._application.getMaterialManager()
+
+ # Update the stack and the model data when the machine changes
+ self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
+
+ # Update this model when switching machines
+ self._machine_manager.activeStackChanged.connect(self._update)
+
+ # Update this model when list of materials changes
+ self._material_manager.materialsUpdated.connect(self._update)
+
+ self.addRoleName(Qt.UserRole + 1, "root_material_id")
+ self.addRoleName(Qt.UserRole + 2, "id")
+ self.addRoleName(Qt.UserRole + 3, "GUID")
+ self.addRoleName(Qt.UserRole + 4, "name")
+ self.addRoleName(Qt.UserRole + 5, "brand")
+ self.addRoleName(Qt.UserRole + 6, "description")
+ self.addRoleName(Qt.UserRole + 7, "material")
+ self.addRoleName(Qt.UserRole + 8, "color_name")
+ self.addRoleName(Qt.UserRole + 9, "color_code")
+ self.addRoleName(Qt.UserRole + 10, "density")
+ self.addRoleName(Qt.UserRole + 11, "diameter")
+ self.addRoleName(Qt.UserRole + 12, "approximate_diameter")
+ self.addRoleName(Qt.UserRole + 13, "adhesion_info")
+ self.addRoleName(Qt.UserRole + 14, "is_read_only")
+ self.addRoleName(Qt.UserRole + 15, "container_node")
+ self.addRoleName(Qt.UserRole + 16, "is_favorite")
self._extruder_position = 0
self._extruder_stack = None
- # Update the stack and the model data when the machine changes
- self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
+
+ self._available_materials = None # type: Optional[Dict[str, MaterialNode]]
+ self._favorite_ids = set() # type: Set[str]
+ self._enabled = True
def _updateExtruderStack(self):
global_stack = self._machine_manager.activeMachine
@@ -49,9 +69,11 @@ class BaseMaterialsModel(ListModel):
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.disconnect(self._update)
+ self._extruder_stack.approximateMaterialDiameterChanged.disconnect(self._update)
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.connect(self._update)
+ self._extruder_stack.approximateMaterialDiameterChanged.connect(self._update)
# Force update the model when the extruder stack changes
self._update()
@@ -65,8 +87,66 @@ class BaseMaterialsModel(ListModel):
def extruderPosition(self) -> int:
return self._extruder_position
- #
- # This is an abstract method that needs to be implemented by
- #
+ def setEnabled(self, enabled):
+ if self._enabled != enabled:
+ self._enabled = enabled
+ if self._enabled:
+ # ensure the data is there again.
+ self._update()
+ self.enabledChanged.emit()
+
+ @pyqtProperty(bool, fset=setEnabled, notify=enabledChanged)
+ def enabled(self):
+ return self._enabled
+
+ ## This is an abstract method that needs to be implemented by the specific
+ # models themselves.
def _update(self):
pass
+
+ ## This method is used by all material models in the beginning of the
+ # _update() method in order to prevent errors. It's the same in all models
+ # so it's placed here for easy access.
+ def _canUpdate(self):
+ global_stack = self._machine_manager.activeMachine
+
+ if global_stack is None or not self._enabled:
+ return False
+
+ extruder_position = str(self._extruder_position)
+
+ if extruder_position not in global_stack.extruders:
+ return False
+
+ extruder_stack = global_stack.extruders[extruder_position]
+ self._available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
+ if self._available_materials is None:
+ return False
+
+ return True
+
+ ## This is another convenience function which is shared by all material
+ # models so it's put here to avoid having so much duplicated code.
+ def _createMaterialItem(self, root_material_id, container_node):
+ metadata = container_node.getMetadata()
+ item = {
+ "root_material_id": root_material_id,
+ "id": metadata["id"],
+ "container_id": metadata["id"], # TODO: Remove duplicate in material manager qml
+ "GUID": metadata["GUID"],
+ "name": metadata["name"],
+ "brand": metadata["brand"],
+ "description": metadata["description"],
+ "material": metadata["material"],
+ "color_name": metadata["color_name"],
+ "color_code": metadata.get("color_code", ""),
+ "density": metadata.get("properties", {}).get("density", ""),
+ "diameter": metadata.get("properties", {}).get("diameter", ""),
+ "approximate_diameter": metadata["approximate_diameter"],
+ "adhesion_info": metadata["adhesion_info"],
+ "is_read_only": self._container_registry.isReadOnly(metadata["id"]),
+ "container_node": container_node,
+ "is_favorite": root_material_id in self._favorite_ids
+ }
+ return item
+
diff --git a/cura/Machines/Models/BrandMaterialsModel.py b/cura/Machines/Models/BrandMaterialsModel.py
deleted file mode 100644
index ad48b3ea21..0000000000
--- a/cura/Machines/Models/BrandMaterialsModel.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# Copyright (c) 2018 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
-
-from UM.Qt.ListModel import ListModel
-from UM.Logger import Logger
-from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
-
-
-#
-# This is an intermediate model to group materials with different colours for a same brand and type.
-#
-class MaterialsModelGroupedByType(ListModel):
- NameRole = Qt.UserRole + 1
- ColorsRole = Qt.UserRole + 2
-
- def __init__(self, parent = None):
- super().__init__(parent)
-
- self.addRoleName(self.NameRole, "name")
- self.addRoleName(self.ColorsRole, "colors")
-
-
-#
-# This model is used to show branded materials in the material drop down menu.
-# The structure of the menu looks like this:
-# Brand -> Material Type -> list of materials
-#
-# To illustrate, a branded material menu may look like this:
-# Ultimaker -> PLA -> Yellow PLA
-# -> Black PLA
-# -> ...
-# -> ABS -> White ABS
-# ...
-#
-class BrandMaterialsModel(ListModel):
- NameRole = Qt.UserRole + 1
- MaterialsRole = Qt.UserRole + 2
-
- extruderPositionChanged = pyqtSignal()
-
- def __init__(self, parent = None):
- super().__init__(parent)
-
- self.addRoleName(self.NameRole, "name")
- self.addRoleName(self.MaterialsRole, "materials")
-
- self._extruder_position = 0
- self._extruder_stack = None
-
- from cura.CuraApplication import CuraApplication
- self._machine_manager = CuraApplication.getInstance().getMachineManager()
- self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
- self._material_manager = CuraApplication.getInstance().getMaterialManager()
-
- self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
- self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
- self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
- self._update()
-
- def _updateExtruderStack(self):
- global_stack = self._machine_manager.activeMachine
- if global_stack is None:
- return
-
- if self._extruder_stack is not None:
- self._extruder_stack.pyqtContainersChanged.disconnect(self._update)
- self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
- if self._extruder_stack is not None:
- self._extruder_stack.pyqtContainersChanged.connect(self._update)
- # Force update the model when the extruder stack changes
- self._update()
-
- def setExtruderPosition(self, position: int):
- if self._extruder_stack is None or self._extruder_position != position:
- self._extruder_position = position
- self._updateExtruderStack()
- self.extruderPositionChanged.emit()
-
- @pyqtProperty(int, fset=setExtruderPosition, notify=extruderPositionChanged)
- def extruderPosition(self) -> int:
- return self._extruder_position
-
- def _update(self):
- Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
- global_stack = self._machine_manager.activeMachine
- if global_stack is None:
- self.setItems([])
- return
- extruder_position = str(self._extruder_position)
- if extruder_position not in global_stack.extruders:
- self.setItems([])
- return
- extruder_stack = global_stack.extruders[str(self._extruder_position)]
-
- available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
- extruder_stack)
- if available_material_dict is None:
- self.setItems([])
- return
-
- brand_item_list = []
- brand_group_dict = {}
- for root_material_id, container_node in available_material_dict.items():
- metadata = container_node.metadata
- brand = metadata["brand"]
- # Only add results for generic materials
- if brand.lower() == "generic":
- continue
-
- # Do not include the materials from a to-be-removed package
- if bool(metadata.get("removed", False)):
- continue
-
- if brand not in brand_group_dict:
- brand_group_dict[brand] = {}
-
- material_type = metadata["material"]
- if material_type not in brand_group_dict[brand]:
- brand_group_dict[brand][material_type] = []
-
- item = {"root_material_id": root_material_id,
- "id": metadata["id"],
- "name": metadata["name"],
- "brand": metadata["brand"],
- "material": metadata["material"],
- "color_name": metadata["color_name"],
- "container_node": container_node
- }
- brand_group_dict[brand][material_type].append(item)
-
- for brand, material_dict in brand_group_dict.items():
- brand_item = {"name": brand,
- "materials": MaterialsModelGroupedByType(self)}
-
- material_type_item_list = []
- for material_type, material_list in material_dict.items():
- material_type_item = {"name": material_type,
- "colors": BaseMaterialsModel(self)}
- material_type_item["colors"].clear()
-
- # Sort materials by name
- material_list = sorted(material_list, key = lambda x: x["name"].upper())
- material_type_item["colors"].setItems(material_list)
-
- material_type_item_list.append(material_type_item)
-
- # Sort material type by name
- material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper())
- brand_item["materials"].setItems(material_type_item_list)
-
- brand_item_list.append(brand_item)
-
- # Sort brand by name
- brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper())
- self.setItems(brand_item_list)
diff --git a/cura/Machines/Models/FavoriteMaterialsModel.py b/cura/Machines/Models/FavoriteMaterialsModel.py
new file mode 100644
index 0000000000..98a2a01597
--- /dev/null
+++ b/cura/Machines/Models/FavoriteMaterialsModel.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
+
+## Model that shows the list of favorite materials.
+class FavoriteMaterialsModel(BaseMaterialsModel):
+ def __init__(self, parent = None):
+ super().__init__(parent)
+ self._update()
+
+ def _update(self):
+ if not self._canUpdate():
+ return
+
+ # Get updated list of favorites
+ self._favorite_ids = self._material_manager.getFavorites()
+
+ item_list = []
+
+ for root_material_id, container_node in self._available_materials.items():
+ metadata = container_node.getMetadata()
+
+ # Do not include the materials from a to-be-removed package
+ if bool(metadata.get("removed", False)):
+ continue
+
+ # Only add results for favorite materials
+ if root_material_id not in self._favorite_ids:
+ continue
+
+ item = self._createMaterialItem(root_material_id, container_node)
+ item_list.append(item)
+
+ # Sort the item list alphabetically by name
+ item_list = sorted(item_list, key = lambda d: d["brand"].upper())
+
+ self.setItems(item_list)
diff --git a/cura/Machines/Models/GenericMaterialsModel.py b/cura/Machines/Models/GenericMaterialsModel.py
index f14b039c91..8f41dd6a70 100644
--- a/cura/Machines/Models/GenericMaterialsModel.py
+++ b/cura/Machines/Models/GenericMaterialsModel.py
@@ -4,63 +4,36 @@
from UM.Logger import Logger
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
-
class GenericMaterialsModel(BaseMaterialsModel):
def __init__(self, parent = None):
super().__init__(parent)
-
- from cura.CuraApplication import CuraApplication
- self._machine_manager = CuraApplication.getInstance().getMachineManager()
- self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
- self._material_manager = CuraApplication.getInstance().getMaterialManager()
-
- self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
- self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
self._update()
def _update(self):
- Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
+ if not self._canUpdate():
+ return
- global_stack = self._machine_manager.activeMachine
- if global_stack is None:
- self.setItems([])
- return
- extruder_position = str(self._extruder_position)
- if extruder_position not in global_stack.extruders:
- self.setItems([])
- return
- extruder_stack = global_stack.extruders[extruder_position]
-
- available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
- extruder_stack)
- if available_material_dict is None:
- self.setItems([])
- return
+ # Get updated list of favorites
+ self._favorite_ids = self._material_manager.getFavorites()
item_list = []
- for root_material_id, container_node in available_material_dict.items():
- metadata = container_node.metadata
- # Only add results for generic materials
- if metadata["brand"].lower() != "generic":
- continue
+ for root_material_id, container_node in self._available_materials.items():
+ metadata = container_node.getMetadata()
# Do not include the materials from a to-be-removed package
if bool(metadata.get("removed", False)):
continue
- item = {"root_material_id": root_material_id,
- "id": metadata["id"],
- "name": metadata["name"],
- "brand": metadata["brand"],
- "material": metadata["material"],
- "color_name": metadata["color_name"],
- "container_node": container_node
- }
+ # Only add results for generic materials
+ if metadata["brand"].lower() != "generic":
+ continue
+
+ item = self._createMaterialItem(root_material_id, container_node)
item_list.append(item)
- # Sort the item list by material name alphabetically
+ # Sort the item list alphabetically by name
item_list = sorted(item_list, key = lambda d: d["name"].upper())
self.setItems(item_list)
diff --git a/cura/Machines/Models/MachineManagementModel.py b/cura/Machines/Models/MachineManagementModel.py
index 7dc51f07f7..3297b8a467 100644
--- a/cura/Machines/Models/MachineManagementModel.py
+++ b/cura/Machines/Models/MachineManagementModel.py
@@ -52,14 +52,14 @@ class MachineManagementModel(ListModel):
"um_network_key": "*",
"hidden": "False"}
self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers)
- self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("connect_group_name"))
+ self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("group_name", ""))
for container in self._network_container_stacks:
metadata = container.getMetaData().copy()
if container.getBottom():
metadata["definition_name"] = container.getBottom().getName()
- items.append({"name": metadata["connect_group_name"],
+ items.append({"name": metadata.get("group_name", ""),
"id": container.getId(),
"metadata": metadata,
"group": catalog.i18nc("@info:title", "Network enabled printers")})
diff --git a/cura/Machines/Models/MaterialBrandsModel.py b/cura/Machines/Models/MaterialBrandsModel.py
new file mode 100644
index 0000000000..ac82cf6670
--- /dev/null
+++ b/cura/Machines/Models/MaterialBrandsModel.py
@@ -0,0 +1,103 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
+from UM.Qt.ListModel import ListModel
+from UM.Logger import Logger
+from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
+
+class MaterialTypesModel(ListModel):
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(Qt.UserRole + 1, "name")
+ self.addRoleName(Qt.UserRole + 2, "brand")
+ self.addRoleName(Qt.UserRole + 3, "colors")
+
+class MaterialBrandsModel(BaseMaterialsModel):
+
+ extruderPositionChanged = pyqtSignal()
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(Qt.UserRole + 1, "name")
+ self.addRoleName(Qt.UserRole + 2, "material_types")
+
+ self._update()
+
+ def _update(self):
+ if not self._canUpdate():
+ return
+ # Get updated list of favorites
+ self._favorite_ids = self._material_manager.getFavorites()
+
+ brand_item_list = []
+ brand_group_dict = {}
+
+ # Part 1: Generate the entire tree of brands -> material types -> spcific materials
+ for root_material_id, container_node in self._available_materials.items():
+ # Do not include the materials from a to-be-removed package
+ if bool(container_node.getMetaDataEntry("removed", False)):
+ continue
+
+ # Add brands we haven't seen yet to the dict, skipping generics
+ brand = container_node.getMetaDataEntry("brand", "")
+ if brand.lower() == "generic":
+ continue
+ if brand not in brand_group_dict:
+ brand_group_dict[brand] = {}
+
+ # Add material types we haven't seen yet to the dict
+ material_type = container_node.getMetaDataEntry("material", "")
+ if material_type not in brand_group_dict[brand]:
+ brand_group_dict[brand][material_type] = []
+
+ # Now handle the individual materials
+ item = self._createMaterialItem(root_material_id, container_node)
+ brand_group_dict[brand][material_type].append(item)
+
+ # Part 2: Organize the tree into models
+ #
+ # Normally, the structure of the menu looks like this:
+ # Brand -> Material Type -> Specific Material
+ #
+ # To illustrate, a branded material menu may look like this:
+ # Ultimaker ┳ PLA ┳ Yellow PLA
+ # ┃ ┣ Black PLA
+ # ┃ ┗ ...
+ # ┃
+ # ┗ ABS ┳ White ABS
+ # ┗ ...
+ for brand, material_dict in brand_group_dict.items():
+
+ material_type_item_list = []
+ brand_item = {
+ "name": brand,
+ "material_types": MaterialTypesModel(self)
+ }
+
+ for material_type, material_list in material_dict.items():
+ material_type_item = {
+ "name": material_type,
+ "brand": brand,
+ "colors": BaseMaterialsModel(self)
+ }
+ material_type_item["colors"].clear()
+
+ # Sort materials by name
+ material_list = sorted(material_list, key = lambda x: x["name"].upper())
+ material_type_item["colors"].setItems(material_list)
+
+ material_type_item_list.append(material_type_item)
+
+ # Sort material type by name
+ material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper())
+ brand_item["material_types"].setItems(material_type_item_list)
+
+ brand_item_list.append(brand_item)
+
+ # Sort brand by name
+ brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper())
+ self.setItems(brand_item_list)
diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py
deleted file mode 100644
index 46e9cb887a..0000000000
--- a/cura/Machines/Models/MaterialManagementModel.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright (c) 2018 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-from PyQt5.QtCore import Qt
-
-from UM.Logger import Logger
-from UM.Qt.ListModel import ListModel
-
-
-#
-# This model is for the Material management page.
-#
-class MaterialManagementModel(ListModel):
- RootMaterialIdRole = Qt.UserRole + 1
- DisplayNameRole = Qt.UserRole + 2
- BrandRole = Qt.UserRole + 3
- MaterialTypeRole = Qt.UserRole + 4
- ColorNameRole = Qt.UserRole + 5
- ColorCodeRole = Qt.UserRole + 6
- ContainerNodeRole = Qt.UserRole + 7
- ContainerIdRole = Qt.UserRole + 8
-
- DescriptionRole = Qt.UserRole + 9
- AdhesionInfoRole = Qt.UserRole + 10
- ApproximateDiameterRole = Qt.UserRole + 11
- GuidRole = Qt.UserRole + 12
- DensityRole = Qt.UserRole + 13
- DiameterRole = Qt.UserRole + 14
- IsReadOnlyRole = Qt.UserRole + 15
-
- def __init__(self, parent = None):
- super().__init__(parent)
-
- self.addRoleName(self.RootMaterialIdRole, "root_material_id")
- self.addRoleName(self.DisplayNameRole, "name")
- self.addRoleName(self.BrandRole, "brand")
- self.addRoleName(self.MaterialTypeRole, "material")
- self.addRoleName(self.ColorNameRole, "color_name")
- self.addRoleName(self.ColorCodeRole, "color_code")
- self.addRoleName(self.ContainerNodeRole, "container_node")
- self.addRoleName(self.ContainerIdRole, "container_id")
-
- self.addRoleName(self.DescriptionRole, "description")
- self.addRoleName(self.AdhesionInfoRole, "adhesion_info")
- self.addRoleName(self.ApproximateDiameterRole, "approximate_diameter")
- self.addRoleName(self.GuidRole, "guid")
- self.addRoleName(self.DensityRole, "density")
- self.addRoleName(self.DiameterRole, "diameter")
- self.addRoleName(self.IsReadOnlyRole, "is_read_only")
-
- from cura.CuraApplication import CuraApplication
- self._container_registry = CuraApplication.getInstance().getContainerRegistry()
- self._machine_manager = CuraApplication.getInstance().getMachineManager()
- self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
- self._material_manager = CuraApplication.getInstance().getMaterialManager()
-
- self._machine_manager.globalContainerChanged.connect(self._update)
- self._extruder_manager.activeExtruderChanged.connect(self._update)
- self._material_manager.materialsUpdated.connect(self._update)
-
- self._update()
-
- def _update(self):
- Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
-
- global_stack = self._machine_manager.activeMachine
- if global_stack is None:
- self.setItems([])
- return
- active_extruder_stack = self._machine_manager.activeStack
-
- available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
- active_extruder_stack)
- if available_material_dict is None:
- self.setItems([])
- return
-
- material_list = []
- for root_material_id, container_node in available_material_dict.items():
- keys_to_fetch = ("name",
- "brand",
- "material",
- "color_name",
- "color_code",
- "description",
- "adhesion_info",
- "approximate_diameter",)
-
- item = {"root_material_id": container_node.metadata["base_file"],
- "container_node": container_node,
- "guid": container_node.metadata["GUID"],
- "container_id": container_node.metadata["id"],
- "density": container_node.metadata.get("properties", {}).get("density", ""),
- "diameter": container_node.metadata.get("properties", {}).get("diameter", ""),
- "is_read_only": self._container_registry.isReadOnly(container_node.metadata["id"]),
- }
-
- for key in keys_to_fetch:
- item[key] = container_node.metadata.get(key, "")
-
- material_list.append(item)
-
- material_list = sorted(material_list, key = lambda k: (k["brand"].upper(), k["name"].upper()))
- self.setItems(material_list)
diff --git a/cura/Machines/Models/MultiBuildPlateModel.py b/cura/Machines/Models/MultiBuildPlateModel.py
index 958e93837a..add960a545 100644
--- a/cura/Machines/Models/MultiBuildPlateModel.py
+++ b/cura/Machines/Models/MultiBuildPlateModel.py
@@ -4,6 +4,7 @@
from PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty
from UM.Application import Application
+from UM.Scene.Camera import Camera
from UM.Scene.Selection import Selection
from UM.Qt.ListModel import ListModel
@@ -34,8 +35,9 @@ class MultiBuildPlateModel(ListModel):
self._active_build_plate = -1
def setMaxBuildPlate(self, max_build_plate):
- self._max_build_plate = max_build_plate
- self.maxBuildPlateChanged.emit()
+ if self._max_build_plate != max_build_plate:
+ self._max_build_plate = max_build_plate
+ self.maxBuildPlateChanged.emit()
## Return the highest build plate number
@pyqtProperty(int, notify = maxBuildPlateChanged)
@@ -43,15 +45,17 @@ class MultiBuildPlateModel(ListModel):
return self._max_build_plate
def setActiveBuildPlate(self, nr):
- self._active_build_plate = nr
- self.activeBuildPlateChanged.emit()
+ if self._active_build_plate != nr:
+ self._active_build_plate = nr
+ self.activeBuildPlateChanged.emit()
@pyqtProperty(int, notify = activeBuildPlateChanged)
def activeBuildPlate(self):
return self._active_build_plate
def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args):
- self._update_timer.start()
+ if not isinstance(args[0], Camera):
+ self._update_timer.start()
def _updateSelectedObjectBuildPlateNumbers(self, *args):
result = set()
diff --git a/cura/Machines/Models/NozzleModel.py b/cura/Machines/Models/NozzleModel.py
index 9d97106d6b..785ff5b9b9 100644
--- a/cura/Machines/Models/NozzleModel.py
+++ b/cura/Machines/Models/NozzleModel.py
@@ -33,8 +33,6 @@ class NozzleModel(ListModel):
def _update(self):
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
- self.items.clear()
-
global_stack = self._machine_manager.activeMachine
if global_stack is None:
self.setItems([])
diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
index 59c4f4fa5b..deabb6e9ba 100644
--- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
+++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
@@ -1,11 +1,12 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import Qt
+from PyQt5.QtCore import Qt, QTimer
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
+from UM.Settings.SettingFunction import SettingFunction
from cura.Machines.QualityManager import QualityGroup
@@ -21,6 +22,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
AvailableRole = Qt.UserRole + 5
QualityGroupRole = Qt.UserRole + 6
QualityChangesGroupRole = Qt.UserRole + 7
+ IsExperimentalRole = Qt.UserRole + 8
def __init__(self, parent = None):
super().__init__(parent)
@@ -32,19 +34,28 @@ class QualityProfilesDropDownMenuModel(ListModel):
self.addRoleName(self.AvailableRole, "available") #Whether the quality profile is available in our current nozzle + material.
self.addRoleName(self.QualityGroupRole, "quality_group")
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
+ self.addRoleName(self.IsExperimentalRole, "is_experimental")
self._application = Application.getInstance()
self._machine_manager = self._application.getMachineManager()
self._quality_manager = Application.getInstance().getQualityManager()
- self._application.globalContainerStackChanged.connect(self._update)
- self._machine_manager.activeQualityGroupChanged.connect(self._update)
- self._machine_manager.extruderChanged.connect(self._update)
- self._quality_manager.qualitiesUpdated.connect(self._update)
+ self._application.globalContainerStackChanged.connect(self._onChange)
+ self._machine_manager.activeQualityGroupChanged.connect(self._onChange)
+ self._machine_manager.extruderChanged.connect(self._onChange)
+ self._quality_manager.qualitiesUpdated.connect(self._onChange)
self._layer_height_unit = "" # This is cached
- self._update()
+ self._update_timer = QTimer() # type: QTimer
+ self._update_timer.setInterval(100)
+ self._update_timer.setSingleShot(True)
+ self._update_timer.timeout.connect(self._update)
+
+ self._onChange()
+
+ def _onChange(self) -> None:
+ self._update_timer.start()
def _update(self):
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
@@ -74,7 +85,8 @@ class QualityProfilesDropDownMenuModel(ListModel):
"layer_height": layer_height,
"layer_height_unit": self._layer_height_unit,
"available": quality_group.is_available,
- "quality_group": quality_group}
+ "quality_group": quality_group,
+ "is_experimental": quality_group.is_experimental}
item_list.append(item)
@@ -106,4 +118,8 @@ class QualityProfilesDropDownMenuModel(ListModel):
container = global_stack.definition
if container and container.hasProperty("layer_height", "value"):
layer_height = container.getProperty("layer_height", "value")
+
+ if isinstance(layer_height, SettingFunction):
+ layer_height = layer_height(global_stack)
+
return float(layer_height)
diff --git a/cura/Machines/Models/SettingVisibilityPresetsModel.py b/cura/Machines/Models/SettingVisibilityPresetsModel.py
index 3062e83889..baa8e3ed29 100644
--- a/cura/Machines/Models/SettingVisibilityPresetsModel.py
+++ b/cura/Machines/Models/SettingVisibilityPresetsModel.py
@@ -1,135 +1,115 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import Optional
-import os
-import urllib.parse
-from configparser import ConfigParser
+from typing import Optional, List
-from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot
+from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
-from UM.Application import Application
from UM.Logger import Logger
-from UM.Qt.ListModel import ListModel
+from UM.Preferences import Preferences
from UM.Resources import Resources
-from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
from UM.i18n import i18nCatalog
+from cura.Settings.SettingVisibilityPreset import SettingVisibilityPreset
+
catalog = i18nCatalog("cura")
-class SettingVisibilityPresetsModel(ListModel):
- IdRole = Qt.UserRole + 1
- NameRole = Qt.UserRole + 2
- SettingsRole = Qt.UserRole + 3
+class SettingVisibilityPresetsModel(QObject):
+ onItemsChanged = pyqtSignal()
+ activePresetChanged = pyqtSignal()
- def __init__(self, parent = None):
+ def __init__(self, preferences: Preferences, parent = None) -> None:
super().__init__(parent)
- self.addRoleName(self.IdRole, "id")
- self.addRoleName(self.NameRole, "name")
- self.addRoleName(self.SettingsRole, "settings")
+
+ self._items = [] # type: List[SettingVisibilityPreset]
+ self._custom_preset = SettingVisibilityPreset(preset_id = "custom", name = "Custom selection", weight = -100)
self._populate()
- basic_item = self.items[1]
- basic_visibile_settings = ";".join(basic_item["settings"])
- self._preferences = Application.getInstance().getPreferences()
+ basic_item = self.getVisibilityPresetById("basic")
+ if basic_item is not None:
+ basic_visibile_settings = ";".join(basic_item.settings)
+ else:
+ Logger.log("w", "Unable to find the basic visiblity preset.")
+ basic_visibile_settings = ""
+
+ self._preferences = preferences
+
# Preference to store which preset is currently selected
self._preferences.addPreference("cura/active_setting_visibility_preset", "basic")
+
# Preference that stores the "custom" set so it can always be restored (even after a restart)
self._preferences.addPreference("cura/custom_visible_settings", basic_visibile_settings)
self._preferences.preferenceChanged.connect(self._onPreferencesChanged)
- self._active_preset_item = self._getItem(self._preferences.getValue("cura/active_setting_visibility_preset"))
+ self._active_preset_item = self.getVisibilityPresetById(self._preferences.getValue("cura/active_setting_visibility_preset"))
+
# Initialize visible settings if it is not done yet
visible_settings = self._preferences.getValue("general/visible_settings")
+
if not visible_settings:
- self._preferences.setValue("general/visible_settings", ";".join(self._active_preset_item["settings"]))
+ new_visible_settings = self._active_preset_item.settings if self._active_preset_item is not None else []
+ self._preferences.setValue("general/visible_settings", ";".join(new_visible_settings))
else:
self._onPreferencesChanged("general/visible_settings")
self.activePresetChanged.emit()
- def _getItem(self, item_id: str) -> Optional[dict]:
+ def getVisibilityPresetById(self, item_id: str) -> Optional[SettingVisibilityPreset]:
result = None
- for item in self.items:
- if item["id"] == item_id:
+ for item in self._items:
+ if item.presetId == item_id:
result = item
break
return result
- def _populate(self):
+ def _populate(self) -> None:
from cura.CuraApplication import CuraApplication
- items = []
+ items = [] # type: List[SettingVisibilityPreset]
+ items.append(self._custom_preset)
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
+ setting_visibility_preset = SettingVisibilityPreset()
try:
- mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
- except MimeTypeNotFoundError:
- Logger.log("e", "Could not determine mime type of file %s", file_path)
- continue
-
- item_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_path)))
- if not os.path.isfile(file_path):
- Logger.log("e", "[%s] is not a file", file_path)
- continue
-
- parser = ConfigParser(allow_no_value = True) # accept options without any value,
- try:
- parser.read([file_path])
- if not parser.has_option("general", "name") or not parser.has_option("general", "weight"):
- continue
-
- settings = []
- for section in parser.sections():
- if section == 'general':
- continue
-
- settings.append(section)
- for option in parser[section].keys():
- settings.append(option)
-
- items.append({
- "id": item_id,
- "name": catalog.i18nc("@action:inmenu", parser["general"]["name"]),
- "weight": parser["general"]["weight"],
- "settings": settings,
- })
-
+ setting_visibility_preset.loadFromFile(file_path)
except Exception:
Logger.logException("e", "Failed to load setting preset %s", file_path)
- items.sort(key = lambda k: (int(k["weight"]), k["id"]))
- # Put "custom" at the top
- items.insert(0, {"id": "custom",
- "name": "Custom selection",
- "weight": -100,
- "settings": []})
+ items.append(setting_visibility_preset)
+
+ # Sort them on weight (and if that fails, use ID)
+ items.sort(key = lambda k: (int(k.weight), k.presetId))
self.setItems(items)
+ @pyqtProperty("QVariantList", notify = onItemsChanged)
+ def items(self) -> List[SettingVisibilityPreset]:
+ return self._items
+
+ def setItems(self, items: List[SettingVisibilityPreset]) -> None:
+ if self._items != items:
+ self._items = items
+ self.onItemsChanged.emit()
+
@pyqtSlot(str)
- def setActivePreset(self, preset_id: str):
- if preset_id == self._active_preset_item["id"]:
+ def setActivePreset(self, preset_id: str) -> None:
+ if self._active_preset_item is not None and preset_id == self._active_preset_item.presetId:
Logger.log("d", "Same setting visibility preset [%s] selected, do nothing.", preset_id)
return
- preset_item = None
- for item in self.items:
- if item["id"] == preset_id:
- preset_item = item
- break
+ preset_item = self.getVisibilityPresetById(preset_id)
if preset_item is None:
Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id)
return
- need_to_save_to_custom = self._active_preset_item["id"] == "custom" and preset_id != "custom"
+ need_to_save_to_custom = self._active_preset_item is None or (self._active_preset_item.presetId == "custom" and preset_id != "custom")
if need_to_save_to_custom:
# Save the current visibility settings to custom
current_visibility_string = self._preferences.getValue("general/visible_settings")
if current_visibility_string:
self._preferences.setValue("cura/custom_visible_settings", current_visibility_string)
- new_visibility_string = ";".join(preset_item["settings"])
+ new_visibility_string = ";".join(preset_item.settings)
if preset_id == "custom":
# Get settings from the stored custom data
new_visibility_string = self._preferences.getValue("cura/custom_visible_settings")
@@ -141,13 +121,13 @@ class SettingVisibilityPresetsModel(ListModel):
self._active_preset_item = preset_item
self.activePresetChanged.emit()
- activePresetChanged = pyqtSignal()
-
@pyqtProperty(str, notify = activePresetChanged)
def activePreset(self) -> str:
- return self._active_preset_item["id"]
+ if self._active_preset_item is not None:
+ return self._active_preset_item.presetId
+ return ""
- def _onPreferencesChanged(self, name: str):
+ def _onPreferencesChanged(self, name: str) -> None:
if name != "general/visible_settings":
return
@@ -158,25 +138,31 @@ class SettingVisibilityPresetsModel(ListModel):
visibility_set = set(visibility_string.split(";"))
matching_preset_item = None
- for item in self.items:
- if item["id"] == "custom":
+ for item in self._items:
+ if item.presetId == "custom":
continue
- if set(item["settings"]) == visibility_set:
+ if set(item.settings) == visibility_set:
matching_preset_item = item
break
item_to_set = self._active_preset_item
if matching_preset_item is None:
# The new visibility setup is "custom" should be custom
- if self._active_preset_item["id"] == "custom":
+ if self._active_preset_item is None or self._active_preset_item.presetId == "custom":
# We are already in custom, just save the settings
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
else:
- item_to_set = self.items[0] # 0 is custom
+ # We need to move to custom preset.
+ item_to_set = self.getVisibilityPresetById("custom")
else:
item_to_set = matching_preset_item
- if self._active_preset_item is None or self._active_preset_item["id"] != item_to_set["id"]:
+ # If we didn't find a matching preset, fallback to custom.
+ if item_to_set is None:
+ item_to_set = self._custom_preset
+
+ if self._active_preset_item is None or self._active_preset_item.presetId != item_to_set.presetId:
self._active_preset_item = item_to_set
- self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item["id"])
+ if self._active_preset_item is not None:
+ self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item.presetId)
self.activePresetChanged.emit()
diff --git a/cura/Machines/QualityChangesGroup.py b/cura/Machines/QualityChangesGroup.py
index 2d0e655ed8..7844b935dc 100644
--- a/cura/Machines/QualityChangesGroup.py
+++ b/cura/Machines/QualityChangesGroup.py
@@ -17,16 +17,16 @@ class QualityChangesGroup(QualityGroup):
super().__init__(name, quality_type, parent)
self._container_registry = Application.getInstance().getContainerRegistry()
- def addNode(self, node: "QualityNode"):
+ def addNode(self, node: "QualityNode") -> None:
extruder_position = node.getMetaDataEntry("position")
if extruder_position is None and self.node_for_global is not None or extruder_position in self.nodes_for_extruders: #We would be overwriting another node.
ConfigurationErrorMessage.getInstance().addFaultyContainers(node.getMetaDataEntry("id"))
return
- if extruder_position is None: #Then we're a global quality changes profile.
+ if extruder_position is None: # Then we're a global quality changes profile.
self.node_for_global = node
- else: #This is an extruder's quality changes profile.
+ else: # This is an extruder's quality changes profile.
self.nodes_for_extruders[extruder_position] = node
def __str__(self) -> str:
diff --git a/cura/Machines/QualityGroup.py b/cura/Machines/QualityGroup.py
index 90ef63af51..f5bcbb0de8 100644
--- a/cura/Machines/QualityGroup.py
+++ b/cura/Machines/QualityGroup.py
@@ -4,8 +4,12 @@
from typing import Dict, Optional, List, Set
from PyQt5.QtCore import QObject, pyqtSlot
+
+from UM.Util import parseBool
+
from cura.Machines.ContainerNode import ContainerNode
+
#
# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used.
# Some concrete examples are Quality and QualityChanges: when we select quality type "normal", this quality type
@@ -28,13 +32,14 @@ class QualityGroup(QObject):
self.nodes_for_extruders = {} # type: Dict[int, ContainerNode]
self.quality_type = quality_type
self.is_available = False
+ self.is_experimental = False
@pyqtSlot(result = str)
def getName(self) -> str:
return self.name
def getAllKeys(self) -> Set[str]:
- result = set() #type: Set[str]
+ result = set() # type: Set[str]
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
if node is None:
continue
@@ -50,3 +55,17 @@ class QualityGroup(QObject):
for extruder_node in self.nodes_for_extruders.values():
result.append(extruder_node)
return result
+
+ def setGlobalNode(self, node: "ContainerNode") -> None:
+ self.node_for_global = node
+
+ # Update is_experimental flag
+ is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False))
+ self.is_experimental |= is_experimental
+
+ def setExtruderNode(self, position: int, node: "ContainerNode") -> None:
+ self.nodes_for_extruders[position] = node
+
+ # Update is_experimental flag
+ is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False))
+ self.is_experimental |= is_experimental
diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py
index df3bec0827..34cc9ce4b2 100644
--- a/cura/Machines/QualityManager.py
+++ b/cura/Machines/QualityManager.py
@@ -1,11 +1,10 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import TYPE_CHECKING, Optional, cast
+from typing import TYPE_CHECKING, Optional, cast, Dict, List, Set
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
-from UM.Application import Application
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Logger import Logger
from UM.Util import parseBool
@@ -17,9 +16,10 @@ from .QualityGroup import QualityGroup
from .QualityNode import QualityNode
if TYPE_CHECKING:
- from UM.Settings.DefinitionContainer import DefinitionContainer
+ from UM.Settings.Interfaces import DefinitionContainerInterface
from cura.Settings.GlobalStack import GlobalStack
from .QualityChangesGroup import QualityChangesGroup
+ from cura.CuraApplication import CuraApplication
#
@@ -36,17 +36,20 @@ class QualityManager(QObject):
qualitiesUpdated = pyqtSignal()
- def __init__(self, container_registry, parent = None):
+ def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent)
- self._application = Application.getInstance()
+ self._application = application
self._material_manager = self._application.getMaterialManager()
- self._container_registry = container_registry
+ self._container_registry = self._application.getContainerRegistry()
self._empty_quality_container = self._application.empty_quality_container
self._empty_quality_changes_container = self._application.empty_quality_changes_container
- self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # for quality lookup
- self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
+ # For quality lookup
+ self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # type: Dict[str, QualityNode]
+
+ # For quality_changes lookup
+ self._machine_quality_type_to_quality_changes_dict = {} # type: Dict[str, QualityNode]
self._default_machine_definition_id = "fdmprinter"
@@ -62,7 +65,7 @@ class QualityManager(QObject):
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._updateMaps)
- def initialize(self):
+ def initialize(self) -> None:
# Initialize the lookup tree for quality profiles with following structure:
# -> -> ->
# ->
@@ -133,13 +136,13 @@ class QualityManager(QObject):
Logger.log("d", "Lookup tables updated.")
self.qualitiesUpdated.emit()
- def _updateMaps(self):
+ def _updateMaps(self) -> None:
self.initialize()
- def _onContainerMetadataChanged(self, container):
+ def _onContainerMetadataChanged(self, container: InstanceContainer) -> None:
self._onContainerChanged(container)
- def _onContainerChanged(self, container):
+ def _onContainerChanged(self, container: InstanceContainer) -> None:
container_type = container.getMetaDataEntry("type")
if container_type not in ("quality", "quality_changes"):
return
@@ -148,7 +151,7 @@ class QualityManager(QObject):
self._update_timer.start()
# Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
- def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list):
+ def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list) -> None:
used_extruders = set()
for i in range(machine.getProperty("machine_extruder_count", "value")):
if str(i) in machine.extruders and machine.extruders[str(i)].isEnabled:
@@ -196,33 +199,43 @@ class QualityManager(QObject):
# Whether a QualityGroup is available can be unknown via the field QualityGroup.is_available.
# For more details, see QualityGroup.
#
- def getQualityGroups(self, machine: "GlobalStack") -> dict:
+ def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
- has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
+ has_machine_specific_qualities = machine.getHasMachineQuality()
# To find the quality container for the GlobalStack, check in the following fall-back manner:
# (1) the machine-specific node
# (2) the generic node
machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
+ # Check if this machine has specific quality profiles for its extruders, if so, when looking up extruder
+ # qualities, we should not fall back to use the global qualities.
+ has_extruder_specific_qualities = False
+ if machine_node:
+ if machine_node.children_map:
+ has_extruder_specific_qualities = True
+
default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
- nodes_to_check = [machine_node, default_machine_node]
+
+ nodes_to_check = [] # type: List[QualityNode]
+ if machine_node is not None:
+ nodes_to_check.append(machine_node)
+ if default_machine_node is not None:
+ nodes_to_check.append(default_machine_node)
# Iterate over all quality_types in the machine node
quality_group_dict = {}
for node in nodes_to_check:
if node and node.quality_type_map:
- # Only include global qualities
- if has_variant_materials:
- quality_node = list(node.quality_type_map.values())[0]
- is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
- if not is_global_quality:
- continue
+ quality_node = list(node.quality_type_map.values())[0]
+ is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
+ if not is_global_quality:
+ continue
for quality_type, quality_node in node.quality_type_map.items():
- quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
- quality_group.node_for_global = quality_node
+ quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
+ quality_group.setGlobalNode(quality_node)
quality_group_dict[quality_type] = quality_group
break
@@ -246,11 +259,15 @@ class QualityManager(QObject):
root_material_id = self._material_manager.getRootMaterialIDWithoutDiameter(root_material_id)
root_material_id_list.append(root_material_id)
- # Also try to get the fallback material
- material_type = extruder.material.getMetaDataEntry("material")
- fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType(material_type)
- if fallback_root_material_id:
- root_material_id_list.append(fallback_root_material_id)
+ # Also try to get the fallback materials
+ fallback_ids = self._material_manager.getFallBackMaterialIdsByMaterial(extruder.material)
+
+ if fallback_ids:
+ root_material_id_list.extend(fallback_ids)
+
+ # Weed out duplicates while preserving the order.
+ seen = set() # type: Set[str]
+ root_material_id_list = [x for x in root_material_id_list if x not in seen and not seen.add(x)] # type: ignore
# Here we construct a list of nodes we want to look for qualities with the highest priority first.
# The use case is that, when we look for qualities for a machine, we first want to search in the following
@@ -259,18 +276,25 @@ class QualityManager(QObject):
# 2. machine-nozzle-and-material-specific qualities if exist
# 3. machine-nozzle-specific qualities if exist
# 4. machine-material-specific qualities if exist
- # 5. machine-specific qualities if exist
- # 6. generic qualities if exist
+ # 5. machine-specific global qualities if exist, otherwise generic global qualities
+ # NOTE: We DO NOT fail back to generic global qualities if machine-specific global qualities exist.
+ # This is because when a machine defines its own global qualities such as Normal, Fine, etc.,
+ # it is intended to maintain those specific qualities ONLY. If we still fail back to the generic
+ # global qualities, there can be unimplemented quality types e.g. "coarse", and this is not
+ # correct.
# Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
# the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
# qualities from there.
- node_info_list_0 = [nozzle_name, buildplate_name, root_material_id]
+ node_info_list_0 = [nozzle_name, buildplate_name, root_material_id] # type: List[Optional[str]]
nodes_to_check = []
# This function tries to recursively find the deepest (the most specific) branch and add those nodes to
# the search list in the order described above. So, by iterating over that search node list, we first look
# in the more specific branches and then the less specific (generic) ones.
- def addNodesToCheck(node, nodes_to_check_list, node_info_list, node_info_idx):
+ def addNodesToCheck(node: Optional[QualityNode], nodes_to_check_list: List[QualityNode], node_info_list, node_info_idx: int) -> None:
+ if node is None:
+ return
+
if node_info_idx < len(node_info_list):
node_name = node_info_list[node_info_idx]
if node_name is not None:
@@ -289,31 +313,44 @@ class QualityManager(QObject):
addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0)
- nodes_to_check += [machine_node, default_machine_node]
- for node in nodes_to_check:
+ # The last fall back will be the global qualities (either from the machine-specific node or the generic
+ # node), but we only use one. For details see the overview comments above.
+
+ if machine_node is not None and machine_node.quality_type_map:
+ nodes_to_check += [machine_node]
+ elif default_machine_node is not None:
+ nodes_to_check += [default_machine_node]
+
+ for node_idx, node in enumerate(nodes_to_check):
if node and node.quality_type_map:
- if has_variant_materials:
+ if has_extruder_specific_qualities:
# Only include variant qualities; skip non global qualities
quality_node = list(node.quality_type_map.values())[0]
- is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
+ is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
if is_global_quality:
continue
for quality_type, quality_node in node.quality_type_map.items():
if quality_type not in quality_group_dict:
- quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
+ quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
quality_group_dict[quality_type] = quality_group
quality_group = quality_group_dict[quality_type]
if position not in quality_group.nodes_for_extruders:
- quality_group.nodes_for_extruders[position] = quality_node
+ quality_group.setExtruderNode(position, quality_node)
+
+ # If the machine has its own specific qualities, for extruders, it should skip the global qualities
+ # and use the material/variant specific qualities.
+ if has_extruder_specific_qualities:
+ if node_idx == len(nodes_to_check) - 1:
+ break
# Update availabilities for each quality group
self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
return quality_group_dict
- def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
+ def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
# To find the quality container for the GlobalStack, check in the following fall-back manner:
@@ -329,8 +366,8 @@ class QualityManager(QObject):
for node in nodes_to_check:
if node and node.quality_type_map:
for quality_type, quality_node in node.quality_type_map.items():
- quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
- quality_group.node_for_global = quality_node
+ quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
+ quality_group.setGlobalNode(quality_node)
quality_group_dict[quality_type] = quality_group
break
@@ -351,10 +388,21 @@ class QualityManager(QObject):
# Remove the given quality changes group.
#
@pyqtSlot(QObject)
- def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
+ def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
+ removed_quality_changes_ids = set()
for node in quality_changes_group.getAllNodes():
- self._container_registry.removeContainer(node.getMetaDataEntry("id"))
+ container_id = node.getMetaDataEntry("id")
+ self._container_registry.removeContainer(container_id)
+ removed_quality_changes_ids.add(container_id)
+
+ # Reset all machines that have activated this quality changes to empty.
+ for global_stack in self._container_registry.findContainerStacks(type = "machine"):
+ if global_stack.qualityChanges.getId() in removed_quality_changes_ids:
+ global_stack.qualityChanges = self._empty_quality_changes_container
+ for extruder_stack in self._container_registry.findContainerStacks(type = "extruder_train"):
+ if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
+ extruder_stack.qualityChanges = self._empty_quality_changes_container
#
# Rename a set of quality changes containers. Returns the new name.
@@ -383,7 +431,7 @@ class QualityManager(QObject):
# Duplicates the given quality.
#
@pyqtSlot(str, "QVariantMap")
- def duplicateQualityChanges(self, quality_changes_name, quality_model_item):
+ def duplicateQualityChanges(self, quality_changes_name: str, quality_model_item) -> None:
global_stack = self._application.getGlobalContainerStack()
if not global_stack:
Logger.log("i", "No active global stack, cannot duplicate quality changes.")
@@ -411,8 +459,8 @@ class QualityManager(QObject):
# the user containers in each stack. These then replace the quality_changes containers in the
# stack and clear the user settings.
@pyqtSlot(str)
- def createQualityChanges(self, base_name):
- machine_manager = Application.getInstance().getMachineManager()
+ def createQualityChanges(self, base_name: str) -> None:
+ machine_manager = self._application.getMachineManager()
global_stack = machine_manager.activeMachine
if not global_stack:
@@ -490,7 +538,7 @@ class QualityManager(QObject):
# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended
# shares the same set of qualities profiles as Ultimaker 3.
#
-def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainer",
+def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainerInterface",
default_definition_id: str = "fdmprinter") -> str:
machine_definition_id = default_definition_id
if parseBool(machine_definition.getMetaDataEntry("has_machine_quality", False)):
diff --git a/cura/Machines/QualityNode.py b/cura/Machines/QualityNode.py
index f384ee7825..991388a4bd 100644
--- a/cura/Machines/QualityNode.py
+++ b/cura/Machines/QualityNode.py
@@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import Optional, Dict, cast
+from typing import Optional, Dict, cast, Any
from .ContainerNode import ContainerNode
from .QualityChangesGroup import QualityChangesGroup
@@ -12,18 +12,21 @@ from .QualityChangesGroup import QualityChangesGroup
#
class QualityNode(ContainerNode):
- def __init__(self, metadata: Optional[dict] = None) -> None:
+ def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
super().__init__(metadata = metadata)
self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer
- def addQualityMetadata(self, quality_type: str, metadata: dict):
+ def getChildNode(self, child_key: str) -> Optional["QualityNode"]:
+ return self.children_map.get(child_key)
+
+ def addQualityMetadata(self, quality_type: str, metadata: Dict[str, Any]):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode(metadata)
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
return self.quality_type_map.get(quality_type)
- def addQualityChangesMetadata(self, quality_type: str, metadata: dict):
+ def addQualityChangesMetadata(self, quality_type: str, metadata: Dict[str, Any]):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode()
quality_type_node = self.quality_type_map[quality_type]
diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py
index 969fed670e..eaaa9fc5f0 100644
--- a/cura/Machines/VariantManager.py
+++ b/cura/Machines/VariantManager.py
@@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from collections import OrderedDict
-from typing import Optional, TYPE_CHECKING
+from typing import Optional, TYPE_CHECKING, Dict
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Logger import Logger
@@ -36,11 +36,11 @@ if TYPE_CHECKING:
#
class VariantManager:
- def __init__(self, container_registry):
- self._container_registry = container_registry # type: ContainerRegistry
+ def __init__(self, container_registry: ContainerRegistry) -> None:
+ self._container_registry = container_registry
- self._machine_to_variant_dict_map = dict() # ->
- self._machine_to_buildplate_dict_map = dict()
+ self._machine_to_variant_dict_map = dict() # type: Dict[str, Dict["VariantType", Dict[str, ContainerNode]]]
+ self._machine_to_buildplate_dict_map = dict() # type: Dict[str, Dict[str, ContainerNode]]
self._exclude_variant_id_list = ["empty_variant"]
@@ -48,7 +48,7 @@ class VariantManager:
# Initializes the VariantManager including:
# - initializing the variant lookup table based on the metadata in ContainerRegistry.
#
- def initialize(self):
+ def initialize(self) -> None:
self._machine_to_variant_dict_map = OrderedDict()
self._machine_to_buildplate_dict_map = OrderedDict()
@@ -106,26 +106,33 @@ class VariantManager:
variant_node = variant_dict[variant_name]
break
return variant_node
- return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
- def getVariantNodes(self, machine: "GlobalStack",
- variant_type: Optional["VariantType"] = None) -> dict:
+ return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {}).get(variant_name)
+
+ def getVariantNodes(self, machine: "GlobalStack", variant_type: "VariantType") -> Dict[str, ContainerNode]:
machine_definition_id = machine.definition.getId()
return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
#
# Gets the default variant for the given machine definition.
+ # If the optional GlobalStack is given, the metadata information will be fetched from the GlobalStack instead of
+ # the DefinitionContainer. Because for machines such as UM2, you can enable Olsson Block, which will set
+ # "has_variants" to True in the GlobalStack. In those cases, we need to fetch metadata from the GlobalStack or
+ # it may not be correct.
#
def getDefaultVariantNode(self, machine_definition: "DefinitionContainer",
- variant_type: VariantType) -> Optional["ContainerNode"]:
+ variant_type: "VariantType",
+ global_stack: Optional["GlobalStack"] = None) -> Optional["ContainerNode"]:
machine_definition_id = machine_definition.getId()
+ container_for_metadata_fetching = global_stack if global_stack is not None else machine_definition
+
preferred_variant_name = None
if variant_type == VariantType.BUILD_PLATE:
- if parseBool(machine_definition.getMetaDataEntry("has_variant_buildplates", False)):
- preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_buildplate_name")
+ if parseBool(container_for_metadata_fetching.getMetaDataEntry("has_variant_buildplates", False)):
+ preferred_variant_name = container_for_metadata_fetching.getMetaDataEntry("preferred_variant_buildplate_name")
else:
- if parseBool(machine_definition.getMetaDataEntry("has_variants", False)):
- preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_name")
+ if parseBool(container_for_metadata_fetching.getMetaDataEntry("has_variants", False)):
+ preferred_variant_name = container_for_metadata_fetching.getMetaDataEntry("preferred_variant_name")
node = None
if preferred_variant_name:
diff --git a/cura/MultiplyObjectsJob.py b/cura/MultiplyObjectsJob.py
index 3cbf795952..e71bbf6668 100644
--- a/cura/MultiplyObjectsJob.py
+++ b/cura/MultiplyObjectsJob.py
@@ -25,7 +25,7 @@ class MultiplyObjectsJob(Job):
def run(self):
status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime=0,
- dismissable=False, progress=0, title = i18n_catalog.i18nc("@info:title", "Placing Object"))
+ dismissable=False, progress=0, title = i18n_catalog.i18nc("@info:title", "Placing Objects"))
status_message.show()
scene = Application.getInstance().getController().getScene()
diff --git a/cura/OAuth2/AuthorizationHelpers.py b/cura/OAuth2/AuthorizationHelpers.py
new file mode 100644
index 0000000000..8224e7b51f
--- /dev/null
+++ b/cura/OAuth2/AuthorizationHelpers.py
@@ -0,0 +1,122 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from datetime import datetime
+import json
+import random
+from hashlib import sha512
+from base64 import b64encode
+from typing import Optional
+
+import requests
+
+from UM.i18n import i18nCatalog
+from UM.Logger import Logger
+
+from cura.OAuth2.Models import AuthenticationResponse, UserProfile, OAuth2Settings
+catalog = i18nCatalog("cura")
+TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+## Class containing several helpers to deal with the authorization flow.
+class AuthorizationHelpers:
+ def __init__(self, settings: "OAuth2Settings") -> None:
+ self._settings = settings
+ self._token_url = "{}/token".format(self._settings.OAUTH_SERVER_URL)
+
+ @property
+ ## The OAuth2 settings object.
+ def settings(self) -> "OAuth2Settings":
+ return self._settings
+
+ ## Request the access token from the authorization server.
+ # \param authorization_code: The authorization code from the 1st step.
+ # \param verification_code: The verification code needed for the PKCE
+ # extension.
+ # \return An AuthenticationResponse object.
+ def getAccessTokenUsingAuthorizationCode(self, authorization_code: str, verification_code: str) -> "AuthenticationResponse":
+ data = {
+ "client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
+ "redirect_uri": self._settings.CALLBACK_URL if self._settings.CALLBACK_URL is not None else "",
+ "grant_type": "authorization_code",
+ "code": authorization_code,
+ "code_verifier": verification_code,
+ "scope": self._settings.CLIENT_SCOPES if self._settings.CLIENT_SCOPES is not None else "",
+ }
+ return self.parseTokenResponse(requests.post(self._token_url, data = data)) # type: ignore
+
+ ## Request the access token from the authorization server using a refresh token.
+ # \param refresh_token:
+ # \return An AuthenticationResponse object.
+ def getAccessTokenUsingRefreshToken(self, refresh_token: str) -> "AuthenticationResponse":
+ data = {
+ "client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
+ "redirect_uri": self._settings.CALLBACK_URL if self._settings.CALLBACK_URL is not None else "",
+ "grant_type": "refresh_token",
+ "refresh_token": refresh_token,
+ "scope": self._settings.CLIENT_SCOPES if self._settings.CLIENT_SCOPES is not None else "",
+ }
+ return self.parseTokenResponse(requests.post(self._token_url, data = data)) # type: ignore
+
+ @staticmethod
+ ## Parse the token response from the authorization server into an AuthenticationResponse object.
+ # \param token_response: The JSON string data response from the authorization server.
+ # \return An AuthenticationResponse object.
+ def parseTokenResponse(token_response: requests.models.Response) -> "AuthenticationResponse":
+ token_data = None
+
+ try:
+ token_data = json.loads(token_response.text)
+ except ValueError:
+ Logger.log("w", "Could not parse token response data: %s", token_response.text)
+
+ if not token_data:
+ return AuthenticationResponse(success = False, err_message = catalog.i18nc("@message", "Could not read response."))
+
+ if token_response.status_code not in (200, 201):
+ return AuthenticationResponse(success = False, err_message = token_data["error_description"])
+
+ return AuthenticationResponse(success=True,
+ token_type=token_data["token_type"],
+ access_token=token_data["access_token"],
+ refresh_token=token_data["refresh_token"],
+ expires_in=token_data["expires_in"],
+ scope=token_data["scope"],
+ received_at=datetime.now().strftime(TOKEN_TIMESTAMP_FORMAT))
+
+ ## Calls the authentication API endpoint to get the token data.
+ # \param access_token: The encoded JWT token.
+ # \return Dict containing some profile data.
+ def parseJWT(self, access_token: str) -> Optional["UserProfile"]:
+ try:
+ token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
+ "Authorization": "Bearer {}".format(access_token)
+ })
+ except requests.exceptions.ConnectionError:
+ # Connection was suddenly dropped. Nothing we can do about that.
+ Logger.log("w", "Something failed while attempting to parse the JWT token")
+ return None
+ if token_request.status_code not in (200, 201):
+ Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text)
+ return None
+ user_data = token_request.json().get("data")
+ if not user_data or not isinstance(user_data, dict):
+ Logger.log("w", "Could not parse user data from token: %s", user_data)
+ return None
+ return UserProfile(
+ user_id = user_data["user_id"],
+ username = user_data["username"],
+ profile_image_url = user_data.get("profile_image_url", "")
+ )
+
+ @staticmethod
+ ## Generate a 16-character verification code.
+ # \param code_length: How long should the code be?
+ def generateVerificationCode(code_length: int = 16) -> str:
+ return "".join(random.choice("0123456789ABCDEF") for i in range(code_length))
+
+ @staticmethod
+ ## Generates a base64 encoded sha512 encrypted version of a given string.
+ # \param verification_code:
+ # \return The encrypted code in base64 format.
+ def generateVerificationCodeChallenge(verification_code: str) -> str:
+ encoded = sha512(verification_code.encode()).digest()
+ return b64encode(encoded, altchars = b"_-").decode()
diff --git a/cura/OAuth2/AuthorizationRequestHandler.py b/cura/OAuth2/AuthorizationRequestHandler.py
new file mode 100644
index 0000000000..66ecfc2787
--- /dev/null
+++ b/cura/OAuth2/AuthorizationRequestHandler.py
@@ -0,0 +1,103 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from http.server import BaseHTTPRequestHandler
+from typing import Optional, Callable, Tuple, Dict, Any, List, TYPE_CHECKING
+from urllib.parse import parse_qs, urlparse
+
+from cura.OAuth2.Models import AuthenticationResponse, ResponseData, HTTP_STATUS
+from UM.i18n import i18nCatalog
+
+if TYPE_CHECKING:
+ from cura.OAuth2.Models import ResponseStatus
+ from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
+
+catalog = i18nCatalog("cura")
+
+## This handler handles all HTTP requests on the local web server.
+# It also requests the access token for the 2nd stage of the OAuth flow.
+class AuthorizationRequestHandler(BaseHTTPRequestHandler):
+ def __init__(self, request, client_address, server) -> None:
+ super().__init__(request, client_address, server)
+
+ # These values will be injected by the HTTPServer that this handler belongs to.
+ self.authorization_helpers = None # type: Optional[AuthorizationHelpers]
+ self.authorization_callback = None # type: Optional[Callable[[AuthenticationResponse], None]]
+ self.verification_code = None # type: Optional[str]
+
+ def do_GET(self) -> None:
+ # Extract values from the query string.
+ parsed_url = urlparse(self.path)
+ query = parse_qs(parsed_url.query)
+
+ # Handle the possible requests
+ if parsed_url.path == "/callback":
+ server_response, token_response = self._handleCallback(query)
+ else:
+ server_response = self._handleNotFound()
+ token_response = None
+
+ # Send the data to the browser.
+ self._sendHeaders(server_response.status, server_response.content_type, server_response.redirect_uri)
+
+ if server_response.data_stream:
+ # If there is data in the response, we send it.
+ self._sendData(server_response.data_stream)
+
+ if token_response and self.authorization_callback is not None:
+ # Trigger the callback if we got a response.
+ # This will cause the server to shut down, so we do it at the very end of the request handling.
+ self.authorization_callback(token_response)
+
+ ## Handler for the callback URL redirect.
+ # \param query Dict containing the HTTP query parameters.
+ # \return HTTP ResponseData containing a success page to show to the user.
+ def _handleCallback(self, query: Dict[Any, List]) -> Tuple[ResponseData, Optional[AuthenticationResponse]]:
+ code = self._queryGet(query, "code")
+ if code and self.authorization_helpers is not None and self.verification_code is not None:
+ # If the code was returned we get the access token.
+ token_response = self.authorization_helpers.getAccessTokenUsingAuthorizationCode(
+ code, self.verification_code)
+
+ elif self._queryGet(query, "error_code") == "user_denied":
+ # Otherwise we show an error message (probably the user clicked "Deny" in the auth dialog).
+ token_response = AuthenticationResponse(
+ success = False,
+ err_message = catalog.i18nc("@message", "Please give the required permissions when authorizing this application.")
+ )
+
+ else:
+ # We don't know what went wrong here, so instruct the user to check the logs.
+ token_response = AuthenticationResponse(
+ success = False,
+ error_message = catalog.i18nc("@message", "Something unexpected happened when trying to log in, please try again.")
+ )
+ if self.authorization_helpers is None:
+ return ResponseData(), token_response
+
+ return ResponseData(
+ status = HTTP_STATUS["REDIRECT"],
+ data_stream = b"Redirecting...",
+ redirect_uri = self.authorization_helpers.settings.AUTH_SUCCESS_REDIRECT if token_response.success else
+ self.authorization_helpers.settings.AUTH_FAILED_REDIRECT
+ ), token_response
+
+ ## Handle all other non-existing server calls.
+ @staticmethod
+ def _handleNotFound() -> ResponseData:
+ return ResponseData(status = HTTP_STATUS["NOT_FOUND"], content_type = "text/html", data_stream = b"Not found.")
+
+ def _sendHeaders(self, status: "ResponseStatus", content_type: str, redirect_uri: str = None) -> None:
+ self.send_response(status.code, status.message)
+ self.send_header("Content-type", content_type)
+ if redirect_uri:
+ self.send_header("Location", redirect_uri)
+ self.end_headers()
+
+ def _sendData(self, data: bytes) -> None:
+ self.wfile.write(data)
+
+ ## Convenience helper for getting values from a pre-parsed query string
+ @staticmethod
+ def _queryGet(query_data: Dict[Any, List], key: str, default: Optional[str] = None) -> Optional[str]:
+ return query_data.get(key, [default])[0]
diff --git a/cura/OAuth2/AuthorizationRequestServer.py b/cura/OAuth2/AuthorizationRequestServer.py
new file mode 100644
index 0000000000..51a8ceba77
--- /dev/null
+++ b/cura/OAuth2/AuthorizationRequestServer.py
@@ -0,0 +1,27 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from http.server import HTTPServer
+from typing import Callable, Any, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from cura.OAuth2.Models import AuthenticationResponse
+ from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
+
+
+## The authorization request callback handler server.
+# This subclass is needed to be able to pass some data to the request handler.
+# This cannot be done on the request handler directly as the HTTPServer
+# creates an instance of the handler after init.
+class AuthorizationRequestServer(HTTPServer):
+ ## Set the authorization helpers instance on the request handler.
+ def setAuthorizationHelpers(self, authorization_helpers: "AuthorizationHelpers") -> None:
+ self.RequestHandlerClass.authorization_helpers = authorization_helpers # type: ignore
+
+ ## Set the authorization callback on the request handler.
+ def setAuthorizationCallback(self, authorization_callback: Callable[["AuthenticationResponse"], Any]) -> None:
+ self.RequestHandlerClass.authorization_callback = authorization_callback # type: ignore
+
+ ## Set the verification code on the request handler.
+ def setVerificationCode(self, verification_code: str) -> None:
+ self.RequestHandlerClass.verification_code = verification_code # type: ignore
diff --git a/cura/OAuth2/AuthorizationService.py b/cura/OAuth2/AuthorizationService.py
new file mode 100644
index 0000000000..dd20ea1d96
--- /dev/null
+++ b/cura/OAuth2/AuthorizationService.py
@@ -0,0 +1,207 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import json
+import webbrowser
+from datetime import datetime, timedelta
+from typing import Optional, TYPE_CHECKING
+from urllib.parse import urlencode
+import requests.exceptions
+
+
+from UM.Logger import Logger
+from UM.Message import Message
+from UM.Signal import Signal
+
+from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer
+from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers, TOKEN_TIMESTAMP_FORMAT
+from cura.OAuth2.Models import AuthenticationResponse
+
+from UM.i18n import i18nCatalog
+i18n_catalog = i18nCatalog("cura")
+
+if TYPE_CHECKING:
+ from cura.OAuth2.Models import UserProfile, OAuth2Settings
+ from UM.Preferences import Preferences
+
+
+## The authorization service is responsible for handling the login flow,
+# storing user credentials and providing account information.
+class AuthorizationService:
+ # Emit signal when authentication is completed.
+ onAuthStateChanged = Signal()
+
+ # Emit signal when authentication failed.
+ onAuthenticationError = Signal()
+
+ def __init__(self, settings: "OAuth2Settings", preferences: Optional["Preferences"] = None) -> None:
+ self._settings = settings
+ self._auth_helpers = AuthorizationHelpers(settings)
+ self._auth_url = "{}/authorize".format(self._settings.OAUTH_SERVER_URL)
+ self._auth_data = None # type: Optional[AuthenticationResponse]
+ self._user_profile = None # type: Optional["UserProfile"]
+ self._preferences = preferences
+ self._server = LocalAuthorizationServer(self._auth_helpers, self._onAuthStateChanged, daemon=True)
+
+ self._unable_to_get_data_message = None # type: Optional[Message]
+
+ self.onAuthStateChanged.connect(self._authChanged)
+
+ def _authChanged(self, logged_in):
+ if logged_in and self._unable_to_get_data_message is not None:
+ self._unable_to_get_data_message.hide()
+
+ def initialize(self, preferences: Optional["Preferences"] = None) -> None:
+ if preferences is not None:
+ self._preferences = preferences
+ if self._preferences:
+ self._preferences.addPreference(self._settings.AUTH_DATA_PREFERENCE_KEY, "{}")
+
+ ## Get the user profile as obtained from the JWT (JSON Web Token).
+ # If the JWT is not yet parsed, calling this will take care of that.
+ # \return UserProfile if a user is logged in, None otherwise.
+ # \sa _parseJWT
+ def getUserProfile(self) -> Optional["UserProfile"]:
+ if not self._user_profile:
+ # If no user profile was stored locally, we try to get it from JWT.
+ try:
+ self._user_profile = self._parseJWT()
+ except requests.exceptions.ConnectionError:
+ # Unable to get connection, can't login.
+ return None
+
+ if not self._user_profile and self._auth_data:
+ # If there is still no user profile from the JWT, we have to log in again.
+ Logger.log("w", "The user profile could not be loaded. The user must log in again!")
+ self.deleteAuthData()
+ return None
+
+ return self._user_profile
+
+ ## Tries to parse the JWT (JSON Web Token) data, which it does if all the needed data is there.
+ # \return UserProfile if it was able to parse, None otherwise.
+ def _parseJWT(self) -> Optional["UserProfile"]:
+ if not self._auth_data or self._auth_data.access_token is None:
+ # If no auth data exists, we should always log in again.
+ return None
+ user_data = self._auth_helpers.parseJWT(self._auth_data.access_token)
+ if user_data:
+ # If the profile was found, we return it immediately.
+ return user_data
+ # The JWT was expired or invalid and we should request a new one.
+ if self._auth_data.refresh_token is None:
+ return None
+ self._auth_data = self._auth_helpers.getAccessTokenUsingRefreshToken(self._auth_data.refresh_token)
+ if not self._auth_data or self._auth_data.access_token is None:
+ # The token could not be refreshed using the refresh token. We should login again.
+ return None
+
+ return self._auth_helpers.parseJWT(self._auth_data.access_token)
+
+ ## Get the access token as provided by the repsonse data.
+ def getAccessToken(self) -> Optional[str]:
+ if self._auth_data is None:
+ Logger.log("d", "No auth data to retrieve the access_token from")
+ return None
+
+ # Check if the current access token is expired and refresh it if that is the case.
+ # We have a fallback on a date far in the past for currently stored auth data in cura.cfg.
+ received_at = datetime.strptime(self._auth_data.received_at, TOKEN_TIMESTAMP_FORMAT) \
+ if self._auth_data.received_at else datetime(2000, 1, 1)
+ expiry_date = received_at + timedelta(seconds = float(self._auth_data.expires_in or 0))
+ if datetime.now() > expiry_date:
+ self.refreshAccessToken()
+
+ return self._auth_data.access_token if self._auth_data else None
+
+ ## Try to refresh the access token. This should be used when it has expired.
+ def refreshAccessToken(self) -> None:
+ if self._auth_data is None or self._auth_data.refresh_token is None:
+ Logger.log("w", "Unable to refresh access token, since there is no refresh token.")
+ return
+ self._storeAuthData(self._auth_helpers.getAccessTokenUsingRefreshToken(self._auth_data.refresh_token))
+ self.onAuthStateChanged.emit(logged_in = True)
+
+ ## Delete the authentication data that we have stored locally (eg; logout)
+ def deleteAuthData(self) -> None:
+ if self._auth_data is not None:
+ self._storeAuthData()
+ self.onAuthStateChanged.emit(logged_in = False)
+
+ ## Start the flow to become authenticated. This will start a new webbrowser tap, prompting the user to login.
+ def startAuthorizationFlow(self) -> None:
+ Logger.log("d", "Starting new OAuth2 flow...")
+
+ # Create the tokens needed for the code challenge (PKCE) extension for OAuth2.
+ # This is needed because the CuraDrivePlugin is a untrusted (open source) client.
+ # More details can be found at https://tools.ietf.org/html/rfc7636.
+ verification_code = self._auth_helpers.generateVerificationCode()
+ challenge_code = self._auth_helpers.generateVerificationCodeChallenge(verification_code)
+
+ # Create the query string needed for the OAuth2 flow.
+ query_string = urlencode({
+ "client_id": self._settings.CLIENT_ID,
+ "redirect_uri": self._settings.CALLBACK_URL,
+ "scope": self._settings.CLIENT_SCOPES,
+ "response_type": "code",
+ "state": "(.Y.)",
+ "code_challenge": challenge_code,
+ "code_challenge_method": "S512"
+ })
+
+ # Open the authorization page in a new browser window.
+ webbrowser.open_new("{}?{}".format(self._auth_url, query_string))
+
+ # Start a local web server to receive the callback URL on.
+ self._server.start(verification_code)
+
+ ## Callback method for the authentication flow.
+ def _onAuthStateChanged(self, auth_response: AuthenticationResponse) -> None:
+ if auth_response.success:
+ self._storeAuthData(auth_response)
+ self.onAuthStateChanged.emit(logged_in = True)
+ else:
+ self.onAuthenticationError.emit(logged_in = False, error_message = auth_response.err_message)
+ self._server.stop() # Stop the web server at all times.
+
+ ## Load authentication data from preferences.
+ def loadAuthDataFromPreferences(self) -> None:
+ if self._preferences is None:
+ Logger.log("e", "Unable to load authentication data, since no preference has been set!")
+ return
+ try:
+ preferences_data = json.loads(self._preferences.getValue(self._settings.AUTH_DATA_PREFERENCE_KEY))
+ if preferences_data:
+ self._auth_data = AuthenticationResponse(**preferences_data)
+ # Also check if we can actually get the user profile information.
+ user_profile = self.getUserProfile()
+ if user_profile is not None:
+ self.onAuthStateChanged.emit(logged_in = True)
+ else:
+ if self._unable_to_get_data_message is not None:
+ self._unable_to_get_data_message.hide()
+
+ self._unable_to_get_data_message = Message(i18n_catalog.i18nc("@info", "Unable to reach the Ultimaker account server."), title = i18n_catalog.i18nc("@info:title", "Warning"))
+ self._unable_to_get_data_message.addAction("retry", i18n_catalog.i18nc("@action:button", "Retry"), "[no_icon]", "[no_description]")
+ self._unable_to_get_data_message.actionTriggered.connect(self._onMessageActionTriggered)
+ self._unable_to_get_data_message.show()
+ except ValueError:
+ Logger.logException("w", "Could not load auth data from preferences")
+
+ ## Store authentication data in preferences.
+ def _storeAuthData(self, auth_data: Optional[AuthenticationResponse] = None) -> None:
+ if self._preferences is None:
+ Logger.log("e", "Unable to save authentication data, since no preference has been set!")
+ return
+
+ self._auth_data = auth_data
+ if auth_data:
+ self._user_profile = self.getUserProfile()
+ self._preferences.setValue(self._settings.AUTH_DATA_PREFERENCE_KEY, json.dumps(vars(auth_data)))
+ else:
+ self._user_profile = None
+ self._preferences.resetPreference(self._settings.AUTH_DATA_PREFERENCE_KEY)
+
+ def _onMessageActionTriggered(self, _, action):
+ if action == "retry":
+ self.loadAuthDataFromPreferences()
diff --git a/cura/OAuth2/LocalAuthorizationServer.py b/cura/OAuth2/LocalAuthorizationServer.py
new file mode 100644
index 0000000000..25b2435012
--- /dev/null
+++ b/cura/OAuth2/LocalAuthorizationServer.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import threading
+from typing import Optional, Callable, Any, TYPE_CHECKING
+
+from UM.Logger import Logger
+
+from cura.OAuth2.AuthorizationRequestServer import AuthorizationRequestServer
+from cura.OAuth2.AuthorizationRequestHandler import AuthorizationRequestHandler
+
+if TYPE_CHECKING:
+ from cura.OAuth2.Models import AuthenticationResponse
+ from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
+
+
+class LocalAuthorizationServer:
+ ## The local LocalAuthorizationServer takes care of the oauth2 callbacks.
+ # Once the flow is completed, this server should be closed down again by
+ # calling stop()
+ # \param auth_helpers An instance of the authorization helpers class.
+ # \param auth_state_changed_callback A callback function to be called when
+ # the authorization state changes.
+ # \param daemon Whether the server thread should be run in daemon mode.
+ # Note: Daemon threads are abruptly stopped at shutdown. Their resources
+ # (e.g. open files) may never be released.
+ def __init__(self, auth_helpers: "AuthorizationHelpers",
+ auth_state_changed_callback: Callable[["AuthenticationResponse"], Any],
+ daemon: bool) -> None:
+ self._web_server = None # type: Optional[AuthorizationRequestServer]
+ self._web_server_thread = None # type: Optional[threading.Thread]
+ self._web_server_port = auth_helpers.settings.CALLBACK_PORT
+ self._auth_helpers = auth_helpers
+ self._auth_state_changed_callback = auth_state_changed_callback
+ self._daemon = daemon
+
+ ## Starts the local web server to handle the authorization callback.
+ # \param verification_code The verification code part of the OAuth2 client identification.
+ def start(self, verification_code: str) -> None:
+ if self._web_server:
+ # If the server is already running (because of a previously aborted auth flow), we don't have to start it.
+ # We still inject the new verification code though.
+ self._web_server.setVerificationCode(verification_code)
+ return
+
+ if self._web_server_port is None:
+ raise Exception("Unable to start server without specifying the port.")
+
+ Logger.log("d", "Starting local web server to handle authorization callback on port %s", self._web_server_port)
+
+ # Create the server and inject the callback and code.
+ self._web_server = AuthorizationRequestServer(("0.0.0.0", self._web_server_port), AuthorizationRequestHandler)
+ self._web_server.setAuthorizationHelpers(self._auth_helpers)
+ self._web_server.setAuthorizationCallback(self._auth_state_changed_callback)
+ self._web_server.setVerificationCode(verification_code)
+
+ # Start the server on a new thread.
+ self._web_server_thread = threading.Thread(None, self._web_server.serve_forever, daemon = self._daemon)
+ self._web_server_thread.start()
+
+ ## Stops the web server if it was running. It also does some cleanup.
+ def stop(self) -> None:
+ Logger.log("d", "Stopping local oauth2 web server...")
+
+ if self._web_server:
+ self._web_server.server_close()
+ self._web_server = None
+ self._web_server_thread = None
diff --git a/cura/OAuth2/Models.py b/cura/OAuth2/Models.py
new file mode 100644
index 0000000000..468351c62b
--- /dev/null
+++ b/cura/OAuth2/Models.py
@@ -0,0 +1,61 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional
+
+
+class BaseModel:
+ def __init__(self, **kwargs):
+ self.__dict__.update(kwargs)
+
+
+## OAuth OAuth2Settings data template.
+class OAuth2Settings(BaseModel):
+ CALLBACK_PORT = None # type: Optional[int]
+ OAUTH_SERVER_URL = None # type: Optional[str]
+ CLIENT_ID = None # type: Optional[str]
+ CLIENT_SCOPES = None # type: Optional[str]
+ CALLBACK_URL = None # type: Optional[str]
+ AUTH_DATA_PREFERENCE_KEY = "" # type: str
+ AUTH_SUCCESS_REDIRECT = "https://ultimaker.com" # type: str
+ AUTH_FAILED_REDIRECT = "https://ultimaker.com" # type: str
+
+
+## User profile data template.
+class UserProfile(BaseModel):
+ user_id = None # type: Optional[str]
+ username = None # type: Optional[str]
+ profile_image_url = None # type: Optional[str]
+
+
+## Authentication data template.
+class AuthenticationResponse(BaseModel):
+ """Data comes from the token response with success flag and error message added."""
+ success = True # type: bool
+ token_type = None # type: Optional[str]
+ access_token = None # type: Optional[str]
+ refresh_token = None # type: Optional[str]
+ expires_in = None # type: Optional[str]
+ scope = None # type: Optional[str]
+ err_message = None # type: Optional[str]
+ received_at = None # type: Optional[str]
+
+
+## Response status template.
+class ResponseStatus(BaseModel):
+ code = 200 # type: int
+ message = "" # type: str
+
+
+## Response data template.
+class ResponseData(BaseModel):
+ status = None # type: ResponseStatus
+ data_stream = None # type: Optional[bytes]
+ redirect_uri = None # type: Optional[str]
+ content_type = "text/html" # type: str
+
+## Possible HTTP responses.
+HTTP_STATUS = {
+ "OK": ResponseStatus(code = 200, message = "OK"),
+ "NOT_FOUND": ResponseStatus(code = 404, message = "NOT FOUND"),
+ "REDIRECT": ResponseStatus(code = 302, message = "REDIRECT")
+}
diff --git a/cura/OAuth2/__init__.py b/cura/OAuth2/__init__.py
new file mode 100644
index 0000000000..d5641e902f
--- /dev/null
+++ b/cura/OAuth2/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
diff --git a/cura/ObjectsModel.py b/cura/ObjectsModel.py
index f3c703d424..f9f923b31d 100644
--- a/cura/ObjectsModel.py
+++ b/cura/ObjectsModel.py
@@ -5,10 +5,12 @@ from PyQt5.QtCore import QTimer
from UM.Application import Application
from UM.Qt.ListModel import ListModel
+from UM.Scene.Camera import Camera
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
from UM.i18n import i18nCatalog
+from collections import defaultdict
catalog = i18nCatalog("cura")
@@ -18,19 +20,24 @@ class ObjectsModel(ListModel):
def __init__(self):
super().__init__()
- Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed)
+ Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSceneDelayed)
Application.getInstance().getPreferences().preferenceChanged.connect(self._updateDelayed)
self._update_timer = QTimer()
- self._update_timer.setInterval(100)
+ self._update_timer.setInterval(200)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._update)
self._build_plate_number = -1
def setActiveBuildPlate(self, nr):
- self._build_plate_number = nr
- self._update()
+ if self._build_plate_number != nr:
+ self._build_plate_number = nr
+ self._update()
+
+ def _updateSceneDelayed(self, source):
+ if not isinstance(source, Camera):
+ self._update_timer.start()
def _updateDelayed(self, *args):
self._update_timer.start()
@@ -40,6 +47,8 @@ class ObjectsModel(ListModel):
filter_current_build_plate = Application.getInstance().getPreferences().getValue("view/filter_current_build_plate")
active_build_plate_number = self._build_plate_number
group_nr = 1
+ name_count_dict = defaultdict(int)
+
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
if not isinstance(node, SceneNode):
continue
@@ -55,6 +64,7 @@ class ObjectsModel(ListModel):
if not node.callDecoration("isGroup"):
name = node.getName()
+
else:
name = catalog.i18nc("@label", "Group #{group_nr}").format(group_nr = str(group_nr))
group_nr += 1
@@ -63,6 +73,14 @@ class ObjectsModel(ListModel):
is_outside_build_area = node.isOutsideBuildArea()
else:
is_outside_build_area = False
+
+ #check if we already have an instance of the object based on name
+ name_count_dict[name] += 1
+ name_count = name_count_dict[name]
+
+ if name_count > 1:
+ name = "{0}({1})".format(name, name_count-1)
+ node.setName(name)
nodes.append({
"name": name,
@@ -71,6 +89,7 @@ class ObjectsModel(ListModel):
"buildPlateNumber": node_build_plate_number,
"node": node
})
+
nodes = sorted(nodes, key=lambda n: n["name"])
self.setItems(nodes)
diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py
index 8ddcdbfb2f..8fffac4501 100755
--- a/cura/PlatformPhysics.py
+++ b/cura/PlatformPhysics.py
@@ -17,7 +17,6 @@ from cura.Scene import ZOffsetDecorator
import random # used for list shuffling
-
class PlatformPhysics:
def __init__(self, controller, volume):
super().__init__()
@@ -40,8 +39,9 @@ class PlatformPhysics:
Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", True)
def _onSceneChanged(self, source):
- if not source.getMeshData():
+ if not source.callDecoration("isSliceable"):
return
+
self._change_timer.start()
def _onChangeTimerFinished(self):
diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py
index 21e2040dc1..ba7c74fd6d 100644
--- a/cura/PrintInformation.py
+++ b/cura/PrintInformation.py
@@ -6,73 +6,58 @@ import math
import os
import unicodedata
import re # To create abbreviations for printer names.
-from typing import Dict
+from typing import Dict, List, Optional
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
-from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Qt.Duration import Duration
from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog
-from UM.MimeTypeDatabase import MimeTypeDatabase
+from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
catalog = i18nCatalog("cura")
-## A class for processing and calculating minimum, current and maximum print time as well as managing the job name
-#
-# This class contains all the logic relating to calculation and slicing for the
-# time/quality slider concept. It is a rather tricky combination of event handling
-# and state management. The logic behind this is as follows:
-#
-# - A scene change or setting change event happens.
-# We track what the source was of the change, either a scene change, a setting change, an active machine change or something else.
-# - This triggers a new slice with the current settings - this is the "current settings pass".
-# - When the slice is done, we update the current print time and material amount.
-# - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here.
-# - When that is done, we update the minimum print time and start the final slice pass, the "Extra Fine settings pass".
-# - When the Extra Fine pass is done, we update the maximum print time.
+## A class for processing and the print times per build plate as well as managing the job name
#
# This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
# This job name is requested by the JobSpecs qml file.
class PrintInformation(QObject):
- class SlicePass:
- CurrentSettings = 1
- LowQualitySettings = 2
- HighQualitySettings = 3
- class SliceReason:
- SceneChanged = 1
- SettingChanged = 2
- ActiveMachineChanged = 3
- Other = 4
+ UNTITLED_JOB_NAME = "Untitled"
- def __init__(self, application, parent = None):
+ def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent)
self._application = application
self.initializeCuraMessagePrintTimeProperties()
- self._material_lengths = {} # indexed by build plate number
- self._material_weights = {}
- self._material_costs = {}
- self._material_names = {}
+ # Indexed by build plate number
+ self._material_lengths = {} # type: Dict[int, List[float]]
+ self._material_weights = {} # type: Dict[int, List[float]]
+ self._material_costs = {} # type: Dict[int, List[float]]
+ self._material_names = {} # type: Dict[int, List[str]]
self._pre_sliced = False
self._backend = self._application.getBackend()
if self._backend:
self._backend.printDurationMessage.connect(self._onPrintDurationMessage)
+
self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged)
self._is_user_specified_job_name = False
self._base_name = ""
self._abbr_machine = ""
self._job_name = ""
- self._project_name = ""
self._active_build_plate = 0
- self._initVariablesWithBuildPlate(self._active_build_plate)
+ self._initVariablesByBuildPlate(self._active_build_plate)
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
@@ -80,19 +65,15 @@ class PrintInformation(QObject):
self._application.globalContainerStackChanged.connect(self.setToZeroPrintInformation)
self._application.fileLoaded.connect(self.setBaseName)
self._application.workspaceLoaded.connect(self.setProjectName)
- self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
-
+ self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged)
self._application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
- self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged)
+ self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
+ self._material_amounts = [] # type: List[float]
self._onActiveMaterialsChanged()
- self._material_amounts = []
-
- # Crate cura message translations and using translation keys initialize empty time Duration object for total time
- # and time for each feature
- def initializeCuraMessagePrintTimeProperties(self):
- self._current_print_time = {} # Duration(None, self)
+ def initializeCuraMessagePrintTimeProperties(self) -> None:
+ self._current_print_time = {} # type: Dict[int, Duration]
self._print_time_message_translations = {
"inset_0": catalog.i18nc("@tooltip", "Outer Wall"),
@@ -108,17 +89,17 @@ class PrintInformation(QObject):
"none": catalog.i18nc("@tooltip", "Other")
}
- self._print_time_message_values = {}
+ self._print_times_per_feature = {} # type: Dict[int, Dict[str, Duration]]
- def _initPrintTimeMessageValues(self, build_plate_number):
+ def _initPrintTimesPerFeature(self, build_plate_number: int) -> None:
# Full fill message values using keys from _print_time_message_translations
- self._print_time_message_values[build_plate_number] = {}
+ self._print_times_per_feature[build_plate_number] = {}
for key in self._print_time_message_translations.keys():
- self._print_time_message_values[build_plate_number][key] = Duration(None, self)
+ self._print_times_per_feature[build_plate_number][key] = Duration(None, self)
- def _initVariablesWithBuildPlate(self, build_plate_number):
- if build_plate_number not in self._print_time_message_values:
- self._initPrintTimeMessageValues(build_plate_number)
+ def _initVariablesByBuildPlate(self, build_plate_number: int) -> None:
+ if build_plate_number not in self._print_times_per_feature:
+ self._initPrintTimesPerFeature(build_plate_number)
if self._active_build_plate not in self._material_lengths:
self._material_lengths[self._active_build_plate] = []
if self._active_build_plate not in self._material_weights:
@@ -128,23 +109,24 @@ class PrintInformation(QObject):
if self._active_build_plate not in self._material_names:
self._material_names[self._active_build_plate] = []
if self._active_build_plate not in self._current_print_time:
- self._current_print_time[self._active_build_plate] = Duration(None, self)
+ self._current_print_time[self._active_build_plate] = Duration(parent = self)
currentPrintTimeChanged = pyqtSignal()
preSlicedChanged = pyqtSignal()
@pyqtProperty(bool, notify=preSlicedChanged)
- def preSliced(self):
+ def preSliced(self) -> bool:
return self._pre_sliced
- def setPreSliced(self, pre_sliced):
- self._pre_sliced = pre_sliced
- self._updateJobName()
- self.preSlicedChanged.emit()
+ def setPreSliced(self, pre_sliced: bool) -> None:
+ if self._pre_sliced != pre_sliced:
+ self._pre_sliced = pre_sliced
+ self._updateJobName()
+ self.preSlicedChanged.emit()
@pyqtProperty(Duration, notify = currentPrintTimeChanged)
- def currentPrintTime(self):
+ def currentPrintTime(self) -> Duration:
return self._current_print_time[self._active_build_plate]
materialLengthsChanged = pyqtSignal()
@@ -171,36 +153,41 @@ class PrintInformation(QObject):
def materialNames(self):
return self._material_names[self._active_build_plate]
- def printTimes(self):
- return self._print_time_message_values[self._active_build_plate]
+ # Get all print times (by feature) of the active buildplate.
+ def printTimes(self) -> Dict[str, Duration]:
+ return self._print_times_per_feature[self._active_build_plate]
- def _onPrintDurationMessage(self, build_plate_number, print_time: Dict[str, int], material_amounts: list):
- self._updateTotalPrintTimePerFeature(build_plate_number, print_time)
+ def _onPrintDurationMessage(self, build_plate_number: int, print_times_per_feature: Dict[str, int], material_amounts: List[float]) -> None:
+ self._updateTotalPrintTimePerFeature(build_plate_number, print_times_per_feature)
self.currentPrintTimeChanged.emit()
self._material_amounts = material_amounts
self._calculateInformation(build_plate_number)
- def _updateTotalPrintTimePerFeature(self, build_plate_number, print_time: Dict[str, int]):
+ def _updateTotalPrintTimePerFeature(self, build_plate_number: int, print_times_per_feature: Dict[str, int]) -> None:
total_estimated_time = 0
- if build_plate_number not in self._print_time_message_values:
- self._initPrintTimeMessageValues(build_plate_number)
+ if build_plate_number not in self._print_times_per_feature:
+ self._initPrintTimesPerFeature(build_plate_number)
+
+ for feature, time in print_times_per_feature.items():
+ if feature not in self._print_times_per_feature[build_plate_number]:
+ self._print_times_per_feature[build_plate_number][feature] = Duration(parent=self)
+ duration = self._print_times_per_feature[build_plate_number][feature]
- for feature, time in print_time.items():
if time != time: # Check for NaN. Engine can sometimes give us weird values.
- self._print_time_message_values[build_plate_number].get(feature).setDuration(0)
+ duration.setDuration(0)
Logger.log("w", "Received NaN for print duration message")
continue
total_estimated_time += time
- self._print_time_message_values[build_plate_number].get(feature).setDuration(time)
+ duration.setDuration(time)
if build_plate_number not in self._current_print_time:
self._current_print_time[build_plate_number] = Duration(None, self)
self._current_print_time[build_plate_number].setDuration(total_estimated_time)
- def _calculateInformation(self, build_plate_number):
+ def _calculateInformation(self, build_plate_number: int) -> None:
global_stack = self._application.getGlobalContainerStack()
if global_stack is None:
return
@@ -213,39 +200,46 @@ class PrintInformation(QObject):
material_preference_values = json.loads(self._application.getInstance().getPreferences().getValue("cura/material_settings"))
extruder_stacks = global_stack.extruders
- for position, extruder_stack in extruder_stacks.items():
+
+ for position in extruder_stacks:
+ extruder_stack = extruder_stacks[position]
index = int(position)
if index >= len(self._material_amounts):
continue
amount = self._material_amounts[index]
- ## Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some
- # list comprehension filtering to solve this for us.
+ # Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some
+ # list comprehension filtering to solve this for us.
density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
- material = extruder_stack.findContainer({"type": "material"})
+ material = extruder_stack.material
radius = extruder_stack.getProperty("material_diameter", "value") / 2
weight = float(amount) * float(density) / 1000
- cost = 0
- material_name = catalog.i18nc("@label unknown material", "Unknown")
- if material:
- material_guid = material.getMetaDataEntry("GUID")
- material_name = material.getName()
- if material_guid in material_preference_values:
- material_values = material_preference_values[material_guid]
+ cost = 0.
- weight_per_spool = float(material_values["spool_weight"] if material_values and "spool_weight" in material_values else 0)
- cost_per_spool = float(material_values["spool_cost"] if material_values and "spool_cost" in material_values else 0)
+ material_guid = material.getMetaDataEntry("GUID")
+ material_name = material.getName()
- if weight_per_spool != 0:
- cost = cost_per_spool * weight / weight_per_spool
- else:
- cost = 0
+ if material_guid in material_preference_values:
+ material_values = material_preference_values[material_guid]
+
+ if material_values and "spool_weight" in material_values:
+ weight_per_spool = float(material_values["spool_weight"])
+ else:
+ weight_per_spool = float(extruder_stack.getMetaDataEntry("properties", {}).get("weight", 0))
+
+ cost_per_spool = float(material_values["spool_cost"] if material_values and "spool_cost" in material_values else 0)
+
+ if weight_per_spool != 0:
+ cost = cost_per_spool * weight / weight_per_spool
+ else:
+ cost = 0
# Material amount is sent as an amount of mm^3, so calculate length from that
if radius != 0:
length = round((amount / (math.pi * radius ** 2)) / 1000, 2)
else:
length = 0
+
self._material_weights[build_plate_number].append(weight)
self._material_lengths[build_plate_number].append(length)
self._material_costs[build_plate_number].append(cost)
@@ -256,20 +250,20 @@ class PrintInformation(QObject):
self.materialCostsChanged.emit()
self.materialNamesChanged.emit()
- def _onPreferencesChanged(self, preference):
+ def _onPreferencesChanged(self, preference: str) -> None:
if preference != "cura/material_settings":
return
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
self._calculateInformation(build_plate_number)
- def _onActiveBuildPlateChanged(self):
+ def _onActiveBuildPlateChanged(self) -> None:
new_active_build_plate = self._multi_build_plate_model.activeBuildPlate
if new_active_build_plate != self._active_build_plate:
self._active_build_plate = new_active_build_plate
self._updateJobName()
- self._initVariablesWithBuildPlate(self._active_build_plate)
+ self._initVariablesByBuildPlate(self._active_build_plate)
self.materialLengthsChanged.emit()
self.materialWeightsChanged.emit()
@@ -277,14 +271,14 @@ class PrintInformation(QObject):
self.materialNamesChanged.emit()
self.currentPrintTimeChanged.emit()
- def _onActiveMaterialsChanged(self, *args, **kwargs):
+ def _onActiveMaterialsChanged(self, *args, **kwargs) -> None:
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
self._calculateInformation(build_plate_number)
# Manual override of job name should also set the base name so that when the printer prefix is updated, it the
# prefix can be added to the manually added name, not the old base name
@pyqtSlot(str, bool)
- def setJobName(self, name, is_user_specified_job_name = False):
+ def setJobName(self, name: str, is_user_specified_job_name = False) -> None:
self._is_user_specified_job_name = is_user_specified_job_name
self._job_name = name
self._base_name = name.replace(self._abbr_machine + "_", "")
@@ -298,15 +292,15 @@ class PrintInformation(QObject):
def jobName(self):
return self._job_name
- def _updateJobName(self):
+ def _updateJobName(self) -> None:
if self._base_name == "":
- self._job_name = "unnamed"
+ self._job_name = self.UNTITLED_JOB_NAME
self._is_user_specified_job_name = False
self.jobNameChanged.emit()
return
base_name = self._stripAccents(self._base_name)
- self._setAbbreviatedMachineName()
+ self._defineAbbreviatedMachineName()
# Only update the job name when it's not user-specified.
if not self._is_user_specified_job_name:
@@ -333,12 +327,12 @@ class PrintInformation(QObject):
self.jobNameChanged.emit()
@pyqtSlot(str)
- def setProjectName(self, name):
+ def setProjectName(self, name: str) -> None:
self.setBaseName(name, is_project_file = True)
baseNameChanged = pyqtSignal()
- def setBaseName(self, base_name: str, is_project_file: bool = False):
+ def setBaseName(self, base_name: str, is_project_file: bool = False) -> None:
self._is_user_specified_job_name = False
# Ensure that we don't use entire path but only filename
@@ -365,7 +359,7 @@ class PrintInformation(QObject):
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(name)
data = mime_type.stripExtension(name)
- except:
+ except MimeTypeNotFoundError:
Logger.log("w", "Unsupported Mime Type Database file extension %s", name)
if data is not None and check_name is not None:
@@ -373,6 +367,16 @@ class PrintInformation(QObject):
else:
self._base_name = ""
+ # Strip the old "curaproject" extension from the name
+ OLD_CURA_PROJECT_EXT = ".curaproject"
+ if self._base_name.lower().endswith(OLD_CURA_PROJECT_EXT):
+ self._base_name = self._base_name[:len(self._base_name) - len(OLD_CURA_PROJECT_EXT)]
+
+ # CURA-5896 Try to strip extra extensions with an infinite amount of ".curaproject.3mf".
+ OLD_CURA_PROJECT_3MF_EXT = ".curaproject.3mf"
+ while self._base_name.lower().endswith(OLD_CURA_PROJECT_3MF_EXT):
+ self._base_name = self._base_name[:len(self._base_name) - len(OLD_CURA_PROJECT_3MF_EXT)]
+
self._updateJobName()
@pyqtProperty(str, fset = setBaseName, notify = baseNameChanged)
@@ -382,39 +386,25 @@ class PrintInformation(QObject):
## Created an acronym-like abbreviated machine name from the currently
# active machine name.
# Called each time the global stack is switched.
- def _setAbbreviatedMachineName(self):
+ def _defineAbbreviatedMachineName(self) -> None:
global_container_stack = self._application.getGlobalContainerStack()
if not global_container_stack:
self._abbr_machine = ""
return
active_machine_type_name = global_container_stack.definition.getName()
- abbr_machine = ""
- for word in re.findall(r"[\w']+", active_machine_type_name):
- if word.lower() == "ultimaker":
- abbr_machine += "UM"
- elif word.isdigit():
- abbr_machine += word
- else:
- stripped_word = self._stripAccents(word.upper())
- # - use only the first character if the word is too long (> 3 characters)
- # - use the whole word if it's not too long (<= 3 characters)
- if len(stripped_word) > 3:
- stripped_word = stripped_word[0]
- abbr_machine += stripped_word
-
- self._abbr_machine = abbr_machine
+ self._abbr_machine = self._application.getMachineManager().getAbbreviatedMachineName(active_machine_type_name)
## Utility method that strips accents from characters (eg: â -> a)
- def _stripAccents(self, str):
- return ''.join(char for char in unicodedata.normalize('NFD', str) if unicodedata.category(char) != 'Mn')
+ def _stripAccents(self, to_strip: str) -> str:
+ return ''.join(char for char in unicodedata.normalize('NFD', to_strip) if unicodedata.category(char) != 'Mn')
@pyqtSlot(result = "QVariantMap")
- def getFeaturePrintTimes(self):
+ def getFeaturePrintTimes(self) -> Dict[str, Duration]:
result = {}
- if self._active_build_plate not in self._print_time_message_values:
- self._initPrintTimeMessageValues(self._active_build_plate)
- for feature, time in self._print_time_message_values[self._active_build_plate].items():
+ if self._active_build_plate not in self._print_times_per_feature:
+ self._initPrintTimesPerFeature(self._active_build_plate)
+ for feature, time in self._print_times_per_feature[self._active_build_plate].items():
if feature in self._print_time_message_translations:
result[self._print_time_message_translations[feature]] = time
else:
@@ -422,22 +412,22 @@ class PrintInformation(QObject):
return result
# Simulate message with zero time duration
- def setToZeroPrintInformation(self, build_plate = None):
+ def setToZeroPrintInformation(self, build_plate: Optional[int] = None) -> None:
if build_plate is None:
build_plate = self._active_build_plate
# Construct the 0-time message
temp_message = {}
- if build_plate not in self._print_time_message_values:
- self._print_time_message_values[build_plate] = {}
- for key in self._print_time_message_values[build_plate].keys():
+ if build_plate not in self._print_times_per_feature:
+ self._print_times_per_feature[build_plate] = {}
+ for key in self._print_times_per_feature[build_plate].keys():
temp_message[key] = 0
- temp_material_amounts = [0]
+ temp_material_amounts = [0.]
self._onPrintDurationMessage(build_plate, temp_message, temp_material_amounts)
## Listen to scene changes to check if we need to reset the print information
- def _onSceneChanged(self, scene_node):
+ def _onSceneChanged(self, scene_node: SceneNode) -> None:
# Ignore any changes that are not related to sliceable objects
if not isinstance(scene_node, SceneNode)\
or not scene_node.callDecoration("isSliceable")\
diff --git a/cura/PrintJobPreviewImageProvider.py b/cura/PrintJobPreviewImageProvider.py
new file mode 100644
index 0000000000..a8df5aa273
--- /dev/null
+++ b/cura/PrintJobPreviewImageProvider.py
@@ -0,0 +1,27 @@
+from PyQt5.QtGui import QImage
+from PyQt5.QtQuick import QQuickImageProvider
+from PyQt5.QtCore import QSize
+
+from UM.Application import Application
+
+
+class PrintJobPreviewImageProvider(QQuickImageProvider):
+ def __init__(self):
+ super().__init__(QQuickImageProvider.Image)
+
+ ## Request a new image.
+ def requestImage(self, id: str, size: QSize) -> QImage:
+ # The id will have an uuid and an increment separated by a slash. As we don't care about the value of the
+ # increment, we need to strip that first.
+ uuid = id[id.find("/") + 1:]
+ for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
+ if not hasattr(output_device, "printJobs"):
+ continue
+
+ for print_job in output_device.printJobs:
+ if print_job.key == uuid:
+ if print_job.getPreviewImage():
+ return print_job.getPreviewImage(), QSize(15, 15)
+ else:
+ return QImage(), QSize(15, 15)
+ return QImage(), QSize(15,15)
\ No newline at end of file
diff --git a/cura/PrinterOutput/ConfigurationModel.py b/cura/PrinterOutput/ConfigurationModel.py
index c03d968b9e..312e3cffb0 100644
--- a/cura/PrinterOutput/ConfigurationModel.py
+++ b/cura/PrinterOutput/ConfigurationModel.py
@@ -13,42 +13,50 @@ class ConfigurationModel(QObject):
configurationChanged = pyqtSignal()
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- self._printer_type = None
+ self._printer_type = ""
self._extruder_configurations = [] # type: List[ExtruderConfigurationModel]
- self._buildplate_configuration = None
+ self._buildplate_configuration = ""
def setPrinterType(self, printer_type):
self._printer_type = printer_type
@pyqtProperty(str, fset = setPrinterType, notify = configurationChanged)
- def printerType(self):
+ def printerType(self) -> str:
return self._printer_type
- def setExtruderConfigurations(self, extruder_configurations):
- self._extruder_configurations = extruder_configurations
+ def setExtruderConfigurations(self, extruder_configurations: List["ExtruderConfigurationModel"]):
+ if self._extruder_configurations != extruder_configurations:
+ self._extruder_configurations = extruder_configurations
+
+ for extruder_configuration in self._extruder_configurations:
+ extruder_configuration.extruderConfigurationChanged.connect(self.configurationChanged)
+
+ self.configurationChanged.emit()
@pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
def extruderConfigurations(self):
return self._extruder_configurations
- def setBuildplateConfiguration(self, buildplate_configuration):
- self._buildplate_configuration = buildplate_configuration
+ def setBuildplateConfiguration(self, buildplate_configuration: str) -> None:
+ if self._buildplate_configuration != buildplate_configuration:
+ self._buildplate_configuration = buildplate_configuration
+ self.configurationChanged.emit()
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
- def buildplateConfiguration(self):
+ def buildplateConfiguration(self) -> str:
return self._buildplate_configuration
## This method is intended to indicate whether the configuration is valid or not.
# The method checks if the mandatory fields are or not set
- def isValid(self):
+ def isValid(self) -> bool:
if not self._extruder_configurations:
return False
for configuration in self._extruder_configurations:
if configuration is None:
return False
- return self._printer_type is not None
+ return self._printer_type != ""
def __str__(self):
message_chunks = []
diff --git a/cura/PrinterOutput/ExtruderConfigurationModel.py b/cura/PrinterOutput/ExtruderConfigurationModel.py
index bc7f1a7c07..da0ad6b0b2 100644
--- a/cura/PrinterOutput/ExtruderConfigurationModel.py
+++ b/cura/PrinterOutput/ExtruderConfigurationModel.py
@@ -1,56 +1,67 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
+from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
+
class ExtruderConfigurationModel(QObject):
extruderConfigurationChanged = pyqtSignal()
- def __init__(self):
+ def __init__(self, position: int = -1) -> None:
super().__init__()
- self._position = -1
- self._material = None
- self._hotend_id = None
+ self._position = position # type: int
+ self._material = None # type: Optional[MaterialOutputModel]
+ self._hotend_id = None # type: Optional[str]
- def setPosition(self, position):
+ def setPosition(self, position: int) -> None:
self._position = position
@pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
- def position(self):
+ def position(self) -> int:
return self._position
- def setMaterial(self, material):
- self._material = material
+ def setMaterial(self, material: Optional[MaterialOutputModel]) -> None:
+ if self._hotend_id != material:
+ self._material = material
+ self.extruderConfigurationChanged.emit()
@pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
- def material(self):
+ def activeMaterial(self) -> Optional[MaterialOutputModel]:
return self._material
- def setHotendID(self, hotend_id):
- self._hotend_id = hotend_id
+ @pyqtProperty(QObject, fset=setMaterial, notify=extruderConfigurationChanged)
+ def material(self) -> Optional[MaterialOutputModel]:
+ return self._material
+
+ def setHotendID(self, hotend_id: Optional[str]) -> None:
+ if self._hotend_id != hotend_id:
+ self._hotend_id = hotend_id
+ self.extruderConfigurationChanged.emit()
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)
- def hotendID(self):
+ def hotendID(self) -> Optional[str]:
return self._hotend_id
## This method is intended to indicate whether the configuration is valid or not.
# The method checks if the mandatory fields are or not set
# At this moment is always valid since we allow to have empty material and variants.
- def isValid(self):
+ def isValid(self) -> bool:
return True
- def __str__(self):
+ def __str__(self) -> str:
message_chunks = []
message_chunks.append("Position: " + str(self._position))
message_chunks.append("-")
- message_chunks.append("Material: " + self.material.type if self.material else "empty")
+ message_chunks.append("Material: " + self.activeMaterial.type if self.activeMaterial else "empty")
message_chunks.append("-")
message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
return " ".join(message_chunks)
- def __eq__(self, other):
+ def __eq__(self, other) -> bool:
return hash(self) == hash(other)
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
diff --git a/cura/PrinterOutput/ExtruderOutputModel.py b/cura/PrinterOutput/ExtruderOutputModel.py
index 0726662c6c..30d53bbd85 100644
--- a/cura/PrinterOutput/ExtruderOutputModel.py
+++ b/cura/PrinterOutput/ExtruderOutputModel.py
@@ -12,64 +12,61 @@ if TYPE_CHECKING:
class ExtruderOutputModel(QObject):
- hotendIDChanged = pyqtSignal()
targetHotendTemperatureChanged = pyqtSignal()
hotendTemperatureChanged = pyqtSignal()
- activeMaterialChanged = pyqtSignal()
+
extruderConfigurationChanged = pyqtSignal()
isPreheatingChanged = pyqtSignal()
- def __init__(self, printer: "PrinterOutputModel", position, parent=None) -> None:
+ def __init__(self, printer: "PrinterOutputModel", position: int, parent=None) -> None:
super().__init__(parent)
- self._printer = printer
+ self._printer = printer # type: PrinterOutputModel
self._position = position
- self._target_hotend_temperature = 0 # type: float
- self._hotend_temperature = 0 # type: float
- self._hotend_id = ""
- self._active_material = None # type: Optional[MaterialOutputModel]
- self._extruder_configuration = ExtruderConfigurationModel()
- self._extruder_configuration.position = self._position
+ self._target_hotend_temperature = 0.0 # type: float
+ self._hotend_temperature = 0.0 # type: float
self._is_preheating = False
- def getPrinter(self):
+ # The extruder output model wraps the configuration model. This way we can use the same config model for jobs
+ # and extruders alike.
+ self._extruder_configuration = ExtruderConfigurationModel()
+ self._extruder_configuration.position = self._position
+ self._extruder_configuration.extruderConfigurationChanged.connect(self.extruderConfigurationChanged)
+
+ def getPrinter(self) -> "PrinterOutputModel":
return self._printer
- def getPosition(self):
+ def getPosition(self) -> int:
return self._position
# Does the printer support pre-heating the bed at all
@pyqtProperty(bool, constant=True)
- def canPreHeatHotends(self):
+ def canPreHeatHotends(self) -> bool:
if self._printer:
return self._printer.canPreHeatHotends
return False
- @pyqtProperty(QObject, notify = activeMaterialChanged)
+ @pyqtProperty(QObject, notify = extruderConfigurationChanged)
def activeMaterial(self) -> Optional["MaterialOutputModel"]:
- return self._active_material
+ return self._extruder_configuration.activeMaterial
- def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
- if self._active_material != material:
- self._active_material = material
- self._extruder_configuration.material = self._active_material
- self.activeMaterialChanged.emit()
- self.extruderConfigurationChanged.emit()
+ def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]) -> None:
+ self._extruder_configuration.setMaterial(material)
## Update the hotend temperature. This only changes it locally.
- def updateHotendTemperature(self, temperature: float):
+ def updateHotendTemperature(self, temperature: float) -> None:
if self._hotend_temperature != temperature:
self._hotend_temperature = temperature
self.hotendTemperatureChanged.emit()
- def updateTargetHotendTemperature(self, temperature: float):
+ def updateTargetHotendTemperature(self, temperature: float) -> None:
if self._target_hotend_temperature != temperature:
self._target_hotend_temperature = temperature
self.targetHotendTemperatureChanged.emit()
## Set the target hotend temperature. This ensures that it's actually sent to the remote.
@pyqtSlot(float)
- def setTargetHotendTemperature(self, temperature: float):
+ def setTargetHotendTemperature(self, temperature: float) -> None:
self._printer.getController().setTargetHotendTemperature(self._printer, self, temperature)
self.updateTargetHotendTemperature(temperature)
@@ -81,30 +78,26 @@ class ExtruderOutputModel(QObject):
def hotendTemperature(self) -> float:
return self._hotend_temperature
- @pyqtProperty(str, notify = hotendIDChanged)
+ @pyqtProperty(str, notify = extruderConfigurationChanged)
def hotendID(self) -> str:
- return self._hotend_id
+ return self._extruder_configuration.hotendID
- def updateHotendID(self, id: str):
- if self._hotend_id != id:
- self._hotend_id = id
- self._extruder_configuration.hotendID = self._hotend_id
- self.hotendIDChanged.emit()
- self.extruderConfigurationChanged.emit()
+ def updateHotendID(self, hotend_id: str) -> None:
+ self._extruder_configuration.setHotendID(hotend_id)
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
- def extruderConfiguration(self):
+ def extruderConfiguration(self) -> Optional[ExtruderConfigurationModel]:
if self._extruder_configuration.isValid():
return self._extruder_configuration
return None
- def updateIsPreheating(self, pre_heating):
+ def updateIsPreheating(self, pre_heating: bool) -> None:
if self._is_preheating != pre_heating:
self._is_preheating = pre_heating
self.isPreheatingChanged.emit()
@pyqtProperty(bool, notify=isPreheatingChanged)
- def isPreheating(self):
+ def isPreheating(self) -> bool:
return self._is_preheating
## Pre-heats the extruder before printer.
@@ -113,9 +106,9 @@ class ExtruderOutputModel(QObject):
# Celsius.
# \param duration How long the bed should stay warm, in seconds.
@pyqtSlot(float, float)
- def preheatHotend(self, temperature, duration):
+ def preheatHotend(self, temperature: float, duration: float) -> None:
self._printer._controller.preheatHotend(self, temperature, duration)
@pyqtSlot()
- def cancelPreheatHotend(self):
- self._printer._controller.cancelPreheatHotend(self)
\ No newline at end of file
+ def cancelPreheatHotend(self) -> None:
+ self._printer._controller.cancelPreheatHotend(self)
diff --git a/cura/PrinterOutput/FirmwareUpdater.py b/cura/PrinterOutput/FirmwareUpdater.py
new file mode 100644
index 0000000000..c6d9513ee0
--- /dev/null
+++ b/cura/PrinterOutput/FirmwareUpdater.py
@@ -0,0 +1,78 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
+
+from enum import IntEnum
+from threading import Thread
+from typing import Union
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutputDevice import PrinterOutputDevice
+
+class FirmwareUpdater(QObject):
+ firmwareProgressChanged = pyqtSignal()
+ firmwareUpdateStateChanged = pyqtSignal()
+
+ def __init__(self, output_device: "PrinterOutputDevice") -> None:
+ super().__init__()
+
+ self._output_device = output_device
+
+ self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
+
+ self._firmware_file = ""
+ self._firmware_progress = 0
+ self._firmware_update_state = FirmwareUpdateState.idle
+
+ def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
+ # the file path could be url-encoded.
+ if firmware_file.startswith("file://"):
+ self._firmware_file = QUrl(firmware_file).toLocalFile()
+ else:
+ self._firmware_file = firmware_file
+
+ self._setFirmwareUpdateState(FirmwareUpdateState.updating)
+
+ self._update_firmware_thread.start()
+
+ def _updateFirmware(self) -> None:
+ raise NotImplementedError("_updateFirmware needs to be implemented")
+
+ ## Cleanup after a succesful update
+ def _cleanupAfterUpdate(self) -> None:
+ # Clean up for next attempt.
+ self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
+ self._firmware_file = ""
+ self._onFirmwareProgress(100)
+ self._setFirmwareUpdateState(FirmwareUpdateState.completed)
+
+ @pyqtProperty(int, notify = firmwareProgressChanged)
+ def firmwareProgress(self) -> int:
+ return self._firmware_progress
+
+ @pyqtProperty(int, notify=firmwareUpdateStateChanged)
+ def firmwareUpdateState(self) -> "FirmwareUpdateState":
+ return self._firmware_update_state
+
+ def _setFirmwareUpdateState(self, state: "FirmwareUpdateState") -> None:
+ if self._firmware_update_state != state:
+ self._firmware_update_state = state
+ self.firmwareUpdateStateChanged.emit()
+
+ # Callback function for firmware update progress.
+ def _onFirmwareProgress(self, progress: int, max_progress: int = 100) -> None:
+ self._firmware_progress = int(progress * 100 / max_progress) # Convert to scale of 0-100
+ self.firmwareProgressChanged.emit()
+
+
+class FirmwareUpdateState(IntEnum):
+ idle = 0
+ updating = 1
+ completed = 2
+ unknown_error = 3
+ communication_error = 4
+ io_error = 5
+ firmware_not_found_error = 6
+
diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py
index e6310e5bff..1cb416787c 100644
--- a/cura/PrinterOutput/GenericOutputController.py
+++ b/cura/PrinterOutput/GenericOutputController.py
@@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Set, Union, Optional
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from PyQt5.QtCore import QTimer
@@ -9,27 +9,28 @@ from PyQt5.QtCore import QTimer
if TYPE_CHECKING:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+ from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
class GenericOutputController(PrinterOutputController):
- def __init__(self, output_device):
+ def __init__(self, output_device: "PrinterOutputDevice") -> None:
super().__init__(output_device)
self._preheat_bed_timer = QTimer()
self._preheat_bed_timer.setSingleShot(True)
self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
- self._preheat_printer = None
+ self._preheat_printer = None # type: Optional[PrinterOutputModel]
self._preheat_hotends_timer = QTimer()
self._preheat_hotends_timer.setSingleShot(True)
self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished)
- self._preheat_hotends = set()
+ self._preheat_hotends = set() # type: Set[ExtruderOutputModel]
self._output_device.printersChanged.connect(self._onPrintersChanged)
- self._active_printer = None
+ self._active_printer = None # type: Optional[PrinterOutputModel]
- def _onPrintersChanged(self):
+ def _onPrintersChanged(self) -> None:
if self._active_printer:
self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged)
self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged)
@@ -43,32 +44,33 @@ class GenericOutputController(PrinterOutputController):
for extruder in self._active_printer.extruders:
extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged)
- def _onPrinterStateChanged(self):
- if self._active_printer.state != "idle":
+ def _onPrinterStateChanged(self) -> None:
+ if self._active_printer and self._active_printer.state != "idle":
if self._preheat_bed_timer.isActive():
self._preheat_bed_timer.stop()
- self._preheat_printer.updateIsPreheating(False)
+ if self._preheat_printer:
+ self._preheat_printer.updateIsPreheating(False)
if self._preheat_hotends_timer.isActive():
self._preheat_hotends_timer.stop()
for extruder in self._preheat_hotends:
extruder.updateIsPreheating(False)
- self._preheat_hotends = set()
+ self._preheat_hotends = set() # type: Set[ExtruderOutputModel]
- def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
+ def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
self._output_device.sendCommand("G91")
self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
self._output_device.sendCommand("G90")
- def homeHead(self, printer):
+ def homeHead(self, printer: "PrinterOutputModel") -> None:
self._output_device.sendCommand("G28 X Y")
- def homeBed(self, printer):
+ def homeBed(self, printer: "PrinterOutputModel") -> None:
self._output_device.sendCommand("G28 Z")
- def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
- self._output_device.sendCommand(command)
+ def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None:
+ self._output_device.sendCommand(command.upper()) #Most printers only understand uppercase g-code commands.
- def setJobState(self, job: "PrintJobOutputModel", state: str):
+ def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
if state == "pause":
self._output_device.pausePrint()
job.updateState("paused")
@@ -79,42 +81,46 @@ class GenericOutputController(PrinterOutputController):
self._output_device.cancelPrint()
pass
- def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
- self._output_device.sendCommand("M140 S%s" % temperature)
+ def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float) -> None:
+ self._output_device.sendCommand("M140 S%s" % round(temperature)) # The API doesn't allow floating point.
- def _onTargetBedTemperatureChanged(self):
- if self._preheat_bed_timer.isActive() and self._preheat_printer.targetBedTemperature == 0:
+ def _onTargetBedTemperatureChanged(self) -> None:
+ if self._preheat_bed_timer.isActive() and self._preheat_printer and self._preheat_printer.targetBedTemperature == 0:
self._preheat_bed_timer.stop()
self._preheat_printer.updateIsPreheating(False)
- def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
+ def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None:
try:
temperature = round(temperature) # The API doesn't allow floating point.
duration = round(duration)
except ValueError:
return # Got invalid values, can't pre-heat.
- self.setTargetBedTemperature(printer, temperature=temperature)
+ self.setTargetBedTemperature(printer, temperature = temperature)
self._preheat_bed_timer.setInterval(duration * 1000)
self._preheat_bed_timer.start()
self._preheat_printer = printer
printer.updateIsPreheating(True)
- def cancelPreheatBed(self, printer: "PrinterOutputModel"):
- self.setTargetBedTemperature(printer, temperature=0)
+ def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None:
+ self.setTargetBedTemperature(printer, temperature = 0)
self._preheat_bed_timer.stop()
printer.updateIsPreheating(False)
- def _onPreheatBedTimerFinished(self):
+ def _onPreheatBedTimerFinished(self) -> None:
+ if not self._preheat_printer:
+ return
self.setTargetBedTemperature(self._preheat_printer, 0)
self._preheat_printer.updateIsPreheating(False)
- def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: int):
+ def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None:
self._output_device.sendCommand("M104 S%s T%s" % (temperature, position))
- def _onTargetHotendTemperatureChanged(self):
+ def _onTargetHotendTemperatureChanged(self) -> None:
if not self._preheat_hotends_timer.isActive():
return
+ if not self._active_printer:
+ return
for extruder in self._active_printer.extruders:
if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0:
@@ -123,7 +129,7 @@ class GenericOutputController(PrinterOutputController):
if not self._preheat_hotends:
self._preheat_hotends_timer.stop()
- def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
+ def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None:
position = extruder.getPosition()
number_of_extruders = len(extruder.getPrinter().extruders)
if position >= number_of_extruders:
@@ -141,7 +147,7 @@ class GenericOutputController(PrinterOutputController):
self._preheat_hotends.add(extruder)
extruder.updateIsPreheating(True)
- def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
+ def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None:
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0)
if extruder in self._preheat_hotends:
extruder.updateIsPreheating(False)
@@ -149,21 +155,22 @@ class GenericOutputController(PrinterOutputController):
if not self._preheat_hotends and self._preheat_hotends_timer.isActive():
self._preheat_hotends_timer.stop()
- def _onPreheatHotendsTimerFinished(self):
+ def _onPreheatHotendsTimerFinished(self) -> None:
for extruder in self._preheat_hotends:
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
- self._preheat_hotends = set()
+ self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
# Cancel any ongoing preheating timers, without setting back the temperature to 0
# This can be used eg at the start of a print
- def stopPreheatTimers(self):
+ def stopPreheatTimers(self) -> None:
if self._preheat_hotends_timer.isActive():
for extruder in self._preheat_hotends:
extruder.updateIsPreheating(False)
- self._preheat_hotends = set()
+ self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
self._preheat_hotends_timer.stop()
if self._preheat_bed_timer.isActive():
- self._preheat_printer.updateIsPreheating(False)
+ if self._preheat_printer:
+ self._preheat_printer.updateIsPreheating(False)
self._preheat_bed_timer.stop()
diff --git a/cura/PrinterOutput/NetworkCamera.py b/cura/PrinterOutput/NetworkCamera.py
deleted file mode 100644
index 5b28ffd30d..0000000000
--- a/cura/PrinterOutput/NetworkCamera.py
+++ /dev/null
@@ -1,119 +0,0 @@
-from UM.Logger import Logger
-
-from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, QObject, pyqtSlot
-from PyQt5.QtGui import QImage
-from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
-
-
-class NetworkCamera(QObject):
- newImage = pyqtSignal()
-
- def __init__(self, target = None, parent = None):
- super().__init__(parent)
- self._stream_buffer = b""
- self._stream_buffer_start_index = -1
- self._manager = None
- self._image_request = None
- self._image_reply = None
- self._image = QImage()
- self._image_id = 0
-
- self._target = target
- self._started = False
-
- @pyqtSlot(str)
- def setTarget(self, target):
- restart_required = False
- if self._started:
- self.stop()
- restart_required = True
-
- self._target = target
-
- if restart_required:
- self.start()
-
- @pyqtProperty(QUrl, notify=newImage)
- def latestImage(self):
- self._image_id += 1
- # There is an image provider that is called "camera". In order to ensure that the image qml object, that
- # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
- # as new (instead of relying on cached version and thus forces an update.
- temp = "image://camera/" + str(self._image_id)
-
- return QUrl(temp, QUrl.TolerantMode)
-
- @pyqtSlot()
- def start(self):
- # Ensure that previous requests (if any) are stopped.
- self.stop()
- if self._target is None:
- Logger.log("w", "Unable to start camera stream without target!")
- return
- self._started = True
- url = QUrl(self._target)
- self._image_request = QNetworkRequest(url)
- if self._manager is None:
- self._manager = QNetworkAccessManager()
-
- self._image_reply = self._manager.get(self._image_request)
- self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
-
- @pyqtSlot()
- def stop(self):
- self._stream_buffer = b""
- self._stream_buffer_start_index = -1
-
- if self._image_reply:
- try:
- # disconnect the signal
- try:
- self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
- except Exception:
- pass
- # abort the request if it's not finished
- if not self._image_reply.isFinished():
- self._image_reply.close()
- except Exception as e: # RuntimeError
- pass # It can happen that the wrapped c++ object is already deleted.
-
- self._image_reply = None
- self._image_request = None
-
- self._manager = None
-
- self._started = False
-
- def getImage(self):
- return self._image
-
- ## Ensure that close gets called when object is destroyed
- def __del__(self):
- self.stop()
-
- def _onStreamDownloadProgress(self, bytes_received, bytes_total):
- # An MJPG stream is (for our purpose) a stream of concatenated JPG images.
- # JPG images start with the marker 0xFFD8, and end with 0xFFD9
- if self._image_reply is None:
- return
- self._stream_buffer += self._image_reply.readAll()
-
- if len(self._stream_buffer) > 2000000: # No single camera frame should be 2 Mb or larger
- Logger.log("w", "MJPEG buffer exceeds reasonable size. Restarting stream...")
- self.stop() # resets stream buffer and start index
- self.start()
- return
-
- if self._stream_buffer_start_index == -1:
- self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
- stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
- # If this happens to be more than a single frame, then so be it; the JPG decoder will
- # ignore the extra data. We do it like this in order not to get a buildup of frames
-
- if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
- jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
- self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
- self._stream_buffer_start_index = -1
- self._image.loadFromData(jpg_data)
-
- self.newImage.emit()
diff --git a/cura/PrinterOutput/NetworkMJPGImage.py b/cura/PrinterOutput/NetworkMJPGImage.py
new file mode 100644
index 0000000000..522d684085
--- /dev/null
+++ b/cura/PrinterOutput/NetworkMJPGImage.py
@@ -0,0 +1,153 @@
+# Copyright (c) 2018 Aldo Hoeben / fieldOfView
+# NetworkMJPGImage is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray
+from PyQt5.QtGui import QImage, QPainter
+from PyQt5.QtQuick import QQuickPaintedItem
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
+
+from UM.Logger import Logger
+
+#
+# A QQuickPaintedItem that progressively downloads a network mjpeg stream,
+# picks it apart in individual jpeg frames, and paints it.
+#
+class NetworkMJPGImage(QQuickPaintedItem):
+
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+
+ self._stream_buffer = QByteArray()
+ self._stream_buffer_start_index = -1
+ self._network_manager = None # type: QNetworkAccessManager
+ self._image_request = None # type: QNetworkRequest
+ self._image_reply = None # type: QNetworkReply
+ self._image = QImage()
+ self._image_rect = QRect()
+
+ self._source_url = QUrl()
+ self._started = False
+
+ self._mirror = False
+
+ self.setAntialiasing(True)
+
+ ## Ensure that close gets called when object is destroyed
+ def __del__(self) -> None:
+ self.stop()
+
+
+ def paint(self, painter: "QPainter") -> None:
+ if self._mirror:
+ painter.drawImage(self.contentsBoundingRect(), self._image.mirrored())
+ return
+
+ painter.drawImage(self.contentsBoundingRect(), self._image)
+
+
+ def setSourceURL(self, source_url: "QUrl") -> None:
+ self._source_url = source_url
+ self.sourceURLChanged.emit()
+ if self._started:
+ self.start()
+
+ def getSourceURL(self) -> "QUrl":
+ return self._source_url
+
+ sourceURLChanged = pyqtSignal()
+ source = pyqtProperty(QUrl, fget = getSourceURL, fset = setSourceURL, notify = sourceURLChanged)
+
+ def setMirror(self, mirror: bool) -> None:
+ if mirror == self._mirror:
+ return
+ self._mirror = mirror
+ self.mirrorChanged.emit()
+ self.update()
+
+ def getMirror(self) -> bool:
+ return self._mirror
+
+ mirrorChanged = pyqtSignal()
+ mirror = pyqtProperty(bool, fget = getMirror, fset = setMirror, notify = mirrorChanged)
+
+ imageSizeChanged = pyqtSignal()
+
+ @pyqtProperty(int, notify = imageSizeChanged)
+ def imageWidth(self) -> int:
+ return self._image.width()
+
+ @pyqtProperty(int, notify = imageSizeChanged)
+ def imageHeight(self) -> int:
+ return self._image.height()
+
+
+ @pyqtSlot()
+ def start(self) -> None:
+ self.stop() # Ensure that previous requests (if any) are stopped.
+
+ if not self._source_url:
+ Logger.log("w", "Unable to start camera stream without target!")
+ return
+ self._started = True
+
+ self._image_request = QNetworkRequest(self._source_url)
+ if self._network_manager is None:
+ self._network_manager = QNetworkAccessManager()
+
+ self._image_reply = self._network_manager.get(self._image_request)
+ self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
+
+ @pyqtSlot()
+ def stop(self) -> None:
+ self._stream_buffer = QByteArray()
+ self._stream_buffer_start_index = -1
+
+ if self._image_reply:
+ try:
+ try:
+ self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
+ except Exception:
+ pass
+
+ if not self._image_reply.isFinished():
+ self._image_reply.close()
+ except Exception as e: # RuntimeError
+ pass # It can happen that the wrapped c++ object is already deleted.
+
+ self._image_reply = None
+ self._image_request = None
+
+ self._network_manager = None
+
+ self._started = False
+
+
+ def _onStreamDownloadProgress(self, bytes_received: int, bytes_total: int) -> None:
+ # An MJPG stream is (for our purpose) a stream of concatenated JPG images.
+ # JPG images start with the marker 0xFFD8, and end with 0xFFD9
+ if self._image_reply is None:
+ return
+ self._stream_buffer += self._image_reply.readAll()
+
+ if len(self._stream_buffer) > 2000000: # No single camera frame should be 2 Mb or larger
+ Logger.log("w", "MJPEG buffer exceeds reasonable size. Restarting stream...")
+ self.stop() # resets stream buffer and start index
+ self.start()
+ return
+
+ if self._stream_buffer_start_index == -1:
+ self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
+ stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
+ # If this happens to be more than a single frame, then so be it; the JPG decoder will
+ # ignore the extra data. We do it like this in order not to get a buildup of frames
+
+ if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
+ jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
+ self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
+ self._stream_buffer_start_index = -1
+ self._image.loadFromData(jpg_data)
+
+ if self._image.rect() != self._image_rect:
+ self.imageSizeChanged.emit()
+
+ self.update()
diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
index b7862251c9..0e33a71249 100644
--- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
+++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
@@ -4,19 +4,21 @@
from UM.FileHandler.FileHandler import FileHandler #For typing.
from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode #For typing.
+from cura.API import Account
from cura.CuraApplication import CuraApplication
-from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
+from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
from time import time
-from typing import Any, Callable, Dict, List, Optional
+from typing import Callable, Dict, List, Optional, Union
from enum import IntEnum
import os # To get the username
import gzip
+
class AuthState(IntEnum):
NotAuthenticated = 1
AuthenticationRequested = 2
@@ -28,8 +30,8 @@ class AuthState(IntEnum):
class NetworkedPrinterOutputDevice(PrinterOutputDevice):
authenticationStateChanged = pyqtSignal()
- def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], parent: QObject = None) -> None:
- super().__init__(device_id = device_id, parent = parent)
+ def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None) -> None:
+ super().__init__(device_id = device_id, connection_type = connection_type, parent = parent)
self._manager = None # type: Optional[QNetworkAccessManager]
self._last_manager_create_time = None # type: Optional[float]
self._recreate_network_manager_time = 30
@@ -41,7 +43,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._api_prefix = ""
self._address = address
self._properties = properties
- self._user_agent = "%s/%s " % (CuraApplication.getInstance().getApplicationName(), CuraApplication.getInstance().getVersion())
+ self._user_agent = "%s/%s " % (CuraApplication.getInstance().getApplicationName(),
+ CuraApplication.getInstance().getVersion())
self._onFinishedCallbacks = {} # type: Dict[str, Callable[[QNetworkReply], None]]
self._authentication_state = AuthState.NotAuthenticated
@@ -53,22 +56,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._sending_gcode = False
self._compressing_gcode = False
self._gcode = [] # type: List[str]
-
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
- printer_type = self._properties.get(b"machine", b"").decode("utf-8")
- printer_type_identifiers = {
- "9066": "ultimaker3",
- "9511": "ultimaker3_extended",
- "9051": "ultimaker_s5"
- }
- self._printer_type = "Unknown"
- for key, value in printer_type_identifiers.items():
- if printer_type.startswith(key):
- self._printer_type = value
- break
-
- def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
+ def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False,
+ file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
raise NotImplementedError("requestWrite needs to be implemented")
def setAuthenticationState(self, authentication_state: AuthState) -> None:
@@ -138,17 +129,15 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
if self._connection_state_before_timeout is None:
self._connection_state_before_timeout = self._connection_state
- self.setConnectionState(ConnectionState.closed)
+ self.setConnectionState(ConnectionState.Closed)
# We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to
# sleep.
if time_since_last_response > self._recreate_network_manager_time:
- if self._last_manager_create_time is None:
- self._createNetworkManager()
- elif time() - self._last_manager_create_time > self._recreate_network_manager_time:
+ if self._last_manager_create_time is None or time() - self._last_manager_create_time > self._recreate_network_manager_time:
self._createNetworkManager()
assert(self._manager is not None)
- elif self._connection_state == ConnectionState.closed:
+ elif self._connection_state == ConnectionState.Closed:
# Go out of timeout.
if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here
self.setConnectionState(self._connection_state_before_timeout)
@@ -158,10 +147,15 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
url = QUrl("http://" + self._address + self._api_prefix + target)
request = QNetworkRequest(url)
if content_type is not None:
- request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
+ request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
return request
+ ## This method was only available privately before, but it was actually called from SendMaterialJob.py.
+ # We now have a public equivalent as well. We did not remove the private one as plugins might be using that.
+ def createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
+ return self._createFormPart(content_header, data, content_type)
+
def _createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
part = QHttpPart()
@@ -175,9 +169,15 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
part.setBody(data)
return part
- ## Convenience function to get the username from the OS.
- # The code was copied from the getpass module, as we try to use as little dependencies as possible.
+ ## Convenience function to get the username, either from the cloud or from the OS.
def _getUserName(self) -> str:
+ # check first if we are logged in with the Ultimaker Account
+ account = CuraApplication.getInstance().getCuraAPI().account # type: Account
+ if account and account.isLoggedIn:
+ return account.userName
+
+ # Otherwise get the username from the US
+ # The code below was copied from the getpass module, as we try to use as little dependencies as possible.
for name in ("LOGNAME", "USER", "LNAME", "USERNAME"):
user = os.environ.get(name)
if user:
@@ -188,40 +188,95 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
if reply in self._kept_alive_multiparts:
del self._kept_alive_multiparts[reply]
- def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+ def _validateManager(self) -> None:
if self._manager is None:
self._createNetworkManager()
assert (self._manager is not None)
- request = self._createEmptyRequest(target)
+
+ ## Sends a put request to the given path.
+ # \param url: The path after the API prefix.
+ # \param data: The data to be sent in the body
+ # \param content_type: The content type of the body data.
+ # \param on_finished: The function to call when the response is received.
+ # \param on_progress: The function to call when the progress changes. Parameters are bytes_sent / bytes_total.
+ def put(self, url: str, data: Union[str, bytes], content_type: Optional[str] = "application/json",
+ on_finished: Optional[Callable[[QNetworkReply], None]] = None,
+ on_progress: Optional[Callable[[int, int], None]] = None) -> None:
+ self._validateManager()
+
+ request = self._createEmptyRequest(url, content_type = content_type)
self._last_request_time = time()
- reply = self._manager.put(request, data.encode())
+
+ if not self._manager:
+ Logger.log("e", "No network manager was created to execute the PUT call with.")
+ return
+
+ body = data if isinstance(data, bytes) else data.encode() # type: bytes
+ reply = self._manager.put(request, body)
self._registerOnFinishedCallback(reply, on_finished)
- def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
- if self._manager is None:
- self._createNetworkManager()
- assert (self._manager is not None)
- request = self._createEmptyRequest(target)
+ if on_progress is not None:
+ reply.uploadProgress.connect(on_progress)
+
+ ## Sends a delete request to the given path.
+ # \param url: The path after the API prefix.
+ # \param on_finished: The function to be call when the response is received.
+ def delete(self, url: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+ self._validateManager()
+
+ request = self._createEmptyRequest(url)
self._last_request_time = time()
+
+ if not self._manager:
+ Logger.log("e", "No network manager was created to execute the DELETE call with.")
+ return
+
+ reply = self._manager.deleteResource(request)
+ self._registerOnFinishedCallback(reply, on_finished)
+
+ ## Sends a get request to the given path.
+ # \param url: The path after the API prefix.
+ # \param on_finished: The function to be call when the response is received.
+ def get(self, url: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+ self._validateManager()
+
+ request = self._createEmptyRequest(url)
+ self._last_request_time = time()
+
+ if not self._manager:
+ Logger.log("e", "No network manager was created to execute the GET call with.")
+ return
+
reply = self._manager.get(request)
self._registerOnFinishedCallback(reply, on_finished)
- def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
- if self._manager is None:
- self._createNetworkManager()
- assert (self._manager is not None)
- request = self._createEmptyRequest(target)
+ ## Sends a post request to the given path.
+ # \param url: The path after the API prefix.
+ # \param data: The data to be sent in the body
+ # \param on_finished: The function to call when the response is received.
+ # \param on_progress: The function to call when the progress changes. Parameters are bytes_sent / bytes_total.
+ def post(self, url: str, data: Union[str, bytes],
+ on_finished: Optional[Callable[[QNetworkReply], None]],
+ on_progress: Optional[Callable[[int, int], None]] = None) -> None:
+ self._validateManager()
+
+ request = self._createEmptyRequest(url)
self._last_request_time = time()
- reply = self._manager.post(request, data)
+
+ if not self._manager:
+ Logger.log("e", "Could not find manager.")
+ return
+
+ body = data if isinstance(data, bytes) else data.encode() # type: bytes
+ reply = self._manager.post(request, body)
if on_progress is not None:
reply.uploadProgress.connect(on_progress)
self._registerOnFinishedCallback(reply, on_finished)
- def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
-
- if self._manager is None:
- self._createNetworkManager()
- assert (self._manager is not None)
+ def postFormWithParts(self, target: str, parts: List[QHttpPart],
+ on_finished: Optional[Callable[[QNetworkReply], None]],
+ on_progress: Optional[Callable[[int, int], None]] = None) -> QNetworkReply:
+ self._validateManager()
request = self._createEmptyRequest(target, content_type=None)
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
for part in parts:
@@ -229,15 +284,18 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._last_request_time = time()
- reply = self._manager.post(request, multi_post_part)
+ if self._manager is not None:
+ reply = self._manager.post(request, multi_post_part)
- self._kept_alive_multiparts[reply] = multi_post_part
+ self._kept_alive_multiparts[reply] = multi_post_part
- if on_progress is not None:
- reply.uploadProgress.connect(on_progress)
- self._registerOnFinishedCallback(reply, on_finished)
+ if on_progress is not None:
+ reply.uploadProgress.connect(on_progress)
+ self._registerOnFinishedCallback(reply, on_finished)
- return reply
+ return reply
+ else:
+ Logger.log("e", "Could not find manager.")
def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
post_part = QHttpPart()
@@ -252,11 +310,11 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
def _createNetworkManager(self) -> None:
Logger.log("d", "Creating network manager")
if self._manager:
- self._manager.finished.disconnect(self.__handleOnFinished)
+ self._manager.finished.disconnect(self._handleOnFinished)
self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired)
self._manager = QNetworkAccessManager()
- self._manager.finished.connect(self.__handleOnFinished)
+ self._manager.finished.connect(self._handleOnFinished)
self._last_manager_create_time = time()
self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
@@ -267,7 +325,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
if on_finished is not None:
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = on_finished
- def __handleOnFinished(self, reply: QNetworkReply) -> None:
+ def _handleOnFinished(self, reply: QNetworkReply) -> None:
# Due to garbage collection, we need to cache certain bits of post operations.
# As we don't want to keep them around forever, delete them if we get a reply.
if reply.operation() == QNetworkAccessManager.PostOperation:
@@ -279,8 +337,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._last_response_time = time()
- if self._connection_state == ConnectionState.connecting:
- self.setConnectionState(ConnectionState.connected)
+ if self._connection_state == ConnectionState.Connecting:
+ self.setConnectionState(ConnectionState.Connected)
callback_key = reply.url().toString() + str(reply.operation())
try:
@@ -323,7 +381,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
@pyqtProperty(str, constant = True)
def printerType(self) -> str:
- return self._printer_type
+ return self._properties.get(b"printer_type", b"Unknown").decode("utf-8")
## IP adress of this printer
@pyqtProperty(str, constant = True)
diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py
index b77600f85c..fb163ef065 100644
--- a/cura/PrinterOutput/PrintJobOutputModel.py
+++ b/cura/PrinterOutput/PrintJobOutputModel.py
@@ -1,12 +1,16 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
-from typing import Optional, TYPE_CHECKING
+from typing import Optional, TYPE_CHECKING, List
+
+from PyQt5.QtCore import QUrl
+from PyQt5.QtGui import QImage
if TYPE_CHECKING:
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+ from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
class PrintJobOutputModel(QObject):
@@ -17,6 +21,9 @@ class PrintJobOutputModel(QObject):
keyChanged = pyqtSignal()
assignedPrinterChanged = pyqtSignal()
ownerChanged = pyqtSignal()
+ configurationChanged = pyqtSignal()
+ previewImageChanged = pyqtSignal()
+ compatibleMachineFamiliesChanged = pyqtSignal()
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
super().__init__(parent)
@@ -29,6 +36,48 @@ class PrintJobOutputModel(QObject):
self._assigned_printer = None # type: Optional[PrinterOutputModel]
self._owner = "" # Who started/owns the print job?
+ self._configuration = None # type: Optional[ConfigurationModel]
+ self._compatible_machine_families = [] # type: List[str]
+ self._preview_image_id = 0
+
+ self._preview_image = None # type: Optional[QImage]
+
+ @pyqtProperty("QStringList", notify=compatibleMachineFamiliesChanged)
+ def compatibleMachineFamilies(self):
+ # Hack; Some versions of cluster will return a family more than once...
+ return list(set(self._compatible_machine_families))
+
+ def setCompatibleMachineFamilies(self, compatible_machine_families: List[str]) -> None:
+ if self._compatible_machine_families != compatible_machine_families:
+ self._compatible_machine_families = compatible_machine_families
+ self.compatibleMachineFamiliesChanged.emit()
+
+ @pyqtProperty(QUrl, notify=previewImageChanged)
+ def previewImageUrl(self):
+ self._preview_image_id += 1
+ # There is an image provider that is called "print_job_preview". In order to ensure that the image qml object, that
+ # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
+ # as new (instead of relying on cached version and thus forces an update.
+ temp = "image://print_job_preview/" + str(self._preview_image_id) + "/" + self._key
+ return QUrl(temp, QUrl.TolerantMode)
+
+ def getPreviewImage(self) -> Optional[QImage]:
+ return self._preview_image
+
+ def updatePreviewImage(self, preview_image: Optional[QImage]) -> None:
+ if self._preview_image != preview_image:
+ self._preview_image = preview_image
+ self.previewImageChanged.emit()
+
+ @pyqtProperty(QObject, notify=configurationChanged)
+ def configuration(self) -> Optional["ConfigurationModel"]:
+ return self._configuration
+
+ def updateConfiguration(self, configuration: Optional["ConfigurationModel"]) -> None:
+ if self._configuration != configuration:
+ self._configuration = configuration
+ self.configurationChanged.emit()
+
@pyqtProperty(str, notify=ownerChanged)
def owner(self):
return self._owner
@@ -42,7 +91,7 @@ class PrintJobOutputModel(QObject):
def assignedPrinter(self):
return self._assigned_printer
- def updateAssignedPrinter(self, assigned_printer: "PrinterOutputModel"):
+ def updateAssignedPrinter(self, assigned_printer: Optional["PrinterOutputModel"]) -> None:
if self._assigned_printer != assigned_printer:
old_printer = self._assigned_printer
self._assigned_printer = assigned_printer
@@ -70,17 +119,39 @@ class PrintJobOutputModel(QObject):
self.nameChanged.emit()
@pyqtProperty(int, notify = timeTotalChanged)
- def timeTotal(self):
+ def timeTotal(self) -> int:
return self._time_total
@pyqtProperty(int, notify = timeElapsedChanged)
- def timeElapsed(self):
+ def timeElapsed(self) -> int:
return self._time_elapsed
+ @pyqtProperty(int, notify = timeElapsedChanged)
+ def timeRemaining(self) -> int:
+ # Never get a negative time remaining
+ return max(self.timeTotal - self.timeElapsed, 0)
+
+ @pyqtProperty(float, notify = timeElapsedChanged)
+ def progress(self) -> float:
+ result = float(self.timeElapsed) / max(self.timeTotal, 1.0) # Prevent a division by zero exception.
+ return min(result, 1.0) # Never get a progress past 1.0
+
@pyqtProperty(str, notify=stateChanged)
- def state(self):
+ def state(self) -> str:
return self._state
+ @pyqtProperty(bool, notify=stateChanged)
+ def isActive(self) -> bool:
+ inactiveStates = [
+ "pausing",
+ "paused",
+ "resuming",
+ "wait_cleanup"
+ ]
+ if self.state in inactiveStates and self.timeRemaining > 0:
+ return False
+ return True
+
def updateTimeTotal(self, new_time_total):
if self._time_total != new_time_total:
self._time_total = new_time_total
@@ -98,4 +169,4 @@ class PrintJobOutputModel(QObject):
@pyqtSlot(str)
def setState(self, state):
- self._output_controller.setJobState(self, state)
\ No newline at end of file
+ self._output_controller.setJobState(self, state)
diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py
index 58c6ef05a7..aa06ada8a3 100644
--- a/cura/PrinterOutput/PrinterOutputController.py
+++ b/cura/PrinterOutput/PrinterOutputController.py
@@ -1,57 +1,68 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
+from UM.Signal import Signal
+
+from typing import Union
MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+ from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
class PrinterOutputController:
- def __init__(self, output_device):
+ def __init__(self, output_device: "PrinterOutputDevice") -> None:
self.can_pause = True
self.can_abort = True
self.can_pre_heat_bed = True
self.can_pre_heat_hotends = True
self.can_send_raw_gcode = True
self.can_control_manually = True
+ self.can_update_firmware = False
self._output_device = output_device
- def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: int):
+ def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: float) -> None:
Logger.log("w", "Set target hotend temperature not implemented in controller")
- def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
+ def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float) -> None:
Logger.log("w", "Set target bed temperature not implemented in controller")
- def setJobState(self, job: "PrintJobOutputModel", state: str):
+ def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
Logger.log("w", "Set job state not implemented in controller")
- def cancelPreheatBed(self, printer: "PrinterOutputModel"):
+ def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None:
Logger.log("w", "Cancel preheat bed not implemented in controller")
- def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
+ def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None:
Logger.log("w", "Preheat bed not implemented in controller")
- def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
+ def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None:
Logger.log("w", "Cancel preheat hotend not implemented in controller")
- def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
+ def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None:
Logger.log("w", "Preheat hotend not implemented in controller")
- def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed):
+ def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
Logger.log("w", "Set head position not implemented in controller")
- def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
+ def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
Logger.log("w", "Move head not implemented in controller")
- def homeBed(self, printer: "PrinterOutputModel"):
+ def homeBed(self, printer: "PrinterOutputModel") -> None:
Logger.log("w", "Home bed not implemented in controller")
- def homeHead(self, printer: "PrinterOutputModel"):
+ def homeHead(self, printer: "PrinterOutputModel") -> None:
Logger.log("w", "Home head not implemented in controller")
- def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
+ def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None:
Logger.log("w", "Custom command not implemented in controller")
+
+ canUpdateFirmwareChanged = Signal()
+ def setCanUpdateFirmware(self, can_update_firmware: bool) -> None:
+ if can_update_firmware != self.can_update_firmware:
+ self.can_update_firmware = can_update_firmware
+ self.canUpdateFirmwareChanged.emit()
\ No newline at end of file
diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py
index f10d6bd75b..12884b5f9b 100644
--- a/cura/PrinterOutput/PrinterOutputModel.py
+++ b/cura/PrinterOutput/PrinterOutputModel.py
@@ -1,8 +1,8 @@
-# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
-from typing import Optional
+from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl
+from typing import List, Dict, Optional
from UM.Math.Vector import Vector
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
@@ -22,138 +22,140 @@ class PrinterOutputModel(QObject):
nameChanged = pyqtSignal()
headPositionChanged = pyqtSignal()
keyChanged = pyqtSignal()
- printerTypeChanged = pyqtSignal()
+ typeChanged = pyqtSignal()
buildplateChanged = pyqtSignal()
- cameraChanged = pyqtSignal()
+ cameraUrlChanged = pyqtSignal()
configurationChanged = pyqtSignal()
+ canUpdateFirmwareChanged = pyqtSignal()
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None:
super().__init__(parent)
- self._bed_temperature = -1 # Use -1 for no heated bed.
- self._target_bed_temperature = 0
+ self._bed_temperature = -1 # type: float # Use -1 for no heated bed.
+ self._target_bed_temperature = 0 # type: float
self._name = ""
self._key = "" # Unique identifier
self._controller = output_controller
+ self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
- self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
+ self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
self._head_position = Vector(0, 0, 0)
self._active_print_job = None # type: Optional[PrintJobOutputModel]
self._firmware_version = firmware_version
self._printer_state = "unknown"
self._is_preheating = False
self._printer_type = ""
- self._buildplate_name = None
- # Update the printer configuration every time any of the extruders changes its configuration
- for extruder in self._extruders:
- extruder.extruderConfigurationChanged.connect(self._updateExtruderConfiguration)
+ self._buildplate = ""
- self._camera = None
+ self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
+ self._extruders]
+
+ self._camera_url = QUrl() # type: QUrl
@pyqtProperty(str, constant = True)
- def firmwareVersion(self):
+ def firmwareVersion(self) -> str:
return self._firmware_version
- def setCamera(self, camera):
- if self._camera is not camera:
- self._camera = camera
- self.cameraChanged.emit()
+ def setCameraUrl(self, camera_url: "QUrl") -> None:
+ if self._camera_url != camera_url:
+ self._camera_url = camera_url
+ self.cameraUrlChanged.emit()
- def updateIsPreheating(self, pre_heating):
+ @pyqtProperty(QUrl, fset = setCameraUrl, notify = cameraUrlChanged)
+ def cameraUrl(self) -> "QUrl":
+ return self._camera_url
+
+ def updateIsPreheating(self, pre_heating: bool) -> None:
if self._is_preheating != pre_heating:
self._is_preheating = pre_heating
self.isPreheatingChanged.emit()
@pyqtProperty(bool, notify=isPreheatingChanged)
- def isPreheating(self):
+ def isPreheating(self) -> bool:
return self._is_preheating
- @pyqtProperty(QObject, notify=cameraChanged)
- def camera(self):
- return self._camera
-
- @pyqtProperty(str, notify = printerTypeChanged)
- def type(self):
+ @pyqtProperty(str, notify = typeChanged)
+ def type(self) -> str:
return self._printer_type
- def updateType(self, printer_type):
+ def updateType(self, printer_type: str) -> None:
if self._printer_type != printer_type:
self._printer_type = printer_type
self._printer_configuration.printerType = self._printer_type
- self.printerTypeChanged.emit()
+ self.typeChanged.emit()
self.configurationChanged.emit()
@pyqtProperty(str, notify = buildplateChanged)
- def buildplate(self):
- return self._buildplate_name
+ def buildplate(self) -> str:
+ return self._buildplate
- def updateBuildplateName(self, buildplate_name):
- if self._buildplate_name != buildplate_name:
- self._buildplate_name = buildplate_name
- self._printer_configuration.buildplateConfiguration = self._buildplate_name
+ def updateBuildplate(self, buildplate: str) -> None:
+ if self._buildplate != buildplate:
+ self._buildplate = buildplate
+ self._printer_configuration.buildplateConfiguration = self._buildplate
self.buildplateChanged.emit()
self.configurationChanged.emit()
@pyqtProperty(str, notify=keyChanged)
- def key(self):
+ def key(self) -> str:
return self._key
- def updateKey(self, key: str):
+ def updateKey(self, key: str) -> None:
if self._key != key:
self._key = key
self.keyChanged.emit()
@pyqtSlot()
- def homeHead(self):
+ def homeHead(self) -> None:
self._controller.homeHead(self)
@pyqtSlot()
- def homeBed(self):
+ def homeBed(self) -> None:
self._controller.homeBed(self)
@pyqtSlot(str)
- def sendRawCommand(self, command: str):
+ def sendRawCommand(self, command: str) -> None:
self._controller.sendRawCommand(self, command)
@pyqtProperty("QVariantList", constant = True)
- def extruders(self):
+ def extruders(self) -> List["ExtruderOutputModel"]:
return self._extruders
@pyqtProperty(QVariant, notify = headPositionChanged)
- def headPosition(self):
+ def headPosition(self) -> Dict[str, float]:
return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position.z}
- def updateHeadPosition(self, x, y, z):
+ def updateHeadPosition(self, x: float, y: float, z: float) -> None:
if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z:
self._head_position = Vector(x, y, z)
self.headPositionChanged.emit()
@pyqtProperty(float, float, float)
@pyqtProperty(float, float, float, float)
- def setHeadPosition(self, x, y, z, speed = 3000):
+ def setHeadPosition(self, x: float, y: float, z: float, speed: float = 3000) -> None:
self.updateHeadPosition(x, y, z)
self._controller.setHeadPosition(self, x, y, z, speed)
@pyqtProperty(float)
@pyqtProperty(float, float)
- def setHeadX(self, x, speed = 3000):
+ def setHeadX(self, x: float, speed: float = 3000) -> None:
self.updateHeadPosition(x, self._head_position.y, self._head_position.z)
self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed)
@pyqtProperty(float)
@pyqtProperty(float, float)
- def setHeadY(self, y, speed = 3000):
+ def setHeadY(self, y: float, speed: float = 3000) -> None:
self.updateHeadPosition(self._head_position.x, y, self._head_position.z)
self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed)
@pyqtProperty(float)
@pyqtProperty(float, float)
- def setHeadZ(self, z, speed = 3000):
+ def setHeadZ(self, z: float, speed:float = 3000) -> None:
self.updateHeadPosition(self._head_position.x, self._head_position.y, z)
self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed)
@pyqtSlot(float, float, float)
@pyqtSlot(float, float, float, float)
- def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
+ def moveHead(self, x: float = 0, y: float = 0, z: float = 0, speed: float = 3000) -> None:
self._controller.moveHead(self, x, y, z, speed)
## Pre-heats the heated bed of the printer.
@@ -162,47 +164,46 @@ class PrinterOutputModel(QObject):
# Celsius.
# \param duration How long the bed should stay warm, in seconds.
@pyqtSlot(float, float)
- def preheatBed(self, temperature, duration):
+ def preheatBed(self, temperature: float, duration: float) -> None:
self._controller.preheatBed(self, temperature, duration)
@pyqtSlot()
- def cancelPreheatBed(self):
+ def cancelPreheatBed(self) -> None:
self._controller.cancelPreheatBed(self)
- def getController(self):
+ def getController(self) -> "PrinterOutputController":
return self._controller
- @pyqtProperty(str, notify=nameChanged)
- def name(self):
+ @pyqtProperty(str, notify = nameChanged)
+ def name(self) -> str:
return self._name
- def setName(self, name):
- self._setName(name)
+ def setName(self, name: str) -> None:
self.updateName(name)
- def updateName(self, name):
+ def updateName(self, name: str) -> None:
if self._name != name:
self._name = name
self.nameChanged.emit()
## Update the bed temperature. This only changes it locally.
- def updateBedTemperature(self, temperature):
+ def updateBedTemperature(self, temperature: float) -> None:
if self._bed_temperature != temperature:
self._bed_temperature = temperature
self.bedTemperatureChanged.emit()
- def updateTargetBedTemperature(self, temperature):
+ def updateTargetBedTemperature(self, temperature: float) -> None:
if self._target_bed_temperature != temperature:
self._target_bed_temperature = temperature
self.targetBedTemperatureChanged.emit()
## Set the target bed temperature. This ensures that it's actually sent to the remote.
- @pyqtSlot(int)
- def setTargetBedTemperature(self, temperature):
+ @pyqtSlot(float)
+ def setTargetBedTemperature(self, temperature: float) -> None:
self._controller.setTargetBedTemperature(self, temperature)
self.updateTargetBedTemperature(temperature)
- def updateActivePrintJob(self, print_job):
+ def updateActivePrintJob(self, print_job: Optional["PrintJobOutputModel"]) -> None:
if self._active_print_job != print_job:
old_print_job = self._active_print_job
@@ -214,76 +215,83 @@ class PrinterOutputModel(QObject):
old_print_job.updateAssignedPrinter(None)
self.activePrintJobChanged.emit()
- def updateState(self, printer_state):
+ def updateState(self, printer_state: str) -> None:
if self._printer_state != printer_state:
self._printer_state = printer_state
self.stateChanged.emit()
@pyqtProperty(QObject, notify = activePrintJobChanged)
- def activePrintJob(self):
+ def activePrintJob(self) -> Optional["PrintJobOutputModel"]:
return self._active_print_job
- @pyqtProperty(str, notify=stateChanged)
- def state(self):
+ @pyqtProperty(str, notify = stateChanged)
+ def state(self) -> str:
return self._printer_state
- @pyqtProperty(int, notify = bedTemperatureChanged)
- def bedTemperature(self):
+ @pyqtProperty(float, notify = bedTemperatureChanged)
+ def bedTemperature(self) -> float:
return self._bed_temperature
- @pyqtProperty(int, notify=targetBedTemperatureChanged)
- def targetBedTemperature(self):
+ @pyqtProperty(float, notify = targetBedTemperatureChanged)
+ def targetBedTemperature(self) -> float:
return self._target_bed_temperature
# Does the printer support pre-heating the bed at all
- @pyqtProperty(bool, constant=True)
- def canPreHeatBed(self):
+ @pyqtProperty(bool, constant = True)
+ def canPreHeatBed(self) -> bool:
if self._controller:
return self._controller.can_pre_heat_bed
return False
# Does the printer support pre-heating the bed at all
- @pyqtProperty(bool, constant=True)
- def canPreHeatHotends(self):
+ @pyqtProperty(bool, constant = True)
+ def canPreHeatHotends(self) -> bool:
if self._controller:
return self._controller.can_pre_heat_hotends
return False
# Does the printer support sending raw G-code at all
- @pyqtProperty(bool, constant=True)
- def canSendRawGcode(self):
+ @pyqtProperty(bool, constant = True)
+ def canSendRawGcode(self) -> bool:
if self._controller:
return self._controller.can_send_raw_gcode
return False
# Does the printer support pause at all
- @pyqtProperty(bool, constant=True)
- def canPause(self):
+ @pyqtProperty(bool, constant = True)
+ def canPause(self) -> bool:
if self._controller:
return self._controller.can_pause
return False
# Does the printer support abort at all
- @pyqtProperty(bool, constant=True)
- def canAbort(self):
+ @pyqtProperty(bool, constant = True)
+ def canAbort(self) -> bool:
if self._controller:
return self._controller.can_abort
return False
# Does the printer support manual control at all
- @pyqtProperty(bool, constant=True)
- def canControlManually(self):
+ @pyqtProperty(bool, constant = True)
+ def canControlManually(self) -> bool:
if self._controller:
return self._controller.can_control_manually
return False
+ # Does the printer support upgrading firmware
+ @pyqtProperty(bool, notify = canUpdateFirmwareChanged)
+ def canUpdateFirmware(self) -> bool:
+ if self._controller:
+ return self._controller.can_update_firmware
+ return False
+
+ # Stub to connect UM.Signal to pyqtSignal
+ def _onControllerCanUpdateFirmwareChanged(self) -> None:
+ self.canUpdateFirmwareChanged.emit()
+
# Returns the configuration (material, variant and buildplate) of the current printer
@pyqtProperty(QObject, notify = configurationChanged)
- def printerConfiguration(self):
+ def printerConfiguration(self) -> Optional[ConfigurationModel]:
if self._printer_configuration.isValid():
return self._printer_configuration
- return None
-
- def _updateExtruderConfiguration(self):
- self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders]
- self.configurationChanged.emit()
+ return None
\ No newline at end of file
diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py
index 5ea65adb8e..dbdf8c986c 100644
--- a/cura/PrinterOutputDevice.py
+++ b/cura/PrinterOutputDevice.py
@@ -1,36 +1,44 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from enum import IntEnum
+from typing import Callable, List, Optional, Union
from UM.Decorators import deprecated
from UM.i18n import i18nCatalog
from UM.OutputDevice.OutputDevice import OutputDevice
-from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal
+from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
from PyQt5.QtWidgets import QMessageBox
from UM.Logger import Logger
-from UM.FileHandler.FileHandler import FileHandler #For typing.
-from UM.Scene.SceneNode import SceneNode #For typing.
from UM.Signal import signalemitter
from UM.Qt.QtApplication import QtApplication
-
-from enum import IntEnum # For the connection state tracking.
-from typing import Callable, List, Optional
+from UM.FlameProfiler import pyqtSlot
MYPY = False
if MYPY:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
+ from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater
+ from UM.FileHandler.FileHandler import FileHandler
+ from UM.Scene.SceneNode import SceneNode
i18n_catalog = i18nCatalog("cura")
## The current processing state of the backend.
class ConnectionState(IntEnum):
- closed = 0
- connecting = 1
- connected = 2
- busy = 3
- error = 4
+ Closed = 0
+ Connecting = 1
+ Connected = 2
+ Busy = 3
+ Error = 4
+
+
+class ConnectionType(IntEnum):
+ NotConnected = 0
+ UsbConnection = 1
+ NetworkConnection = 2
+ CloudConnection = 3
## Printer output device adds extra interface options on top of output device.
@@ -44,6 +52,7 @@ class ConnectionState(IntEnum):
# For all other uses it should be used in the same way as a "regular" OutputDevice.
@signalemitter
class PrinterOutputDevice(QObject, OutputDevice):
+
printersChanged = pyqtSignal()
connectionStateChanged = pyqtSignal(str)
acceptsCommandsChanged = pyqtSignal()
@@ -60,32 +69,34 @@ class PrinterOutputDevice(QObject, OutputDevice):
# Signal to indicate that the configuration of one of the printers has changed.
uniqueConfigurationsChanged = pyqtSignal()
- def __init__(self, device_id: str, parent: QObject = None) -> None:
+ def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.NotConnected, parent: QObject = None) -> None:
super().__init__(device_id = device_id, parent = parent) # type: ignore # MyPy complains with the multiple inheritance
self._printers = [] # type: List[PrinterOutputModel]
self._unique_configurations = [] # type: List[ConfigurationModel]
- self._monitor_view_qml_path = "" #type: str
- self._monitor_component = None #type: Optional[QObject]
- self._monitor_item = None #type: Optional[QObject]
+ self._monitor_view_qml_path = "" # type: str
+ self._monitor_component = None # type: Optional[QObject]
+ self._monitor_item = None # type: Optional[QObject]
- self._control_view_qml_path = "" #type: str
- self._control_component = None #type: Optional[QObject]
- self._control_item = None #type: Optional[QObject]
+ self._control_view_qml_path = "" # type: str
+ self._control_component = None # type: Optional[QObject]
+ self._control_item = None # type: Optional[QObject]
- self._accepts_commands = False #type: bool
+ self._accepts_commands = False # type: bool
- self._update_timer = QTimer() #type: QTimer
+ self._update_timer = QTimer() # type: QTimer
self._update_timer.setInterval(2000) # TODO; Add preference for update interval
self._update_timer.setSingleShot(False)
self._update_timer.timeout.connect(self._update)
- self._connection_state = ConnectionState.closed #type: ConnectionState
+ self._connection_state = ConnectionState.Closed # type: ConnectionState
+ self._connection_type = connection_type # type: ConnectionType
- self._firmware_name = None #type: Optional[str]
- self._address = "" #type: str
- self._connection_text = "" #type: str
+ self._firmware_updater = None # type: Optional[FirmwareUpdater]
+ self._firmware_name = None # type: Optional[str]
+ self._address = "" # type: str
+ self._connection_text = "" # type: str
self.printersChanged.connect(self._onPrintersChanged)
QtApplication.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
@@ -107,15 +118,19 @@ class PrinterOutputDevice(QObject, OutputDevice):
callback(QMessageBox.Yes)
def isConnected(self) -> bool:
- return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error
+ return self._connection_state != ConnectionState.Closed and self._connection_state != ConnectionState.Error
- def setConnectionState(self, connection_state: ConnectionState) -> None:
+ def setConnectionState(self, connection_state: "ConnectionState") -> None:
if self._connection_state != connection_state:
self._connection_state = connection_state
self.connectionStateChanged.emit(self._id)
- @pyqtProperty(str, notify = connectionStateChanged)
- def connectionState(self) -> ConnectionState:
+ @pyqtProperty(int, constant = True)
+ def connectionType(self) -> "ConnectionType":
+ return self._connection_type
+
+ @pyqtProperty(int, notify = connectionStateChanged)
+ def connectionState(self) -> "ConnectionState":
return self._connection_state
def _update(self) -> None:
@@ -128,7 +143,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
return None
- def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
+ def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False,
+ file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None:
raise NotImplementedError("requestWrite needs to be implemented")
@pyqtProperty(QObject, notify = printersChanged)
@@ -171,13 +187,13 @@ class PrinterOutputDevice(QObject, OutputDevice):
## Attempt to establish connection
def connect(self) -> None:
- self.setConnectionState(ConnectionState.connecting)
+ self.setConnectionState(ConnectionState.Connecting)
self._update_timer.start()
## Attempt to close the connection
def close(self) -> None:
self._update_timer.stop()
- self.setConnectionState(ConnectionState.closed)
+ self.setConnectionState(ConnectionState.Closed)
## Ensure that close gets called when object is destroyed
def __del__(self) -> None:
@@ -204,10 +220,17 @@ class PrinterOutputDevice(QObject, OutputDevice):
return self._unique_configurations
def _updateUniqueConfigurations(self) -> None:
- self._unique_configurations = list(set([printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None]))
- self._unique_configurations.sort(key = lambda k: k.printerType)
+ self._unique_configurations = sorted(
+ {printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None},
+ key=lambda config: config.printerType,
+ )
self.uniqueConfigurationsChanged.emit()
+ # Returns the unique configurations of the printers within this output device
+ @pyqtProperty("QStringList", notify = uniqueConfigurationsChanged)
+ def uniquePrinterTypes(self) -> List[str]:
+ return list(sorted(set([configuration.printerType for configuration in self._unique_configurations])))
+
def _onPrintersChanged(self) -> None:
for printer in self._printers:
printer.configurationChanged.connect(self._updateUniqueConfigurations)
@@ -225,4 +248,14 @@ class PrinterOutputDevice(QObject, OutputDevice):
#
# This name can be used to define device type
def getFirmwareName(self) -> Optional[str]:
- return self._firmware_name
\ No newline at end of file
+ return self._firmware_name
+
+ def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]:
+ return self._firmware_updater
+
+ @pyqtSlot(str)
+ def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
+ if not self._firmware_updater:
+ return
+
+ self._firmware_updater.updateFirmware(firmware_file)
diff --git a/cura/Scene/BlockSlicingDecorator.py b/cura/Scene/BlockSlicingDecorator.py
index 3fc0015836..0536e1635f 100644
--- a/cura/Scene/BlockSlicingDecorator.py
+++ b/cura/Scene/BlockSlicingDecorator.py
@@ -1,9 +1,12 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
class BlockSlicingDecorator(SceneNodeDecorator):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- def isBlockSlicing(self):
+ def isBlockSlicing(self) -> bool:
return True
diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py
index 367144abfc..da71f6920e 100644
--- a/cura/Scene/ConvexHullDecorator.py
+++ b/cura/Scene/ConvexHullDecorator.py
@@ -5,6 +5,7 @@ from PyQt5.QtCore import QTimer
from UM.Application import Application
from UM.Math.Polygon import Polygon
+
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from UM.Settings.ContainerRegistry import ContainerRegistry
@@ -13,39 +14,49 @@ from cura.Scene import ConvexHullNode
import numpy
+from typing import TYPE_CHECKING, Any, Optional
+
+if TYPE_CHECKING:
+ from UM.Scene.SceneNode import SceneNode
+ from cura.Settings.GlobalStack import GlobalStack
+ from UM.Mesh.MeshData import MeshData
+ from UM.Math.Matrix import Matrix
+
+
## The convex hull decorator is a scene node decorator that adds the convex hull functionality to a scene node.
# If a scene node has a convex hull decorator, it will have a shadow in which other objects can not be printed.
class ConvexHullDecorator(SceneNodeDecorator):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- self._convex_hull_node = None
+ self._convex_hull_node = None # type: Optional["SceneNode"]
self._init2DConvexHullCache()
- self._global_stack = None
+ self._global_stack = None # type: Optional[GlobalStack]
# Make sure the timer is created on the main thread
- self._recompute_convex_hull_timer = None
- Application.getInstance().callLater(self.createRecomputeConvexHullTimer)
+ self._recompute_convex_hull_timer = None # type: Optional[QTimer]
+ from cura.CuraApplication import CuraApplication
+ if CuraApplication.getInstance() is not None:
+ CuraApplication.getInstance().callLater(self.createRecomputeConvexHullTimer)
self._raft_thickness = 0.0
- # For raft thickness, DRY
- self._build_volume = Application.getInstance().getBuildVolume()
+ self._build_volume = CuraApplication.getInstance().getBuildVolume()
self._build_volume.raftThicknessChanged.connect(self._onChanged)
- Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
- Application.getInstance().getController().toolOperationStarted.connect(self._onChanged)
- Application.getInstance().getController().toolOperationStopped.connect(self._onChanged)
+ CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
+ CuraApplication.getInstance().getController().toolOperationStarted.connect(self._onChanged)
+ CuraApplication.getInstance().getController().toolOperationStopped.connect(self._onChanged)
self._onGlobalStackChanged()
- def createRecomputeConvexHullTimer(self):
+ def createRecomputeConvexHullTimer(self) -> None:
self._recompute_convex_hull_timer = QTimer()
self._recompute_convex_hull_timer.setInterval(200)
self._recompute_convex_hull_timer.setSingleShot(True)
self._recompute_convex_hull_timer.timeout.connect(self.recomputeConvexHull)
- def setNode(self, node):
+ def setNode(self, node: "SceneNode") -> None:
previous_node = self._node
# Disconnect from previous node signals
if previous_node is not None and node is not previous_node:
@@ -53,9 +64,9 @@ class ConvexHullDecorator(SceneNodeDecorator):
previous_node.parentChanged.disconnect(self._onChanged)
super().setNode(node)
-
- self._node.transformationChanged.connect(self._onChanged)
- self._node.parentChanged.connect(self._onChanged)
+ # Mypy doesn't understand that self._node is no longer optional, so just use the node.
+ node.transformationChanged.connect(self._onChanged)
+ node.parentChanged.connect(self._onChanged)
self._onChanged()
@@ -63,37 +74,46 @@ class ConvexHullDecorator(SceneNodeDecorator):
def __deepcopy__(self, memo):
return ConvexHullDecorator()
- ## Get the unmodified 2D projected convex hull of the node
- def getConvexHull(self):
+ ## Get the unmodified 2D projected convex hull of the node (if any)
+ def getConvexHull(self) -> Optional[Polygon]:
if self._node is None:
return None
hull = self._compute2DConvexHull()
- if self._global_stack and self._node:
+ if self._global_stack and self._node is not None and hull is not None:
# Parent can be None if node is just loaded.
- if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")):
+ if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self.hasGroupAsParent(self._node):
hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32)))
hull = self._add2DAdhesionMargin(hull)
return hull
## Get the convex hull of the node with the full head size
- def getConvexHullHeadFull(self):
+ def getConvexHullHeadFull(self) -> Optional[Polygon]:
if self._node is None:
return None
return self._compute2DConvexHeadFull()
+ @staticmethod
+ def hasGroupAsParent(node: "SceneNode") -> bool:
+ parent = node.getParent()
+ if parent is None:
+ return False
+ return bool(parent.callDecoration("isGroup"))
+
## Get convex hull of the object + head size
# In case of printing all at once this is the same as the convex hull.
# For one at the time this is area with intersection of mirrored head
- def getConvexHullHead(self):
+ def getConvexHullHead(self) -> Optional[Polygon]:
if self._node is None:
return None
if self._global_stack:
- if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")):
+ if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self.hasGroupAsParent(self._node):
head_with_fans = self._compute2DConvexHeadMin()
+ if head_with_fans is None:
+ return None
head_with_fans_with_adhesion_margin = self._add2DAdhesionMargin(head_with_fans)
return head_with_fans_with_adhesion_margin
return None
@@ -101,26 +121,33 @@ class ConvexHullDecorator(SceneNodeDecorator):
## Get convex hull of the node
# In case of printing all at once this is the same as the convex hull.
# For one at the time this is the area without the head.
- def getConvexHullBoundary(self):
+ def getConvexHullBoundary(self) -> Optional[Polygon]:
if self._node is None:
return None
if self._global_stack:
- if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")):
+ if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self.hasGroupAsParent(self._node):
# Printing one at a time and it's not an object in a group
return self._compute2DConvexHull()
return None
- def recomputeConvexHullDelayed(self):
+ ## The same as recomputeConvexHull, but using a timer if it was set.
+ def recomputeConvexHullDelayed(self) -> None:
if self._recompute_convex_hull_timer is not None:
self._recompute_convex_hull_timer.start()
else:
self.recomputeConvexHull()
- def recomputeConvexHull(self):
+ def recomputeConvexHull(self) -> None:
controller = Application.getInstance().getController()
root = controller.getScene().getRoot()
if self._node is None or controller.isToolOperationActive() or not self.__isDescendant(root, self._node):
+ # If the tool operation is still active, we need to compute the convex hull later after the controller is
+ # no longer active.
+ if controller.isToolOperationActive():
+ self.recomputeConvexHullDelayed()
+ return
+
if self._convex_hull_node:
self._convex_hull_node.setParent(None)
self._convex_hull_node = None
@@ -132,33 +159,38 @@ class ConvexHullDecorator(SceneNodeDecorator):
hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull, self._raft_thickness, root)
self._convex_hull_node = hull_node
- def _onSettingValueChanged(self, key, property_name):
- if property_name != "value": #Not the value that was changed.
+ def _onSettingValueChanged(self, key: str, property_name: str) -> None:
+ if property_name != "value": # Not the value that was changed.
return
if key in self._affected_settings:
self._onChanged()
if key in self._influencing_settings:
- self._init2DConvexHullCache() #Invalidate the cache.
+ self._init2DConvexHullCache() # Invalidate the cache.
self._onChanged()
- def _init2DConvexHullCache(self):
+ def _init2DConvexHullCache(self) -> None:
# Cache for the group code path in _compute2DConvexHull()
- self._2d_convex_hull_group_child_polygon = None
- self._2d_convex_hull_group_result = None
+ self._2d_convex_hull_group_child_polygon = None # type: Optional[Polygon]
+ self._2d_convex_hull_group_result = None # type: Optional[Polygon]
# Cache for the mesh code path in _compute2DConvexHull()
- self._2d_convex_hull_mesh = None
- self._2d_convex_hull_mesh_world_transform = None
- self._2d_convex_hull_mesh_result = None
+ self._2d_convex_hull_mesh = None # type: Optional[MeshData]
+ self._2d_convex_hull_mesh_world_transform = None # type: Optional[Matrix]
+ self._2d_convex_hull_mesh_result = None # type: Optional[Polygon]
- def _compute2DConvexHull(self):
+ def _compute2DConvexHull(self) -> Optional[Polygon]:
+ if self._node is None:
+ return None
if self._node.callDecoration("isGroup"):
points = numpy.zeros((0, 2), dtype=numpy.int32)
for child in self._node.getChildren():
child_hull = child.callDecoration("_compute2DConvexHull")
if child_hull:
- points = numpy.append(points, child_hull.getPoints(), axis = 0)
+ try:
+ points = numpy.append(points, child_hull.getPoints(), axis = 0)
+ except ValueError:
+ pass
if points.size < 3:
return None
@@ -178,49 +210,47 @@ class ConvexHullDecorator(SceneNodeDecorator):
return offset_hull
else:
- offset_hull = None
- mesh = None
- world_transform = None
- if self._node.getMeshData():
- mesh = self._node.getMeshData()
- world_transform = self._node.getWorldTransformation()
-
- # Check the cache
- if mesh is self._2d_convex_hull_mesh and world_transform == self._2d_convex_hull_mesh_world_transform:
- return self._2d_convex_hull_mesh_result
-
- vertex_data = mesh.getConvexHullTransformedVertices(world_transform)
- # Don't use data below 0.
- # TODO; We need a better check for this as this gives poor results for meshes with long edges.
- # Do not throw away vertices: the convex hull may be too small and objects can collide.
- # vertex_data = vertex_data[vertex_data[:,1] >= -0.01]
-
- if len(vertex_data) >= 4:
- # Round the vertex data to 1/10th of a mm, then remove all duplicate vertices
- # This is done to greatly speed up further convex hull calculations as the convex hull
- # becomes much less complex when dealing with highly detailed models.
- vertex_data = numpy.round(vertex_data, 1)
-
- vertex_data = vertex_data[:, [0, 2]] # Drop the Y components to project to 2D.
-
- # Grab the set of unique points.
- #
- # This basically finds the unique rows in the array by treating them as opaque groups of bytes
- # which are as long as the 2 float64s in each row, and giving this view to numpy.unique() to munch.
- # See http://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array
- vertex_byte_view = numpy.ascontiguousarray(vertex_data).view(
- numpy.dtype((numpy.void, vertex_data.dtype.itemsize * vertex_data.shape[1])))
- _, idx = numpy.unique(vertex_byte_view, return_index=True)
- vertex_data = vertex_data[idx] # Select the unique rows by index.
-
- hull = Polygon(vertex_data)
-
- if len(vertex_data) >= 3:
- convex_hull = hull.getConvexHull()
- offset_hull = self._offsetHull(convex_hull)
- else:
+ offset_hull = Polygon([])
+ mesh = self._node.getMeshData()
+ if mesh is None:
return Polygon([]) # Node has no mesh data, so just return an empty Polygon.
+ world_transform = self._node.getWorldTransformation()
+
+ # Check the cache
+ if mesh is self._2d_convex_hull_mesh and world_transform == self._2d_convex_hull_mesh_world_transform:
+ return self._2d_convex_hull_mesh_result
+
+ vertex_data = mesh.getConvexHullTransformedVertices(world_transform)
+ # Don't use data below 0.
+ # TODO; We need a better check for this as this gives poor results for meshes with long edges.
+ # Do not throw away vertices: the convex hull may be too small and objects can collide.
+ # vertex_data = vertex_data[vertex_data[:,1] >= -0.01]
+
+ if len(vertex_data) >= 4: # type: ignore # mypy and numpy don't play along well just yet.
+ # Round the vertex data to 1/10th of a mm, then remove all duplicate vertices
+ # This is done to greatly speed up further convex hull calculations as the convex hull
+ # becomes much less complex when dealing with highly detailed models.
+ vertex_data = numpy.round(vertex_data, 1)
+
+ vertex_data = vertex_data[:, [0, 2]] # Drop the Y components to project to 2D.
+
+ # Grab the set of unique points.
+ #
+ # This basically finds the unique rows in the array by treating them as opaque groups of bytes
+ # which are as long as the 2 float64s in each row, and giving this view to numpy.unique() to munch.
+ # See http://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array
+ vertex_byte_view = numpy.ascontiguousarray(vertex_data).view(
+ numpy.dtype((numpy.void, vertex_data.dtype.itemsize * vertex_data.shape[1])))
+ _, idx = numpy.unique(vertex_byte_view, return_index = True)
+ vertex_data = vertex_data[idx] # Select the unique rows by index.
+
+ hull = Polygon(vertex_data)
+
+ if len(vertex_data) >= 3:
+ convex_hull = hull.getConvexHull()
+ offset_hull = self._offsetHull(convex_hull)
+
# Store the result in the cache
self._2d_convex_hull_mesh = mesh
self._2d_convex_hull_mesh_world_transform = world_transform
@@ -228,42 +258,56 @@ class ConvexHullDecorator(SceneNodeDecorator):
return offset_hull
- def _getHeadAndFans(self):
- return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32))
+ def _getHeadAndFans(self) -> Polygon:
+ if self._global_stack:
+ return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32))
+ return Polygon()
- def _compute2DConvexHeadFull(self):
- return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans())
+ def _compute2DConvexHeadFull(self) -> Optional[Polygon]:
+ convex_hull = self._compute2DConvexHull()
+ if convex_hull:
+ return convex_hull.getMinkowskiHull(self._getHeadAndFans())
+ return None
- def _compute2DConvexHeadMin(self):
- headAndFans = self._getHeadAndFans()
- mirrored = headAndFans.mirror([0, 0], [0, 1]).mirror([0, 0], [1, 0]) # Mirror horizontally & vertically.
+ def _compute2DConvexHeadMin(self) -> Optional[Polygon]:
+ head_and_fans = self._getHeadAndFans()
+ mirrored = head_and_fans.mirror([0, 0], [0, 1]).mirror([0, 0], [1, 0]) # Mirror horizontally & vertically.
head_and_fans = self._getHeadAndFans().intersectionConvexHulls(mirrored)
# Min head hull is used for the push free
- min_head_hull = self._compute2DConvexHull().getMinkowskiHull(head_and_fans)
- return min_head_hull
+ convex_hull = self._compute2DConvexHull()
+ if convex_hull:
+ return convex_hull.getMinkowskiHull(head_and_fans)
+ return None
## Compensate given 2D polygon with adhesion margin
# \return 2D polygon with added margin
- def _add2DAdhesionMargin(self, poly):
+ def _add2DAdhesionMargin(self, poly: Polygon) -> Polygon:
+ if not self._global_stack:
+ return Polygon()
# Compensate for raft/skirt/brim
# Add extra margin depending on adhesion type
adhesion_type = self._global_stack.getProperty("adhesion_type", "value")
+ max_length_available = 0.5 * min(
+ self._getSettingProperty("machine_width", "value"),
+ self._getSettingProperty("machine_depth", "value")
+ )
+
if adhesion_type == "raft":
- extra_margin = max(0, self._getSettingProperty("raft_margin", "value"))
+ extra_margin = min(max_length_available, max(0, self._getSettingProperty("raft_margin", "value")))
elif adhesion_type == "brim":
- extra_margin = max(0, self._getSettingProperty("brim_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value"))
+ extra_margin = min(max_length_available, max(0, self._getSettingProperty("brim_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value")))
elif adhesion_type == "none":
extra_margin = 0
elif adhesion_type == "skirt":
- extra_margin = max(
+ extra_margin = min(max_length_available, max(
0, self._getSettingProperty("skirt_gap", "value") +
- self._getSettingProperty("skirt_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value"))
+ self._getSettingProperty("skirt_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value")))
else:
raise Exception("Unknown bed adhesion type. Did you forget to update the convex hull calculations for your new bed adhesion type?")
- # adjust head_and_fans with extra margin
+ # Adjust head_and_fans with extra margin
if extra_margin > 0:
extra_margin_polygon = Polygon.approximatedCircle(extra_margin)
poly = poly.getMinkowskiHull(extra_margin_polygon)
@@ -274,7 +318,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
# \param convex_hull Polygon of the original convex hull.
# \return New Polygon instance that is offset with everything that
# influences the collision area.
- def _offsetHull(self, convex_hull):
+ def _offsetHull(self, convex_hull: Polygon) -> Polygon:
horizontal_expansion = max(
self._getSettingProperty("xy_offset", "value"),
self._getSettingProperty("xy_offset_layer_0", "value")
@@ -295,16 +339,16 @@ class ConvexHullDecorator(SceneNodeDecorator):
else:
return convex_hull
- def _onChanged(self, *args):
+ def _onChanged(self, *args) -> None:
self._raft_thickness = self._build_volume.getRaftThickness()
if not args or args[0] == self._node:
self.recomputeConvexHullDelayed()
- def _onGlobalStackChanged(self):
+ def _onGlobalStackChanged(self) -> None:
if self._global_stack:
self._global_stack.propertyChanged.disconnect(self._onSettingValueChanged)
self._global_stack.containersChanged.disconnect(self._onChanged)
- extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_stack.getId())
+ extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
for extruder in extruders:
extruder.propertyChanged.disconnect(self._onSettingValueChanged)
@@ -314,14 +358,16 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._global_stack.propertyChanged.connect(self._onSettingValueChanged)
self._global_stack.containersChanged.connect(self._onChanged)
- extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_stack.getId())
+ extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
for extruder in extruders:
extruder.propertyChanged.connect(self._onSettingValueChanged)
self._onChanged()
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
- def _getSettingProperty(self, setting_key, prop = "value"):
+ def _getSettingProperty(self, setting_key: str, prop: str = "value") -> Any:
+ if self._global_stack is None or self._node is None:
+ return None
per_mesh_stack = self._node.callDecoration("getStack")
if per_mesh_stack:
return per_mesh_stack.getProperty(setting_key, prop)
@@ -339,8 +385,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
# Limit_to_extruder is set. The global stack handles this then
return self._global_stack.getProperty(setting_key, prop)
- ## Returns true if node is a descendant or the same as the root node.
- def __isDescendant(self, root, node):
+ ## Returns True if node is a descendant or the same as the root node.
+ def __isDescendant(self, root: "SceneNode", node: Optional["SceneNode"]) -> bool:
if node is None:
return False
if root is node:
diff --git a/cura/Scene/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py
index 4c79c7d5dc..90bf536308 100644
--- a/cura/Scene/ConvexHullNode.py
+++ b/cura/Scene/ConvexHullNode.py
@@ -1,7 +1,10 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional
from UM.Application import Application
+from UM.Math.Polygon import Polygon
+from UM.Qt.QtApplication import QtApplication
from UM.Scene.SceneNode import SceneNode
from UM.Resources import Resources
from UM.Math.Color import Color
@@ -16,7 +19,7 @@ class ConvexHullNode(SceneNode):
# location an object uses on the buildplate. This area (or area's in case of one at a time printing) is
# then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded
# to represent the raft as well.
- def __init__(self, node, hull, thickness, parent = None):
+ def __init__(self, node: SceneNode, hull: Optional[Polygon], thickness: float, parent: Optional[SceneNode] = None) -> None:
super().__init__(parent)
self.setCalculateBoundingBox(False)
@@ -25,7 +28,11 @@ class ConvexHullNode(SceneNode):
# Color of the drawn convex hull
if not Application.getInstance().getIsHeadLess():
- self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
+ theme = QtApplication.getInstance().getTheme()
+ if theme:
+ self._color = Color(*theme.getColor("convex_hull").getRgb())
+ else:
+ self._color = Color(0, 0, 0)
else:
self._color = Color(0, 0, 0)
@@ -47,7 +54,7 @@ class ConvexHullNode(SceneNode):
if hull_mesh_builder.addConvexPolygonExtrusion(
self._hull.getPoints()[::-1], # bottom layer is reversed
- self._mesh_height-thickness, self._mesh_height, color=self._color):
+ self._mesh_height - thickness, self._mesh_height, color = self._color):
hull_mesh = hull_mesh_builder.build()
self.setMeshData(hull_mesh)
@@ -75,7 +82,7 @@ class ConvexHullNode(SceneNode):
return True
- def _onNodeDecoratorsChanged(self, node):
+ def _onNodeDecoratorsChanged(self, node: SceneNode) -> None:
convex_hull_head = self._node.callDecoration("getConvexHullHead")
if convex_hull_head:
convex_hull_head_builder = MeshBuilder()
diff --git a/cura/Scene/CuraSceneController.py b/cura/Scene/CuraSceneController.py
index 4b19271538..9f26ea7cc3 100644
--- a/cura/Scene/CuraSceneController.py
+++ b/cura/Scene/CuraSceneController.py
@@ -3,6 +3,7 @@ from UM.Logger import Logger
from PyQt5.QtCore import Qt, pyqtSlot, QObject
from PyQt5.QtWidgets import QApplication
+from UM.Scene.Camera import Camera
from cura.ObjectsModel import ObjectsModel
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
@@ -33,7 +34,7 @@ class CuraSceneController(QObject):
source = args[0]
else:
source = None
- if not isinstance(source, SceneNode):
+ if not isinstance(source, SceneNode) or isinstance(source, Camera):
return
max_build_plate = self._calcMaxBuildPlate()
changed = False
diff --git a/cura/Scene/GCodeListDecorator.py b/cura/Scene/GCodeListDecorator.py
index 5738d0a7f2..d3dadb3f23 100644
--- a/cura/Scene/GCodeListDecorator.py
+++ b/cura/Scene/GCodeListDecorator.py
@@ -1,13 +1,19 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
+from typing import List
class GCodeListDecorator(SceneNodeDecorator):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- self._gcode_list = []
+ self._gcode_list = [] # type: List[str]
- def getGCodeList(self):
+ def getGCodeList(self) -> List[str]:
return self._gcode_list
- def setGCodeList(self, list):
+ def setGCodeList(self, list: List[str]) -> None:
self._gcode_list = list
+
+ def __deepcopy__(self, memo) -> "GCodeListDecorator":
+ copied_decorator = GCodeListDecorator()
+ copied_decorator.setGCodeList(self.getGCodeList())
+ return copied_decorator
diff --git a/cura/Scene/SliceableObjectDecorator.py b/cura/Scene/SliceableObjectDecorator.py
index 1cb589d9c6..982a38d667 100644
--- a/cura/Scene/SliceableObjectDecorator.py
+++ b/cura/Scene/SliceableObjectDecorator.py
@@ -2,11 +2,11 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
class SliceableObjectDecorator(SceneNodeDecorator):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- def isSliceable(self):
+ def isSliceable(self) -> bool:
return True
- def __deepcopy__(self, memo):
+ def __deepcopy__(self, memo) -> "SliceableObjectDecorator":
return type(self)()
diff --git a/cura/Scene/ZOffsetDecorator.py b/cura/Scene/ZOffsetDecorator.py
index d3ee5c8454..b35b17a412 100644
--- a/cura/Scene/ZOffsetDecorator.py
+++ b/cura/Scene/ZOffsetDecorator.py
@@ -1,18 +1,19 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
+
## A decorator that stores the amount an object has been moved below the platform.
class ZOffsetDecorator(SceneNodeDecorator):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- self._z_offset = 0
+ self._z_offset = 0.
- def setZOffset(self, offset):
+ def setZOffset(self, offset: float) -> None:
self._z_offset = offset
- def getZOffset(self):
+ def getZOffset(self) -> float:
return self._z_offset
- def __deepcopy__(self, memo):
+ def __deepcopy__(self, memo) -> "ZOffsetDecorator":
copied_decorator = ZOffsetDecorator()
copied_decorator.setZOffset(self.getZOffset())
return copied_decorator
diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py
index 2b8ff4a234..133e04e8fc 100644
--- a/cura/Settings/ContainerManager.py
+++ b/cura/Settings/ContainerManager.py
@@ -4,12 +4,12 @@
import os
import urllib.parse
import uuid
-from typing import Any
-from typing import Dict, Union, Optional
+from typing import Dict, Union, Any, TYPE_CHECKING, List
-from PyQt5.QtCore import QObject, QUrl, QVariant
+from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtWidgets import QMessageBox
+
from UM.i18n import i18nCatalog
from UM.FlameProfiler import pyqtSlot
from UM.Logger import Logger
@@ -21,6 +21,18 @@ from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
+
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
+ from cura.Machines.ContainerNode import ContainerNode
+ from cura.Machines.MaterialNode import MaterialNode
+ from cura.Machines.QualityChangesGroup import QualityChangesGroup
+ from UM.PluginRegistry import PluginRegistry
+ from cura.Settings.MachineManager import MachineManager
+ from cura.Machines.MaterialManager import MaterialManager
+ from cura.Machines.QualityManager import QualityManager
+ from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
+
catalog = i18nCatalog("cura")
@@ -31,20 +43,20 @@ catalog = i18nCatalog("cura")
# when a certain action happens. This can be done through this class.
class ContainerManager(QObject):
- def __init__(self, application):
+ def __init__(self, application: "CuraApplication") -> None:
if ContainerManager.__instance is not None:
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
ContainerManager.__instance = self
super().__init__(parent = application)
- self._application = application
- self._plugin_registry = self._application.getPluginRegistry()
- self._container_registry = self._application.getContainerRegistry()
- self._machine_manager = self._application.getMachineManager()
- self._material_manager = self._application.getMaterialManager()
- self._quality_manager = self._application.getQualityManager()
- self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
+ self._application = application # type: CuraApplication
+ self._plugin_registry = self._application.getPluginRegistry() # type: PluginRegistry
+ self._container_registry = self._application.getContainerRegistry() # type: CuraContainerRegistry
+ self._machine_manager = self._application.getMachineManager() # type: MachineManager
+ self._material_manager = self._application.getMaterialManager() # type: MaterialManager
+ self._quality_manager = self._application.getQualityManager() # type: QualityManager
+ self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
@pyqtSlot(str, str, result=str)
def getContainerMetaDataEntry(self, container_id: str, entry_names: str) -> str:
@@ -69,21 +81,23 @@ class ContainerManager(QObject):
# by using "/" as a separator. For example, to change an entry "foo" in a
# dictionary entry "bar", you can specify "bar/foo" as entry name.
#
- # \param container_id \type{str} The ID of the container to change.
+ # \param container_node \type{ContainerNode}
# \param entry_name \type{str} The name of the metadata entry to change.
# \param entry_value The new value of the entry.
#
- # \return True if successful, False if not.
# TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
# Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want?
@pyqtSlot("QVariant", str, str)
- def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
- root_material_id = container_node.metadata["base_file"]
+ def setContainerMetaDataEntry(self, container_node: "ContainerNode", entry_name: str, entry_value: str) -> bool:
+ root_material_id = container_node.getMetaDataEntry("base_file", "")
if self._container_registry.isReadOnly(root_material_id):
Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
return False
material_group = self._material_manager.getMaterialGroup(root_material_id)
+ if material_group is None:
+ Logger.log("w", "Unable to find material group for: %s.", root_material_id)
+ return False
entries = entry_name.split("/")
entry_name = entries.pop()
@@ -91,11 +105,11 @@ class ContainerManager(QObject):
sub_item_changed = False
if entries:
root_name = entries.pop(0)
- root = material_group.root_material_node.metadata.get(root_name)
+ root = material_group.root_material_node.getMetaDataEntry(root_name)
item = root
for _ in range(len(entries)):
- item = item.get(entries.pop(0), { })
+ item = item.get(entries.pop(0), {})
if item[entry_name] != entry_value:
sub_item_changed = True
@@ -109,9 +123,10 @@ class ContainerManager(QObject):
container.setMetaDataEntry(entry_name, entry_value)
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
container.metaDataChanged.emit(container)
+ return True
@pyqtSlot(str, result = str)
- def makeUniqueName(self, original_name):
+ def makeUniqueName(self, original_name: str) -> str:
return self._container_registry.uniqueName(original_name)
## Get a list of string that can be used as name filters for a Qt File Dialog
@@ -125,7 +140,7 @@ class ContainerManager(QObject):
#
# \return A string list with name filters.
@pyqtSlot(str, result = "QStringList")
- def getContainerNameFilters(self, type_name):
+ def getContainerNameFilters(self, type_name: str) -> List[str]:
if not self._container_name_filters:
self._updateContainerNameFilters()
@@ -257,7 +272,7 @@ class ContainerManager(QObject):
#
# \return \type{bool} True if successful, False if not.
@pyqtSlot(result = bool)
- def updateQualityChanges(self):
+ def updateQualityChanges(self) -> bool:
global_stack = self._machine_manager.activeMachine
if not global_stack:
return False
@@ -313,10 +328,10 @@ class ContainerManager(QObject):
# \param material_id \type{str} the id of the material for which to get the linked materials.
# \return \type{list} a list of names of materials with the same GUID
@pyqtSlot("QVariant", bool, result = "QStringList")
- def getLinkedMaterials(self, material_node, exclude_self = False):
- guid = material_node.metadata["GUID"]
+ def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False):
+ guid = material_node.getMetaDataEntry("GUID", "")
- self_root_material_id = material_node.metadata["base_file"]
+ self_root_material_id = material_node.getMetaDataEntry("base_file")
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
linked_material_names = []
@@ -324,15 +339,19 @@ class ContainerManager(QObject):
for material_group in material_group_list:
if exclude_self and material_group.name == self_root_material_id:
continue
- linked_material_names.append(material_group.root_material_node.metadata["name"])
+ linked_material_names.append(material_group.root_material_node.getMetaDataEntry("name", ""))
return linked_material_names
## Unlink a material from all other materials by creating a new GUID
# \param material_id \type{str} the id of the material to create a new GUID for.
@pyqtSlot("QVariant")
- def unlinkMaterial(self, material_node):
+ def unlinkMaterial(self, material_node: "MaterialNode") -> None:
# Get the material group
- material_group = self._material_manager.getMaterialGroup(material_node.metadata["base_file"])
+ material_group = self._material_manager.getMaterialGroup(material_node.getMetaDataEntry("base_file", ""))
+
+ if material_group is None:
+ Logger.log("w", "Unable to find material group for %s", material_node)
+ return
# Generate a new GUID
new_guid = str(uuid.uuid4())
@@ -344,7 +363,7 @@ class ContainerManager(QObject):
if container is not None:
container.setMetaDataEntry("GUID", new_guid)
- def _performMerge(self, merge_into, merge, clear_settings = True):
+ def _performMerge(self, merge_into: InstanceContainer, merge: InstanceContainer, clear_settings: bool = True) -> None:
if merge == merge_into:
return
@@ -372,7 +391,8 @@ class ContainerManager(QObject):
continue
mime_type = self._container_registry.getMimeTypeForContainer(container_type)
-
+ if mime_type is None:
+ continue
entry = {
"type": serialize_type,
"mime": mime_type,
@@ -399,17 +419,17 @@ class ContainerManager(QObject):
self._container_name_filters[name_filter] = entry
## Import single profile, file_url does not have to end with curaprofile
- @pyqtSlot(QUrl, result="QVariantMap")
- def importProfile(self, file_url):
+ @pyqtSlot(QUrl, result = "QVariantMap")
+ def importProfile(self, file_url: QUrl) -> Dict[str, str]:
if not file_url.isValid():
- return
+ return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + str(file_url)}
path = file_url.toLocalFile()
if not path:
- return
+ return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + str(file_url)}
return self._container_registry.importProfile(path)
@pyqtSlot(QObject, QUrl, str)
- def exportQualityChangesGroup(self, quality_changes_group, file_url: QUrl, file_type: str):
+ def exportQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", file_url: QUrl, file_type: str) -> None:
if not file_url.isValid():
return
path = file_url.toLocalFile()
diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py
index e1f50a157d..a9f79d63d3 100644
--- a/cura/Settings/CuraContainerRegistry.py
+++ b/cura/Settings/CuraContainerRegistry.py
@@ -5,12 +5,12 @@ import os
import re
import configparser
-from typing import cast, Optional
-
+from typing import cast, Dict, Optional
from PyQt5.QtWidgets import QMessageBox
from UM.Decorators import override
from UM.Settings.ContainerFormatError import ContainerFormatError
+from UM.Settings.Interfaces import ContainerInterface
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
@@ -28,7 +28,7 @@ from . import GlobalStack
import cura.CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
-from cura.ReaderWriters.ProfileReader import NoProfileException
+from cura.ReaderWriters.ProfileReader import NoProfileException, ProfileReader
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@@ -161,20 +161,20 @@ class CuraContainerRegistry(ContainerRegistry):
## Imports a profile from a file
#
- # \param file_name \type{str} the full path and filename of the profile to import
- # \return \type{Dict} dict with a 'status' key containing the string 'ok' or 'error', and a 'message' key
- # containing a message for the user
- def importProfile(self, file_name):
+ # \param file_name The full path and filename of the profile to import.
+ # \return Dict with a 'status' key containing the string 'ok' or 'error',
+ # and a 'message' key containing a message for the user.
+ def importProfile(self, file_name: str) -> Dict[str, str]:
Logger.log("d", "Attempting to import profile %s", file_name)
if not file_name:
- return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "Failed to import profile from {0}: {1}", file_name, "Invalid path")}
+ return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "Failed to import profile from {0}: {1}", file_name, "Invalid path")}
plugin_registry = PluginRegistry.getInstance()
extension = file_name.split(".")[-1]
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
- return
+ return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "Can't import profile from {0} before a printer is added.", file_name)}
machine_extruders = []
for position in sorted(global_stack.extruders):
@@ -183,15 +183,15 @@ class CuraContainerRegistry(ContainerRegistry):
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
if meta_data["profile_reader"][0]["extension"] != extension:
continue
- profile_reader = plugin_registry.getPluginObject(plugin_id)
+ profile_reader = cast(ProfileReader, plugin_registry.getPluginObject(plugin_id))
try:
profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader.
except NoProfileException:
- return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "No custom profile to import in file {0}", file_name)}
+ return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "No custom profile to import in file {0}", file_name)}
except Exception as e:
# Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e))
- return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "Failed to import profile from {0}: {1}", file_name, "\n" + str(e))}
+ return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "Failed to import profile from {0}:", file_name) + "\n" + str(e) + ""}
if profile_or_list:
# Ensure it is always a list of profiles
@@ -215,19 +215,19 @@ class CuraContainerRegistry(ContainerRegistry):
if not global_profile:
Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name)
return { "status": "error",
- "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "This profile {0} contains incorrect data, could not import it.", file_name)}
+ "message": catalog.i18nc("@info:status Don't translate the XML tags !", "This profile {0} contains incorrect data, could not import it.", file_name)}
profile_definition = global_profile.getMetaDataEntry("definition")
# Make sure we have a profile_definition in the file:
if profile_definition is None:
break
- machine_definition = self.findDefinitionContainers(id = profile_definition)
- if not machine_definition:
+ machine_definitions = self.findDefinitionContainers(id = profile_definition)
+ if not machine_definitions:
Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition)
return {"status": "error",
- "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "This profile {0} contains incorrect data, could not import it.", file_name)
+ "message": catalog.i18nc("@info:status Don't translate the XML tags !", "This profile {0} contains incorrect data, could not import it.", file_name)
}
- machine_definition = machine_definition[0]
+ machine_definition = machine_definitions[0]
# Get the expected machine definition.
# i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode...
@@ -238,7 +238,7 @@ class CuraContainerRegistry(ContainerRegistry):
if profile_definition != expected_machine_definition:
Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition)
return { "status": "error",
- "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)}
+ "message": catalog.i18nc("@info:status Don't translate the XML tags !", "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)}
# Fix the global quality profile's definition field in case it's not correct
global_profile.setMetaDataEntry("definition", expected_machine_definition)
@@ -267,19 +267,19 @@ class CuraContainerRegistry(ContainerRegistry):
profile.setMetaDataEntry("position", "0")
profile.setDirty(True)
if idx == 0:
- # move all per-extruder settings to the first extruder's quality_changes
+ # Move all per-extruder settings to the first extruder's quality_changes
for qc_setting_key in global_profile.getAllKeys():
- settable_per_extruder = global_stack.getProperty(qc_setting_key,
- "settable_per_extruder")
+ settable_per_extruder = global_stack.getProperty(qc_setting_key, "settable_per_extruder")
if settable_per_extruder:
setting_value = global_profile.getProperty(qc_setting_key, "value")
setting_definition = global_stack.getSettingDefinition(qc_setting_key)
- new_instance = SettingInstance(setting_definition, profile)
- new_instance.setProperty("value", setting_value)
- new_instance.resetState() # Ensure that the state is not seen as a user state.
- profile.addInstance(new_instance)
- profile.setDirty(True)
+ if setting_definition is not None:
+ new_instance = SettingInstance(setting_definition, profile)
+ new_instance.setProperty("value", setting_value)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ profile.addInstance(new_instance)
+ profile.setDirty(True)
global_profile.removeInstance(qc_setting_key, postpone_emit=True)
extruder_profiles.append(profile)
@@ -291,7 +291,7 @@ class CuraContainerRegistry(ContainerRegistry):
for profile_index, profile in enumerate(profile_or_list):
if profile_index == 0:
# This is assumed to be the global profile
- profile_id = (global_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
+ profile_id = (cast(ContainerInterface, global_stack.getBottom()).getId() + "_" + name_seed).lower().replace(" ", "_")
elif profile_index < len(machine_extruders) + 1:
# This is assumed to be an extruder profile
@@ -303,15 +303,15 @@ class CuraContainerRegistry(ContainerRegistry):
profile.setMetaDataEntry("position", extruder_position)
profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_")
- else: #More extruders in the imported file than in the machine.
- continue #Delete the additional profiles.
+ else: # More extruders in the imported file than in the machine.
+ continue # Delete the additional profiles.
result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition)
if result is not None:
return {"status": "error", "message": catalog.i18nc(
"@info:status Don't translate the XML tags or !",
- "Failed to import profile from {0}: {1}",
- file_name, result)}
+ "Failed to import profile from {0}:",
+ file_name) + " " + result + ""}
return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())}
@@ -386,30 +386,6 @@ class CuraContainerRegistry(ContainerRegistry):
result.append( (plugin_id, meta_data) )
return result
- ## Returns true if the current machine requires its own materials
- # \return True if the current machine requires its own materials
- def _machineHasOwnMaterials(self):
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack:
- return global_container_stack.getMetaDataEntry("has_materials", False)
- return False
-
- ## Gets the ID of the active material
- # \return the ID of the active material or the empty string
- def _activeMaterialId(self):
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack and global_container_stack.material:
- return global_container_stack.material.getId()
- return ""
-
- ## Returns true if the current machine requires its own quality profiles
- # \return true if the current machine requires its own quality profiles
- def _machineHasOwnQualities(self):
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack:
- return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False))
- return False
-
## Convert an "old-style" pure ContainerStack to either an Extruder or Global stack.
def _convertContainerStack(self, container):
assert type(container) == ContainerStack
@@ -521,7 +497,7 @@ class CuraContainerRegistry(ContainerRegistry):
user_container.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
if machine.userChanges:
- # for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes
+ # For the newly created extruder stack, we need to move all "per-extruder" settings to the user changes
# container to the extruder stack.
for user_setting_key in machine.userChanges.getAllKeys():
settable_per_extruder = machine.getProperty(user_setting_key, "settable_per_extruder")
@@ -583,7 +559,7 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_quality_changes_container.setMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
else:
- # if we still cannot find a quality changes container for the extruder, create a new one
+ # If we still cannot find a quality changes container for the extruder, create a new one
container_name = machine_quality_changes.getName()
container_id = self.uniqueName(extruder_stack.getId() + "_qc_" + container_name)
extruder_quality_changes_container = InstanceContainer(container_id, parent = application)
@@ -601,7 +577,7 @@ class CuraContainerRegistry(ContainerRegistry):
Logger.log("w", "Could not find quality_changes named [%s] for extruder [%s]",
machine_quality_changes.getName(), extruder_stack.getId())
else:
- # move all per-extruder settings to the extruder's quality changes
+ # Move all per-extruder settings to the extruder's quality changes
for qc_setting_key in machine_quality_changes.getAllKeys():
settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder")
if settable_per_extruder:
@@ -642,7 +618,7 @@ class CuraContainerRegistry(ContainerRegistry):
if qc_name not in qc_groups:
qc_groups[qc_name] = []
qc_groups[qc_name].append(qc)
- # try to find from the quality changes cura directory too
+ # Try to find from the quality changes cura directory too
quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName())
if quality_changes_container:
qc_groups[qc_name].append(quality_changes_container)
@@ -656,7 +632,7 @@ class CuraContainerRegistry(ContainerRegistry):
else:
qc_dict["global"] = qc
if qc_dict["global"] is not None and len(qc_dict["extruders"]) == 1:
- # move per-extruder settings
+ # Move per-extruder settings
for qc_setting_key in qc_dict["global"].getAllKeys():
settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder")
if settable_per_extruder:
@@ -686,21 +662,21 @@ class CuraContainerRegistry(ContainerRegistry):
if not os.path.isfile(file_path):
continue
- parser = configparser.ConfigParser(interpolation=None)
+ parser = configparser.ConfigParser(interpolation = None)
try:
parser.read([file_path])
except:
- # skip, it is not a valid stack file
+ # Skip, it is not a valid stack file
continue
if not parser.has_option("general", "name"):
continue
if parser["general"]["name"] == name:
- # load the container
+ # Load the container
container_id = os.path.basename(file_path).replace(".inst.cfg", "")
if self.findInstanceContainers(id = container_id):
- # this container is already in the registry, skip it
+ # This container is already in the registry, skip it
continue
instance_container = InstanceContainer(container_id)
@@ -734,8 +710,8 @@ class CuraContainerRegistry(ContainerRegistry):
else:
Logger.log("w", "Could not find machine {machine} for extruder {extruder}", machine = extruder_stack.getMetaDataEntry("machine"), extruder = extruder_stack.getId())
- #Override just for the type.
+ # Override just for the type.
@classmethod
@override(ContainerRegistry)
def getInstance(cls, *args, **kwargs) -> "CuraContainerRegistry":
- return cast(CuraContainerRegistry, super().getInstance(*args, **kwargs))
\ No newline at end of file
+ return cast(CuraContainerRegistry, super().getInstance(*args, **kwargs))
diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py
index c8d1d9e370..042b065226 100755
--- a/cura/Settings/CuraContainerStack.py
+++ b/cura/Settings/CuraContainerStack.py
@@ -145,13 +145,11 @@ class CuraContainerStack(ContainerStack):
def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
- ## Get the definition container.
- #
- # \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
- @pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged)
- def definition(self) -> DefinitionContainer:
+ def getDefinition(self) -> "DefinitionContainer":
return cast(DefinitionContainer, self._containers[_ContainerIndexes.Definition])
+ definition = pyqtProperty(QObject, fget = getDefinition, fset = setDefinition, notify = pyqtContainersChanged)
+
@override(ContainerStack)
def getBottom(self) -> "DefinitionContainer":
return self.definition
@@ -291,7 +289,7 @@ class CuraContainerStack(ContainerStack):
# Helper to make sure we emit a PyQt signal on container changes.
def _onContainersChanged(self, container: Any) -> None:
- self.pyqtContainersChanged.emit()
+ Application.getInstance().callLater(self.pyqtContainersChanged.emit)
# Helper that can be overridden to get the "machine" definition, that is, the definition that defines the machine
# and its properties rather than, for example, the extruder. Defaults to simply returning the definition property.
diff --git a/cura/Settings/CuraFormulaFunctions.py b/cura/Settings/CuraFormulaFunctions.py
new file mode 100644
index 0000000000..9ef80bd3d4
--- /dev/null
+++ b/cura/Settings/CuraFormulaFunctions.py
@@ -0,0 +1,135 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Any, List, Optional, TYPE_CHECKING
+
+from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
+from UM.Settings.SettingFunction import SettingFunction
+from UM.Logger import Logger
+
+if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
+ from cura.Settings.CuraContainerStack import CuraContainerStack
+
+
+#
+# This class contains all Cura-related custom functions that can be used in formulas. Some functions requires
+# information such as the currently active machine, so this is made into a class instead of standalone functions.
+#
+class CuraFormulaFunctions:
+
+ def __init__(self, application: "CuraApplication") -> None:
+ self._application = application
+
+ # ================
+ # Custom Functions
+ # ================
+
+ # Gets the default extruder position of the currently active machine.
+ def getDefaultExtruderPosition(self) -> str:
+ machine_manager = self._application.getMachineManager()
+ return machine_manager.defaultExtruderPosition
+
+ # Gets the given setting key from the given extruder position.
+ def getValueInExtruder(self, extruder_position: int, property_key: str,
+ context: Optional["PropertyEvaluationContext"] = None) -> Any:
+ machine_manager = self._application.getMachineManager()
+
+ if extruder_position == -1:
+ extruder_position = int(machine_manager.defaultExtruderPosition)
+
+ global_stack = machine_manager.activeMachine
+ try:
+ extruder_stack = global_stack.extruders[str(extruder_position)]
+ except KeyError:
+ Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available" % (property_key, extruder_position))
+ return None
+
+ value = extruder_stack.getRawProperty(property_key, "value", context = context)
+ if isinstance(value, SettingFunction):
+ value = value(extruder_stack, context = context)
+
+ return value
+
+ # Gets all extruder values as a list for the given property.
+ def getValuesInAllExtruders(self, property_key: str,
+ context: Optional["PropertyEvaluationContext"] = None) -> List[Any]:
+ machine_manager = self._application.getMachineManager()
+ extruder_manager = self._application.getExtruderManager()
+
+ global_stack = machine_manager.activeMachine
+
+ result = []
+ for extruder in extruder_manager.getActiveExtruderStacks():
+ if not extruder.isEnabled:
+ continue
+ # only include values from extruders that are "active" for the current machine instance
+ if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context):
+ continue
+
+ value = extruder.getRawProperty(property_key, "value", context = context)
+
+ if value is None:
+ continue
+
+ if isinstance(value, SettingFunction):
+ value = value(extruder, context = context)
+
+ result.append(value)
+
+ if not result:
+ result.append(global_stack.getProperty(property_key, "value", context = context))
+
+ return result
+
+ # Get the resolve value or value for a given key.
+ def getResolveOrValue(self, property_key: str, context: Optional["PropertyEvaluationContext"] = None) -> Any:
+ machine_manager = self._application.getMachineManager()
+
+ global_stack = machine_manager.activeMachine
+ resolved_value = global_stack.getProperty(property_key, "value", context = context)
+
+ return resolved_value
+
+ # Gets the default setting value from given extruder position. The default value is what excludes the values in
+ # the user_changes container.
+ def getDefaultValueInExtruder(self, extruder_position: int, property_key: str) -> Any:
+ machine_manager = self._application.getMachineManager()
+
+ global_stack = machine_manager.activeMachine
+ extruder_stack = global_stack.extruders[str(extruder_position)]
+
+ context = self.createContextForDefaultValueEvaluation(extruder_stack)
+
+ return self.getValueInExtruder(extruder_position, property_key, context = context)
+
+ # Gets all default setting values as a list from all extruders of the currently active machine.
+ # The default values are those excluding the values in the user_changes container.
+ def getDefaultValuesInAllExtruders(self, property_key: str) -> List[Any]:
+ machine_manager = self._application.getMachineManager()
+
+ global_stack = machine_manager.activeMachine
+
+ context = self.createContextForDefaultValueEvaluation(global_stack)
+
+ return self.getValuesInAllExtruders(property_key, context = context)
+
+ # Gets the resolve value or value for a given key without looking the first container (user container).
+ def getDefaultResolveOrValue(self, property_key: str) -> Any:
+ machine_manager = self._application.getMachineManager()
+
+ global_stack = machine_manager.activeMachine
+
+ context = self.createContextForDefaultValueEvaluation(global_stack)
+ return self.getResolveOrValue(property_key, context = context)
+
+ # Creates a context for evaluating default values (skip the user_changes container).
+ def createContextForDefaultValueEvaluation(self, source_stack: "CuraContainerStack") -> "PropertyEvaluationContext":
+ context = PropertyEvaluationContext(source_stack)
+ context.context["evaluate_from_container_index"] = 1 # skip the user settings container
+ context.context["override_operators"] = {
+ "extruderValue": self.getDefaultValueInExtruder,
+ "extruderValues": self.getDefaultValuesInAllExtruders,
+ "resolveOrValue": self.getDefaultResolveOrValue,
+ }
+ return context
diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py
index 12fe732e3e..c98c63f529 100644
--- a/cura/Settings/CuraStackBuilder.py
+++ b/cura/Settings/CuraStackBuilder.py
@@ -15,6 +15,7 @@ from .ExtruderStack import ExtruderStack
## Contains helper functions to create new machines.
class CuraStackBuilder:
+
## Create a new instance of a machine.
#
# \param name The name of the new machine.
@@ -26,7 +27,6 @@ class CuraStackBuilder:
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
variant_manager = application.getVariantManager()
- material_manager = application.getMaterialManager()
quality_manager = application.getQualityManager()
registry = application.getContainerRegistry()
@@ -46,16 +46,6 @@ class CuraStackBuilder:
if not global_variant_container:
global_variant_container = application.empty_variant_container
- # get variant container for extruders
- extruder_variant_container = application.empty_variant_container
- extruder_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.NOZZLE)
- extruder_variant_name = None
- if extruder_variant_node:
- extruder_variant_container = extruder_variant_node.getContainer()
- if not extruder_variant_container:
- extruder_variant_container = application.empty_variant_container
- extruder_variant_name = extruder_variant_container.getName()
-
generated_name = registry.createUniqueName("machine", "", name, machine_definition.getName())
# Make sure the new name does not collide with any definition or (quality) profile
# createUniqueName() only looks at other stacks, but not at definitions or quality profiles
@@ -74,34 +64,8 @@ class CuraStackBuilder:
# Create ExtruderStacks
extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains")
-
- for position, extruder_definition_id in extruder_dict.items():
- # Sanity check: make sure that the positions in the extruder definitions are same as in the machine
- # definition
- extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
- position_in_extruder_def = extruder_definition.getMetaDataEntry("position")
- if position_in_extruder_def != position:
- ConfigurationErrorMessage.getInstance().addFaultyContainers(extruder_definition_id)
- return None #Don't return any container stack then, not the rest of the extruders either.
-
- # get material container for extruders
- material_container = application.empty_material_container
- material_node = material_manager.getDefaultMaterial(new_global_stack, position, extruder_variant_name, extruder_definition = extruder_definition)
- if material_node and material_node.getContainer():
- material_container = material_node.getContainer()
-
- new_extruder_id = registry.uniqueName(extruder_definition_id)
- new_extruder = cls.createExtruderStack(
- new_extruder_id,
- extruder_definition = extruder_definition,
- machine_definition_id = definition_id,
- position = position,
- variant_container = extruder_variant_container,
- material_container = material_container,
- quality_container = application.empty_quality_container
- )
- new_extruder.setNextStack(new_global_stack)
- new_global_stack.addExtruder(new_extruder)
+ for position in extruder_dict:
+ cls.createExtruderStackWithDefaultSetup(new_global_stack, position)
for new_extruder in new_global_stack.extruders.values(): #Only register the extruders if we're sure that all of them are correct.
registry.addContainer(new_extruder)
@@ -136,19 +100,73 @@ class CuraStackBuilder:
return new_global_stack
+ ## Create a default Extruder Stack
+ #
+ # \param global_stack The global stack this extruder refers to.
+ # \param extruder_position The position of the current extruder.
+ @classmethod
+ def createExtruderStackWithDefaultSetup(cls, global_stack: "GlobalStack", extruder_position: int) -> None:
+ from cura.CuraApplication import CuraApplication
+ application = CuraApplication.getInstance()
+ variant_manager = application.getVariantManager()
+ material_manager = application.getMaterialManager()
+ registry = application.getContainerRegistry()
+
+ # get variant container for extruders
+ extruder_variant_container = application.empty_variant_container
+ extruder_variant_node = variant_manager.getDefaultVariantNode(global_stack.definition, VariantType.NOZZLE,
+ global_stack = global_stack)
+ extruder_variant_name = None
+ if extruder_variant_node:
+ extruder_variant_container = extruder_variant_node.getContainer()
+ if not extruder_variant_container:
+ extruder_variant_container = application.empty_variant_container
+ extruder_variant_name = extruder_variant_container.getName()
+
+ extruder_definition_dict = global_stack.getMetaDataEntry("machine_extruder_trains")
+ extruder_definition_id = extruder_definition_dict[str(extruder_position)]
+ extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
+
+ # get material container for extruders
+ material_container = application.empty_material_container
+ material_node = material_manager.getDefaultMaterial(global_stack, str(extruder_position), extruder_variant_name,
+ extruder_definition = extruder_definition)
+ if material_node and material_node.getContainer():
+ material_container = material_node.getContainer()
+
+ new_extruder_id = registry.uniqueName(extruder_definition_id)
+ new_extruder = cls.createExtruderStack(
+ new_extruder_id,
+ extruder_definition = extruder_definition,
+ machine_definition_id = global_stack.definition.getId(),
+ position = extruder_position,
+ variant_container = extruder_variant_container,
+ material_container = material_container,
+ quality_container = application.empty_quality_container
+ )
+ new_extruder.setNextStack(global_stack)
+
+ registry.addContainer(new_extruder)
+
## Create a new Extruder stack
#
# \param new_stack_id The ID of the new stack.
- # \param definition The definition to base the new stack on.
- # \param machine_definition_id The ID of the machine definition to use for
- # the user container.
- # \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
+ # \param extruder_definition The definition to base the new stack on.
+ # \param machine_definition_id The ID of the machine definition to use for the user container.
+ # \param position The position the extruder occupies in the machine.
+ # \param variant_container The variant selected for the current extruder.
+ # \param material_container The material selected for the current extruder.
+ # \param quality_container The quality selected for the current extruder.
#
- # \return A new Global stack instance with the specified parameters.
+ # \return A new Extruder stack instance with the specified parameters.
@classmethod
- def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
+ def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface,
+ machine_definition_id: str,
position: int,
- variant_container, material_container, quality_container) -> ExtruderStack:
+ variant_container: "InstanceContainer",
+ material_container: "InstanceContainer",
+ quality_container: "InstanceContainer") -> ExtruderStack:
+
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
@@ -157,7 +175,7 @@ class CuraStackBuilder:
stack.setName(extruder_definition.getName())
stack.setDefinition(extruder_definition)
- stack.setMetaDataEntry("position", position)
+ stack.setMetaDataEntry("position", str(position))
user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id,
is_global_stack = False)
@@ -183,9 +201,22 @@ class CuraStackBuilder:
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
#
# \return A new Global stack instance with the specified parameters.
+
+ ## Create a new Global stack
+ #
+ # \param new_stack_id The ID of the new stack.
+ # \param definition The definition to base the new stack on.
+ # \param variant_container The variant selected for the current stack.
+ # \param material_container The material selected for the current stack.
+ # \param quality_container The quality selected for the current stack.
+ #
+ # \return A new Global stack instance with the specified parameters.
@classmethod
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface,
- variant_container, material_container, quality_container) -> GlobalStack:
+ variant_container: "InstanceContainer",
+ material_container: "InstanceContainer",
+ quality_container: "InstanceContainer") -> GlobalStack:
+
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py
index e046082b5f..f8dccb4ba6 100755
--- a/cura/Settings/ExtruderManager.py
+++ b/cura/Settings/ExtruderManager.py
@@ -4,23 +4,20 @@
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
from UM.FlameProfiler import pyqtSlot
-import cura.CuraApplication #To get the global container stack to find the current machine.
+import cura.CuraApplication # To get the global container stack to find the current machine.
+from cura.Settings.GlobalStack import GlobalStack
from UM.Logger import Logger
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
-from UM.Settings.SettingFunction import SettingFunction
-from UM.Settings.SettingInstance import SettingInstance
from UM.Settings.ContainerStack import ContainerStack
-from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
-from typing import Optional, List, TYPE_CHECKING, Union, Dict
+from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union
if TYPE_CHECKING:
from cura.Settings.ExtruderStack import ExtruderStack
- from cura.Settings.GlobalStack import GlobalStack
## Manages all existing extruder stacks.
@@ -38,9 +35,13 @@ class ExtruderManager(QObject):
self._application = cura.CuraApplication.CuraApplication.getInstance()
- self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
+ # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
+ self._extruder_trains = {} # type: Dict[str, Dict[str, "ExtruderStack"]]
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
- self._selected_object_extruders = []
+
+ # TODO; I have no idea why this is a union of ID's and extruder stacks. This needs to be fixed at some point.
+ self._selected_object_extruders = [] # type: List[Union[str, "ExtruderStack"]]
+
self._addCurrentMachineExtruders()
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
@@ -62,52 +63,29 @@ class ExtruderManager(QObject):
if not self._application.getGlobalContainerStack():
return None # No active machine, so no active extruder.
try:
- return self._extruder_trains[self._application.getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId()
+ return self._extruder_trains[self._application.getGlobalContainerStack().getId()][str(self.activeExtruderIndex)].getId()
except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
return None
- ## Return extruder count according to extruder trains.
- @pyqtProperty(int, notify = extrudersChanged)
- def extruderCount(self):
- if not self._application.getGlobalContainerStack():
- return 0 # No active machine, so no extruders.
- try:
- return len(self._extruder_trains[self._application.getGlobalContainerStack().getId()])
- except KeyError:
- return 0
-
## Gets a dict with the extruder stack ids with the extruder number as the key.
@pyqtProperty("QVariantMap", notify = extrudersChanged)
def extruderIds(self) -> Dict[str, str]:
- extruder_stack_ids = {}
+ extruder_stack_ids = {} # type: Dict[str, str]
global_container_stack = self._application.getGlobalContainerStack()
if global_container_stack:
- global_stack_id = global_container_stack.getId()
-
- if global_stack_id in self._extruder_trains:
- for position in self._extruder_trains[global_stack_id]:
- extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId()
+ extruder_stack_ids = {position: extruder.id for position, extruder in global_container_stack.extruders.items()}
return extruder_stack_ids
- @pyqtSlot(str, result = str)
- def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str:
- global_container_stack = self._application.getGlobalContainerStack()
- if global_container_stack is not None:
- for position in self._extruder_trains[global_container_stack.getId()]:
- extruder = self._extruder_trains[global_container_stack.getId()][position]
- if extruder.getId() == extruder_stack_id:
- return extruder.qualityChanges.getId()
- return ""
-
## Changes the active extruder by index.
#
# \param index The index of the new active extruder.
@pyqtSlot(int)
def setActiveExtruderIndex(self, index: int) -> None:
- self._active_extruder_index = index
- self.activeExtruderChanged.emit()
+ if self._active_extruder_index != index:
+ self._active_extruder_index = index
+ self.activeExtruderChanged.emit()
@pyqtProperty(int, notify = activeExtruderChanged)
def activeExtruderIndex(self) -> int:
@@ -117,9 +95,9 @@ class ExtruderManager(QObject):
#
# \param index The index of the extruder whose name to get.
@pyqtSlot(int, result = str)
- def getExtruderName(self, index):
+ def getExtruderName(self, index: int) -> str:
try:
- return list(self.getActiveExtruderStacks())[index].getName()
+ return self.getActiveExtruderStacks()[index].getName()
except IndexError:
return ""
@@ -128,12 +106,12 @@ class ExtruderManager(QObject):
## Provides a list of extruder IDs used by the current selected objects.
@pyqtProperty("QVariantList", notify = selectedObjectExtrudersChanged)
- def selectedObjectExtruders(self) -> List[str]:
+ def selectedObjectExtruders(self) -> List[Union[str, "ExtruderStack"]]:
if not self._selected_object_extruders:
object_extruders = set()
# First, build a list of the actual selected objects (including children of groups, excluding group nodes)
- selected_nodes = []
+ selected_nodes = [] # type: List["SceneNode"]
for node in Selection.getAllSelectedObjects():
if node.callDecoration("isGroup"):
for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
@@ -145,16 +123,15 @@ class ExtruderManager(QObject):
selected_nodes.append(node)
# Then, figure out which nodes are used by those selected nodes.
- global_stack = self._application.getGlobalContainerStack()
- current_extruder_trains = self._extruder_trains.get(global_stack.getId())
+ current_extruder_trains = self.getActiveExtruderStacks()
for node in selected_nodes:
extruder = node.callDecoration("getActiveExtruder")
if extruder:
object_extruders.add(extruder)
elif current_extruder_trains:
- object_extruders.add(current_extruder_trains["0"].getId())
+ object_extruders.add(current_extruder_trains[0].getId())
- self._selected_object_extruders = list(object_extruders)
+ self._selected_object_extruders = list(object_extruders) # type: List[Union[str, "ExtruderStack"]]
return self._selected_object_extruders
@@ -163,19 +140,12 @@ class ExtruderManager(QObject):
# This will trigger a recalculation of the extruders used for the
# selection.
def resetSelectedObjectExtruders(self) -> None:
- self._selected_object_extruders = []
+ self._selected_object_extruders = [] # type: List[Union[str, "ExtruderStack"]]
self.selectedObjectExtrudersChanged.emit()
@pyqtSlot(result = QObject)
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
- global_container_stack = self._application.getGlobalContainerStack()
-
- if global_container_stack:
- if global_container_stack.getId() in self._extruder_trains:
- if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]:
- return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)]
-
- return None
+ return self.getExtruderStack(self.activeExtruderIndex)
## Get an extruder stack by index
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
@@ -186,16 +156,7 @@ class ExtruderManager(QObject):
return self._extruder_trains[global_container_stack.getId()][str(index)]
return None
- ## Get all extruder stacks
- def getExtruderStacks(self) -> List["ExtruderStack"]:
- result = []
- for i in range(self.extruderCount):
- stack = self.getExtruderStack(i)
- if stack:
- result.append(stack)
- return result
-
- def registerExtruder(self, extruder_train, machine_id):
+ def registerExtruder(self, extruder_train: "ExtruderStack", machine_id: str) -> None:
changed = False
if machine_id not in self._extruder_trains:
@@ -214,23 +175,20 @@ class ExtruderManager(QObject):
if changed:
self.extrudersChanged.emit(machine_id)
- def getAllExtruderValues(self, setting_key):
- return self.getAllExtruderSettings(setting_key, "value")
-
## Gets a property of a setting for all extruders.
#
# \param setting_key \type{str} The setting to get the property of.
# \param property \type{str} The property to get.
# \return \type{List} the list of results
- def getAllExtruderSettings(self, setting_key: str, prop: str):
+ def getAllExtruderSettings(self, setting_key: str, prop: str) -> List:
result = []
- for index in self.extruderIds:
- extruder_stack_id = self.extruderIds[str(index)]
- extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
+
+ for extruder_stack in self.getActiveExtruderStacks():
result.append(extruder_stack.getProperty(setting_key, prop))
+
return result
- def extruderValueWithDefault(self, value):
+ def extruderValueWithDefault(self, value: str) -> str:
machine_manager = self._application.getMachineManager()
if value == "-1":
return machine_manager.defaultExtruderPosition
@@ -306,7 +264,9 @@ class ExtruderManager(QObject):
used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_roof_extruder_nr", "value")))])
# The platform adhesion extruder. Not used if using none.
- if global_stack.getProperty("adhesion_type", "value") != "none":
+ if global_stack.getProperty("adhesion_type", "value") != "none" or (
+ global_stack.getProperty("prime_tower_brim_enable", "value") and
+ global_stack.getProperty("adhesion_type", "value") != 'raft'):
extruder_str_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
if extruder_str_nr == "-1":
extruder_str_nr = self._application.getMachineManager().defaultExtruderPosition
@@ -321,7 +281,7 @@ class ExtruderManager(QObject):
## Removes the container stack and user profile for the extruders for a specific machine.
#
# \param machine_id The machine to remove the extruders for.
- def removeMachineExtruders(self, machine_id: str):
+ def removeMachineExtruders(self, machine_id: str) -> None:
for extruder in self.getMachineExtruders(machine_id):
ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId())
ContainerRegistry.getInstance().removeContainer(extruder.getId())
@@ -331,24 +291,11 @@ class ExtruderManager(QObject):
## Returns extruders for a specific machine.
#
# \param machine_id The machine to get the extruders of.
- def getMachineExtruders(self, machine_id: str):
+ def getMachineExtruders(self, machine_id: str) -> List["ExtruderStack"]:
if machine_id not in self._extruder_trains:
return []
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
- ## Returns a list containing the global stack and active extruder stacks.
- #
- # The first element is the global container stack, followed by any extruder stacks.
- # \return \type{List[ContainerStack]}
- def getActiveGlobalAndExtruderStacks(self) -> Optional[List[Union["ExtruderStack", "GlobalStack"]]]:
- global_stack = self._application.getGlobalContainerStack()
- if not global_stack:
- return None
-
- result = [global_stack]
- result.extend(self.getActiveExtruderStacks())
- return result
-
## Returns the list of active extruder stacks, taking into account the machine extruder count.
#
# \return \type{List[ContainerStack]} a list of
@@ -356,15 +303,7 @@ class ExtruderManager(QObject):
global_stack = self._application.getGlobalContainerStack()
if not global_stack:
return []
-
- result = []
- if global_stack.getId() in self._extruder_trains:
- for extruder in sorted(self._extruder_trains[global_stack.getId()]):
- result.append(self._extruder_trains[global_stack.getId()][extruder])
-
- machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value")
-
- return result[:machine_extruder_count]
+ return global_stack.extruderList
def _globalContainerStackChanged(self) -> None:
# If the global container changed, the machine changed and might have extruders that were not registered yet
@@ -403,99 +342,37 @@ class ExtruderManager(QObject):
if extruders_changed:
self.extrudersChanged.emit(global_stack_id)
self.setActiveExtruderIndex(0)
+ self.activeExtruderChanged.emit()
# After 3.4, all single-extrusion machines have their own extruder definition files instead of reusing
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.
- def _fixSingleExtrusionMachineExtruderDefinition(self, global_stack):
+ def _fixSingleExtrusionMachineExtruderDefinition(self, global_stack: "GlobalStack") -> None:
+ container_registry = ContainerRegistry.getInstance()
expected_extruder_definition_0_id = global_stack.getMetaDataEntry("machine_extruder_trains")["0"]
- extruder_stack_0 = global_stack.extruders["0"]
- if extruder_stack_0.definition.getId() != expected_extruder_definition_0_id:
+ extruder_stack_0 = global_stack.extruders.get("0")
+ # At this point, extruder stacks for this machine may not have been loaded yet. In this case, need to look in
+ # the container registry as well.
+ if not global_stack.extruders:
+ extruder_trains = container_registry.findContainerStacks(type = "extruder_train",
+ machine = global_stack.getId())
+ if extruder_trains:
+ for extruder in extruder_trains:
+ if extruder.getMetaDataEntry("position") == "0":
+ extruder_stack_0 = extruder
+ break
+
+ if extruder_stack_0 is None:
+ Logger.log("i", "No extruder stack for global stack [%s], create one", global_stack.getId())
+ # Single extrusion machine without an ExtruderStack, create it
+ from cura.Settings.CuraStackBuilder import CuraStackBuilder
+ CuraStackBuilder.createExtruderStackWithDefaultSetup(global_stack, 0)
+
+ elif extruder_stack_0.definition.getId() != expected_extruder_definition_0_id:
Logger.log("e", "Single extruder printer [{printer}] expected extruder [{expected}], but got [{got}]. I'm making it [{expected}].".format(
printer = global_stack.getId(), expected = expected_extruder_definition_0_id, got = extruder_stack_0.definition.getId()))
- container_registry = ContainerRegistry.getInstance()
extruder_definition = container_registry.findDefinitionContainers(id = expected_extruder_definition_0_id)[0]
extruder_stack_0.definition = extruder_definition
- ## Get all extruder values for a certain setting.
- #
- # This is exposed to SettingFunction so it can be used in value functions.
- #
- # \param key The key of the setting to retrieve values for.
- #
- # \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
- # If no extruder has the value, the list will contain the global value.
- @staticmethod
- def getExtruderValues(key):
- global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
-
- result = []
- for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
- if not extruder.isEnabled:
- continue
- # only include values from extruders that are "active" for the current machine instance
- if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"):
- continue
-
- value = extruder.getRawProperty(key, "value")
-
- if value is None:
- continue
-
- if isinstance(value, SettingFunction):
- value = value(extruder)
-
- result.append(value)
-
- if not result:
- result.append(global_stack.getProperty(key, "value"))
-
- return result
-
- ## Get all extruder values for a certain setting. This function will skip the user settings container.
- #
- # This is exposed to SettingFunction so it can be used in value functions.
- #
- # \param key The key of the setting to retrieve values for.
- #
- # \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
- # If no extruder has the value, the list will contain the global value.
- @staticmethod
- def getDefaultExtruderValues(key):
- global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
- context = PropertyEvaluationContext(global_stack)
- context.context["evaluate_from_container_index"] = 1 # skip the user settings container
- context.context["override_operators"] = {
- "extruderValue": ExtruderManager.getDefaultExtruderValue,
- "extruderValues": ExtruderManager.getDefaultExtruderValues,
- "resolveOrValue": ExtruderManager.getDefaultResolveOrValue
- }
-
- result = []
- for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
- # only include values from extruders that are "active" for the current machine instance
- if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context):
- continue
-
- value = extruder.getRawProperty(key, "value", context = context)
-
- if value is None:
- continue
-
- if isinstance(value, SettingFunction):
- value = value(extruder, context = context)
-
- result.append(value)
-
- if not result:
- result.append(global_stack.getProperty(key, "value", context = context))
-
- return result
-
- ## Return the default extruder position from the machine manager
- @staticmethod
- def getDefaultExtruderPosition() -> str:
- return cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition
-
## Get all extruder values for a certain setting.
#
# This is exposed to qml for display purposes
@@ -504,62 +381,8 @@ class ExtruderManager(QObject):
#
# \return String representing the extruder values
@pyqtSlot(str, result="QVariant")
- def getInstanceExtruderValues(self, key):
- return ExtruderManager.getExtruderValues(key)
-
- ## Get the value for a setting from a specific extruder.
- #
- # This is exposed to SettingFunction to use in value functions.
- #
- # \param extruder_index The index of the extruder to get the value from.
- # \param key The key of the setting to get the value of.
- #
- # \return The value of the setting for the specified extruder or for the
- # global stack if not found.
- @staticmethod
- def getExtruderValue(extruder_index, key):
- if extruder_index == -1:
- extruder_index = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
- extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
-
- if extruder:
- value = extruder.getRawProperty(key, "value")
- if isinstance(value, SettingFunction):
- value = value(extruder)
- else:
- # Just a value from global.
- value = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().getProperty(key, "value")
-
- return value
-
- ## Get the default value from the given extruder. This function will skip the user settings container.
- #
- # This is exposed to SettingFunction to use in value functions.
- #
- # \param extruder_index The index of the extruder to get the value from.
- # \param key The key of the setting to get the value of.
- #
- # \return The value of the setting for the specified extruder or for the
- # global stack if not found.
- @staticmethod
- def getDefaultExtruderValue(extruder_index, key):
- extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
- context = PropertyEvaluationContext(extruder)
- context.context["evaluate_from_container_index"] = 1 # skip the user settings container
- context.context["override_operators"] = {
- "extruderValue": ExtruderManager.getDefaultExtruderValue,
- "extruderValues": ExtruderManager.getDefaultExtruderValues,
- "resolveOrValue": ExtruderManager.getDefaultResolveOrValue
- }
-
- if extruder:
- value = extruder.getRawProperty(key, "value", context = context)
- if isinstance(value, SettingFunction):
- value = value(extruder, context = context)
- else: # Just a value from global.
- value = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().getProperty(key, "value", context = context)
-
- return value
+ def getInstanceExtruderValues(self, key: str) -> List:
+ return self._application.getCuraFormulaFunctions().getValuesInAllExtruders(key)
## Get the resolve value or value for a given key
#
@@ -569,34 +392,12 @@ class ExtruderManager(QObject):
#
# \return The effective value
@staticmethod
- def getResolveOrValue(key):
- global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
+ def getResolveOrValue(key: str) -> Any:
+ global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack())
resolved_value = global_stack.getProperty(key, "value")
return resolved_value
- ## Get the resolve value or value for a given key without looking the first container (user container)
- #
- # This is the effective value for a given key, it is used for values in the global stack.
- # This is exposed to SettingFunction to use in value functions.
- # \param key The key of the setting to get the value of.
- #
- # \return The effective value
- @staticmethod
- def getDefaultResolveOrValue(key):
- global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
- context = PropertyEvaluationContext(global_stack)
- context.context["evaluate_from_container_index"] = 1 # skip the user settings container
- context.context["override_operators"] = {
- "extruderValue": ExtruderManager.getDefaultExtruderValue,
- "extruderValues": ExtruderManager.getDefaultExtruderValues,
- "resolveOrValue": ExtruderManager.getDefaultResolveOrValue
- }
-
- resolved_value = global_stack.getProperty(key, "value", context = context)
-
- return resolved_value
-
__instance = None # type: ExtruderManager
@classmethod
diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py
index ca687e358b..edb0e7d41f 100644
--- a/cura/Settings/ExtruderStack.py
+++ b/cura/Settings/ExtruderStack.py
@@ -52,8 +52,8 @@ class ExtruderStack(CuraContainerStack):
return super().getNextStack()
def setEnabled(self, enabled: bool) -> None:
- if "enabled" not in self._metadata:
- self.setMetaDataEntry("enabled", "True")
+ if self.getMetaDataEntry("enabled", True) == enabled: # No change.
+ return # Don't emit a signal then.
self.setMetaDataEntry("enabled", str(enabled))
self.enabledChanged.emit()
@@ -65,16 +65,33 @@ class ExtruderStack(CuraContainerStack):
def getLoadingPriority(cls) -> int:
return 3
+ compatibleMaterialDiameterChanged = pyqtSignal()
+
## Return the filament diameter that the machine requires.
#
# If the machine has no requirement for the diameter, -1 is returned.
# \return The filament diameter for the printer
- @property
- def materialDiameter(self) -> float:
+ def getCompatibleMaterialDiameter(self) -> float:
context = PropertyEvaluationContext(self)
context.context["evaluate_from_container_index"] = _ContainerIndexes.Variant
- return self.getProperty("material_diameter", "value", context = context)
+ return float(self.getProperty("material_diameter", "value", context = context))
+
+ def setCompatibleMaterialDiameter(self, value: float) -> None:
+ old_approximate_diameter = self.getApproximateMaterialDiameter()
+ if self.getCompatibleMaterialDiameter() != value:
+ self.definitionChanges.setProperty("material_diameter", "value", value)
+ self.compatibleMaterialDiameterChanged.emit()
+
+ # Emit approximate diameter changed signal if needed
+ if old_approximate_diameter != self.getApproximateMaterialDiameter():
+ self.approximateMaterialDiameterChanged.emit()
+
+ compatibleMaterialDiameter = pyqtProperty(float, fset = setCompatibleMaterialDiameter,
+ fget = getCompatibleMaterialDiameter,
+ notify = compatibleMaterialDiameterChanged)
+
+ approximateMaterialDiameterChanged = pyqtSignal()
## Return the approximate filament diameter that the machine requires.
#
@@ -84,9 +101,11 @@ class ExtruderStack(CuraContainerStack):
# If the machine has no requirement for the diameter, -1 is returned.
#
# \return The approximate filament diameter for the printer
- @pyqtProperty(float)
- def approximateMaterialDiameter(self) -> float:
- return round(float(self.materialDiameter))
+ def getApproximateMaterialDiameter(self) -> float:
+ return round(self.getCompatibleMaterialDiameter())
+
+ approximateMaterialDiameter = pyqtProperty(float, fget = getApproximateMaterialDiameter,
+ notify = approximateMaterialDiameterChanged)
## Overridden from ContainerStack
#
diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py
index f179dabd5a..afc2af94b3 100644
--- a/cura/Settings/ExtrudersModel.py
+++ b/cura/Settings/ExtrudersModel.py
@@ -1,7 +1,7 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, pyqtProperty, QTimer
+from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
from typing import Iterable
from UM.i18n import i18nCatalog
@@ -24,8 +24,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
## Human-readable name of the extruder.
NameRole = Qt.UserRole + 2
- ## Is the extruder enabled?
- EnabledRole = Qt.UserRole + 9
## Colour of the material loaded in the extruder.
ColorRole = Qt.UserRole + 3
@@ -47,6 +45,12 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
VariantRole = Qt.UserRole + 7
StackRole = Qt.UserRole + 8
+ MaterialBrandRole = Qt.UserRole + 9
+ ColorNameRole = Qt.UserRole + 10
+
+ ## Is the extruder enabled?
+ EnabledRole = Qt.UserRole + 11
+
## List of colours to display if there is no material or the material has no known
# colour.
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
@@ -67,14 +71,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
self.addRoleName(self.MaterialRole, "material")
self.addRoleName(self.VariantRole, "variant")
self.addRoleName(self.StackRole, "stack")
-
+ self.addRoleName(self.MaterialBrandRole, "material_brand")
+ self.addRoleName(self.ColorNameRole, "color_name")
self._update_extruder_timer = QTimer()
self._update_extruder_timer.setInterval(100)
self._update_extruder_timer.setSingleShot(True)
self._update_extruder_timer.timeout.connect(self.__updateExtruders)
- self._simple_names = False
-
self._active_machine_extruders = [] # type: Iterable[ExtruderStack]
self._add_optional_extruder = False
@@ -96,21 +99,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
def addOptionalExtruder(self):
return self._add_optional_extruder
- ## Set the simpleNames property.
- def setSimpleNames(self, simple_names):
- if simple_names != self._simple_names:
- self._simple_names = simple_names
- self.simpleNamesChanged.emit()
- self._updateExtruders()
-
- ## Emitted when the simpleNames property changes.
- simpleNamesChanged = pyqtSignal()
-
- ## Whether or not the model should show all definitions regardless of visibility.
- @pyqtProperty(bool, fset = setSimpleNames, notify = simpleNamesChanged)
- def simpleNames(self):
- return self._simple_names
-
## Links to the stack-changed signal of the new extruders when an extruder
# is swapped out or added in the current machine.
#
@@ -119,25 +107,28 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
# that signal. Application.globalContainerStackChanged doesn't fill this
# signal; it's assumed to be the current printer in that case.
def _extrudersChanged(self, machine_id = None):
+ machine_manager = Application.getInstance().getMachineManager()
if machine_id is not None:
- if Application.getInstance().getGlobalContainerStack() is None:
+ if machine_manager.activeMachine is None:
# No machine, don't need to update the current machine's extruders
return
- if machine_id != Application.getInstance().getGlobalContainerStack().getId():
+ if machine_id != machine_manager.activeMachine.getId():
# Not the current machine
return
# Unlink from old extruders
for extruder in self._active_machine_extruders:
extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged)
+ extruder.enabledChanged.disconnect(self._updateExtruders)
# Link to new extruders
self._active_machine_extruders = []
extruder_manager = Application.getInstance().getExtruderManager()
- for extruder in extruder_manager.getExtruderStacks():
+ for extruder in extruder_manager.getActiveExtruderStacks():
if extruder is None: #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML.
continue
extruder.containersChanged.connect(self._onExtruderStackContainersChanged)
+ extruder.enabledChanged.connect(self._updateExtruders)
self._active_machine_extruders.append(extruder)
self._updateExtruders() # Since the new extruders may have different properties, update our own model.
@@ -160,7 +151,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
def __updateExtruders(self):
extruders_changed = False
- if self.rowCount() != 0:
+ if self.count != 0:
extruders_changed = True
items = []
@@ -171,8 +162,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
# get machine extruder count for verification
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
- for extruder in Application.getInstance().getExtruderManager().getMachineExtruders(global_container_stack.getId()):
- position = extruder.getMetaDataEntry("position", default = "0") # Get the position
+ for extruder in Application.getInstance().getExtruderManager().getActiveExtruderStacks():
+ position = extruder.getMetaDataEntry("position", default = "0")
try:
position = int(position)
except ValueError:
@@ -183,7 +174,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
default_color = self.defaultColors[position] if 0 <= position < len(self.defaultColors) else self.defaultColors[0]
color = extruder.material.getMetaDataEntry("color_code", default = default_color) if extruder.material else default_color
-
+ material_brand = extruder.material.getMetaDataEntry("brand", default = "generic")
+ color_name = extruder.material.getMetaDataEntry("color_name")
# construct an item with only the relevant information
item = {
"id": extruder.getId(),
@@ -195,6 +187,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
"material": extruder.material.getName() if extruder.material else "",
"variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core
"stack": extruder,
+ "material_brand": material_brand,
+ "color_name": color_name
}
items.append(item)
@@ -213,9 +207,14 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
"enabled": True,
"color": "#ffffff",
"index": -1,
- "definition": ""
+ "definition": "",
+ "material": "",
+ "variant": "",
+ "stack": None,
+ "material_brand": "",
+ "color_name": "",
}
items.append(item)
-
- self.setItems(items)
- self.modelChanged.emit()
+ if self._items != items:
+ self.setItems(items)
+ self.modelChanged.emit()
diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py
index dda21f3719..3940af7ecc 100755
--- a/cura/Settings/GlobalStack.py
+++ b/cura/Settings/GlobalStack.py
@@ -3,8 +3,8 @@
from collections import defaultdict
import threading
-from typing import Any, Dict, Optional, Set, TYPE_CHECKING
-from PyQt5.QtCore import pyqtProperty
+from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List
+from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
@@ -13,6 +13,10 @@ from UM.Settings.SettingInstance import InstanceState
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import PropertyEvaluationContext
from UM.Logger import Logger
+from UM.Resources import Resources
+from UM.Platform import Platform
+from UM.Util import parseBool
+
import cura.CuraApplication
from . import Exceptions
@@ -21,6 +25,7 @@ from .CuraContainerStack import CuraContainerStack
if TYPE_CHECKING:
from cura.Settings.ExtruderStack import ExtruderStack
+
## Represents the Global or Machine stack and its related containers.
#
class GlobalStack(CuraContainerStack):
@@ -37,17 +42,63 @@ class GlobalStack(CuraContainerStack):
# Per thread we have our own resolving_settings, or strange things sometimes occur.
self._resolving_settings = defaultdict(set) #type: Dict[str, Set[str]] # keys are thread names
+ # Since the metadatachanged is defined in container stack, we can't use it here as a notifier for pyqt
+ # properties. So we need to tie them together like this.
+ self.metaDataChanged.connect(self.configuredConnectionTypesChanged)
+
+ extrudersChanged = pyqtSignal()
+ configuredConnectionTypesChanged = pyqtSignal()
+
## Get the list of extruders of this stack.
#
# \return The extruders registered with this stack.
- @pyqtProperty("QVariantMap")
+ @pyqtProperty("QVariantMap", notify = extrudersChanged)
def extruders(self) -> Dict[str, "ExtruderStack"]:
return self._extruders
+ @pyqtProperty("QVariantList", notify = extrudersChanged)
+ def extruderList(self) -> List["ExtruderStack"]:
+ result_tuple_list = sorted(list(self.extruders.items()), key=lambda x: int(x[0]))
+ result_list = [item[1] for item in result_tuple_list]
+
+ machine_extruder_count = self.getProperty("machine_extruder_count", "value")
+ return result_list[:machine_extruder_count]
+
@classmethod
def getLoadingPriority(cls) -> int:
return 2
+ ## The configured connection types can be used to find out if the global
+ # stack is configured to be connected with a printer, without having to
+ # know all the details as to how this is exactly done (and without
+ # actually setting the stack to be active).
+ #
+ # This data can then in turn also be used when the global stack is active;
+ # If we can't get a network connection, but it is configured to have one,
+ # we can display a different icon to indicate the difference.
+ @pyqtProperty("QVariantList", notify=configuredConnectionTypesChanged)
+ def configuredConnectionTypes(self) -> List[int]:
+ # Requesting it from the metadata actually gets them as strings (as that's what you get from serializing).
+ # But we do want them returned as a list of ints (so the rest of the code can directly compare)
+ connection_types = self.getMetaDataEntry("connection_type", "").split(",")
+ return [int(connection_type) for connection_type in connection_types if connection_type != ""]
+
+ ## \sa configuredConnectionTypes
+ def addConfiguredConnectionType(self, connection_type: int) -> None:
+ configured_connection_types = self.configuredConnectionTypes
+ if connection_type not in configured_connection_types:
+ # Store the values as a string.
+ configured_connection_types.append(connection_type)
+ self.setMetaDataEntry("connection_type", ",".join([str(c_type) for c_type in configured_connection_types]))
+
+ ## \sa configuredConnectionTypes
+ def removeConfiguredConnectionType(self, connection_type: int) -> None:
+ configured_connection_types = self.configuredConnectionTypes
+ if connection_type in self.configured_connection_types:
+ # Store the values as a string.
+ configured_connection_types.remove(connection_type)
+ self.setMetaDataEntry("connection_type", ",".join([str(c_type) for c_type in configured_connection_types]))
+
@classmethod
def getConfigurationTypeFromSerialized(cls, serialized: str) -> Optional[str]:
configuration_type = super().getConfigurationTypeFromSerialized(serialized)
@@ -61,6 +112,10 @@ class GlobalStack(CuraContainerStack):
name = self.variant.getName()
return name
+ @pyqtProperty(str, constant = True)
+ def preferred_output_file_formats(self) -> str:
+ return self.getMetaDataEntry("file_formats")
+
## Add an extruder to the list of extruders of this stack.
#
# \param extruder The extruder to add.
@@ -78,6 +133,7 @@ class GlobalStack(CuraContainerStack):
return
self._extruders[position] = extruder
+ self.extrudersChanged.emit()
Logger.log("i", "Extruder[%s] added to [%s] at position [%s]", extruder.id, self.id, position)
## Overridden from ContainerStack
@@ -184,6 +240,40 @@ class GlobalStack(CuraContainerStack):
def getHeadAndFansCoordinates(self):
return self.getProperty("machine_head_with_fans_polygon", "value")
+ def getHasMaterials(self) -> bool:
+ return parseBool(self.getMetaDataEntry("has_materials", False))
+
+ def getHasVariants(self) -> bool:
+ return parseBool(self.getMetaDataEntry("has_variants", False))
+
+ def getHasMachineQuality(self) -> bool:
+ return parseBool(self.getMetaDataEntry("has_machine_quality", False))
+
+ ## Get default firmware file name if one is specified in the firmware
+ @pyqtSlot(result = str)
+ def getDefaultFirmwareName(self) -> str:
+ machine_has_heated_bed = self.getProperty("machine_heated_bed", "value")
+
+ baudrate = 250000
+ if Platform.isLinux():
+ # Linux prefers a baudrate of 115200 here because older versions of
+ # pySerial did not support a baudrate of 250000
+ baudrate = 115200
+
+ # If a firmware file is available, it should be specified in the definition for the printer
+ hex_file = self.getMetaDataEntry("firmware_file", None)
+ if machine_has_heated_bed:
+ hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file)
+
+ if not hex_file:
+ Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id)
+ return ""
+
+ try:
+ return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
+ except FileNotFoundError:
+ Logger.log("w", "Firmware file %s not found.", hex_file)
+ return ""
## private:
global_stack_mime = MimeType(
diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py
index d65bbfddd9..3416f0a321 100755
--- a/cura/Settings/MachineManager.py
+++ b/cura/Settings/MachineManager.py
@@ -1,9 +1,10 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-import collections
import time
-from typing import Any, Callable, List, Dict, TYPE_CHECKING, Optional, cast
+import re
+import unicodedata
+from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@@ -20,15 +21,17 @@ from UM.Message import Message
from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique
-import cura.CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
-from cura.PrinterOutputDevice import PrinterOutputDevice
+from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionType
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
+from cura.Settings.cura_empty_instance_containers import (empty_definition_changes_container, empty_variant_container,
+ empty_material_container, empty_quality_container,
+ empty_quality_changes_container)
from .CuraStackBuilder import CuraStackBuilder
@@ -36,6 +39,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
if TYPE_CHECKING:
+ from cura.CuraApplication import CuraApplication
from cura.Settings.CuraContainerStack import CuraContainerStack
from cura.Settings.GlobalStack import GlobalStack
from cura.Machines.MaterialManager import MaterialManager
@@ -47,7 +51,7 @@ if TYPE_CHECKING:
class MachineManager(QObject):
- def __init__(self, parent: QObject = None) -> None:
+ def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent)
self._active_container_stack = None # type: Optional[ExtruderStack]
@@ -59,18 +63,17 @@ class MachineManager(QObject):
self._default_extruder_position = "0" # to be updated when extruders are switched on and off
- self.machine_extruder_material_update_dict = collections.defaultdict(list) #type: Dict[str, List[Callable[[], None]]]
-
- self._instance_container_timer = QTimer() #type: QTimer
+ self._instance_container_timer = QTimer() # type: QTimer
self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True)
self._instance_container_timer.timeout.connect(self.__emitChangedSignals)
- self._application = cura.CuraApplication.CuraApplication.getInstance() #type: cura.CuraApplication.CuraApplication
+ self._application = application
+ self._container_registry = self._application.getContainerRegistry()
self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
- self._application.getContainerRegistry().containerLoadComplete.connect(self._onContainersChanged)
+ self._container_registry.containerLoadComplete.connect(self._onContainersChanged)
- ## When the global container is changed, active material probably needs to be updated.
+ # When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged)
self.globalContainerChanged.connect(self.activeVariantChanged)
self.globalContainerChanged.connect(self.activeQualityChanged)
@@ -80,21 +83,16 @@ class MachineManager(QObject):
self._stacks_have_errors = None # type: Optional[bool]
- self._empty_container = CuraContainerRegistry.getInstance().getEmptyInstanceContainer() #type: InstanceContainer
- self._empty_definition_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] #type: InstanceContainer
- self._empty_variant_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] #type: InstanceContainer
- self._empty_material_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_material")[0] #type: InstanceContainer
- self._empty_quality_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] #type: InstanceContainer
- self._empty_quality_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] #type: InstanceContainer
-
self._onGlobalContainerChanged()
- ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
+ extruder_manager = self._application.getExtruderManager()
+
+ extruder_manager.activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
self._onActiveExtruderStackChanged()
- ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeMaterialChanged)
- ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeVariantChanged)
- ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeQualityChanged)
+ extruder_manager.activeExtruderChanged.connect(self.activeMaterialChanged)
+ extruder_manager.activeExtruderChanged.connect(self.activeVariantChanged)
+ extruder_manager.activeExtruderChanged.connect(self.activeQualityChanged)
self.globalContainerChanged.connect(self.activeStackChanged)
self.globalValueChanged.connect(self.activeStackValueChanged)
@@ -116,17 +114,13 @@ class MachineManager(QObject):
self._application.callLater(self.setInitialActiveMachine)
- self._material_incompatible_message = Message(catalog.i18nc("@info:status",
- "The selected material is incompatible with the selected machine or configuration."),
- title = catalog.i18nc("@info:title", "Incompatible Material")) #type: Message
-
- containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) #type: List[InstanceContainer]
+ containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) # type: List[InstanceContainer]
if containers:
containers[0].nameChanged.connect(self._onMaterialNameChanged)
- self._material_manager = self._application.getMaterialManager() #type: MaterialManager
- self._variant_manager = self._application.getVariantManager() #type: VariantManager
- self._quality_manager = self._application.getQualityManager() #type: QualityManager
+ self._material_manager = self._application.getMaterialManager() # type: MaterialManager
+ self._variant_manager = self._application.getVariantManager() # type: VariantManager
+ self._quality_manager = self._application.getQualityManager() # type: QualityManager
# When the materials lookup table gets updated, it can mean that a material has its name changed, which should
# be reflected on the GUI. This signal emission makes sure that it happens.
@@ -159,7 +153,7 @@ class MachineManager(QObject):
blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
outputDevicesChanged = pyqtSignal()
- currentConfigurationChanged = pyqtSignal() # Emitted every time the current configurations of the machine changes
+ currentConfigurationChanged = pyqtSignal() # Emitted every time the current configurations of the machine changes
printerConnectedStatusChanged = pyqtSignal() # Emitted every time the active machine change or the outputdevices change
rootMaterialChanged = pyqtSignal()
@@ -177,6 +171,7 @@ class MachineManager(QObject):
self._printer_output_devices.append(printer_output_device)
self.outputDevicesChanged.emit()
+ self.printerConnectedStatusChanged.emit()
@pyqtProperty(QObject, notify = currentConfigurationChanged)
def currentConfiguration(self) -> ConfigurationModel:
@@ -192,19 +187,21 @@ class MachineManager(QObject):
for extruder in self._global_container_stack.extruders.values():
extruder_configuration = ExtruderConfigurationModel()
# For compare just the GUID is needed at this moment
- mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != self._empty_material_container else None
- mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != self._empty_material_container else None
- mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != self._empty_material_container else None
- mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != self._empty_material_container else None
- mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != self._empty_material_container else None
+ mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != empty_material_container else None
+ mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != empty_material_container else None
+ mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != empty_material_container else None
+ mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != empty_material_container else None
+ mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != empty_material_container else None
material_model = MaterialOutputModel(mat_guid, mat_type, mat_color, mat_brand, mat_name)
extruder_configuration.position = int(extruder.getMetaDataEntry("position"))
extruder_configuration.material = material_model
- extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != self._empty_variant_container else None
+ extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != empty_variant_container else None
self._current_printer_configuration.extruderConfigurations.append(extruder_configuration)
- self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != self._empty_variant_container else None
+ # An empty build plate configuration from the network printer is presented as an empty string, so use "" for an
+ # empty build plate.
+ self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != empty_variant_container else ""
self.currentConfigurationChanged.emit()
@pyqtSlot(QObject, result = bool)
@@ -248,7 +245,7 @@ class MachineManager(QObject):
self.updateNumberExtrudersEnabled()
self.globalContainerChanged.emit()
- # after switching the global stack we reconnect all the signals and set the variant and material references
+ # After switching the global stack we reconnect all the signals and set the variant and material references
if self._global_container_stack:
self._application.getPreferences().setValue("cura/active_machine", self._global_container_stack.getId())
@@ -258,44 +255,33 @@ class MachineManager(QObject):
# Global stack can have only a variant if it is a buildplate
global_variant = self._global_container_stack.variant
- if global_variant != self._empty_variant_container:
+ if global_variant != empty_variant_container:
if global_variant.getMetaDataEntry("hardware_type") != "buildplate":
- self._global_container_stack.setVariant(self._empty_variant_container)
+ self._global_container_stack.setVariant(empty_variant_container)
- # set the global material to empty as we now use the extruder stack at all times - CURA-4482
+ # Set the global material to empty as we now use the extruder stack at all times - CURA-4482
global_material = self._global_container_stack.material
- if global_material != self._empty_material_container:
- self._global_container_stack.setMaterial(self._empty_material_container)
+ if global_material != empty_material_container:
+ self._global_container_stack.setMaterial(empty_material_container)
# Listen for changes on all extruder stacks
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
extruder_stack.containersChanged.connect(self._onContainersChanged)
- if self._global_container_stack.getId() in self.machine_extruder_material_update_dict:
- for func in self.machine_extruder_material_update_dict[self._global_container_stack.getId()]:
- self._application.callLater(func)
- del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
-
self.activeQualityGroupChanged.emit()
def _onActiveExtruderStackChanged(self) -> None:
self.blurSettings.emit() # Ensure no-one has focus.
- old_active_container_stack = self._active_container_stack
-
self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
- if old_active_container_stack != self._active_container_stack:
- # Many methods and properties related to the active quality actually depend
- # on _active_container_stack. If it changes, then the properties change.
- self.activeQualityChanged.emit()
-
def __emitChangedSignals(self) -> None:
self.activeQualityChanged.emit()
self.activeVariantChanged.emit()
self.activeMaterialChanged.emit()
self.rootMaterialChanged.emit()
+ self.numberExtrudersEnabledChanged.emit()
def _onContainersChanged(self, container: ContainerInterface) -> None:
self._instance_container_timer.start()
@@ -367,15 +353,21 @@ class MachineManager(QObject):
return
global_stack = containers[0]
+
+ # Make sure that the default machine actions for this machine have been added
+ self._application.getMachineActionManager().addDefaultMachineActions(global_stack)
+
+ ExtruderManager.getInstance()._fixSingleExtrusionMachineExtruderDefinition(global_stack)
if not global_stack.isValid():
# Mark global stack as invalid
ConfigurationErrorMessage.getInstance().addFaultyContainers(global_stack.getId())
return # We're done here
ExtruderManager.getInstance().setActiveExtruderIndex(0) # Switch to first extruder
+
self._global_container_stack = global_stack
self._application.setGlobalContainerStack(global_stack)
ExtruderManager.getInstance()._globalContainerStackChanged()
- self._initMachineState(containers[0])
+ self._initMachineState(global_stack)
self._onGlobalContainerChanged()
self.__emitChangedSignals()
@@ -385,7 +377,9 @@ class MachineManager(QObject):
# \param definition_id \type{str} definition id that needs to look for
# \param metadata_filter \type{dict} list of metadata keys and values used for filtering
@staticmethod
- def getMachine(definition_id: str, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]:
+ def getMachine(definition_id: str, metadata_filter: Optional[Dict[str, str]] = None) -> Optional["GlobalStack"]:
+ if metadata_filter is None:
+ metadata_filter = {}
machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
for machine in machines:
if machine.definition.getId() == definition_id:
@@ -412,8 +406,8 @@ class MachineManager(QObject):
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
- extruder_stacks = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
- count = 1 # we start with the global stack
+ extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
+ count = 1 # We start with the global stack
for stack in extruder_stacks:
md = stack.getMetaData()
if "position" in md and int(md["position"]) >= machine_extruder_count:
@@ -432,12 +426,12 @@ class MachineManager(QObject):
if not self._global_container_stack:
return False
- if self._global_container_stack.getTop().findInstances():
+ if self._global_container_stack.getTop().getNumInstances() != 0:
return True
- stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
+ stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
for stack in stacks:
- if stack.getTop().findInstances():
+ if stack.getTop().getNumInstances() != 0:
return True
return False
@@ -447,10 +441,10 @@ class MachineManager(QObject):
if not self._global_container_stack:
return 0
num_user_settings = 0
- num_user_settings += len(self._global_container_stack.getTop().findInstances())
- stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
+ num_user_settings += self._global_container_stack.getTop().getNumInstances()
+ stacks = self._global_container_stack.extruderList
for stack in stacks:
- num_user_settings += len(stack.getTop().findInstances())
+ num_user_settings += stack.getTop().getNumInstances()
return num_user_settings
## Delete a user setting from the global stack and all extruder stacks.
@@ -473,7 +467,7 @@ class MachineManager(QObject):
stack = ExtruderManager.getInstance().getActiveExtruderStack()
stacks = [stack]
else:
- stacks = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
+ stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
for stack in stacks:
if stack is not None:
@@ -500,7 +494,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = globalContainerChanged)
def activeMachineName(self) -> str:
if self._global_container_stack:
- return self._global_container_stack.getName()
+ return self._global_container_stack.getMetaDataEntry("group_name", self._global_container_stack.getName())
return ""
@pyqtProperty(str, notify = globalContainerChanged)
@@ -509,11 +503,57 @@ class MachineManager(QObject):
return self._global_container_stack.getId()
return ""
+ @pyqtProperty(str, notify = globalContainerChanged)
+ def activeMachineFirmwareVersion(self) -> str:
+ if not self._printer_output_devices:
+ return ""
+ return self._printer_output_devices[0].firmwareVersion
+
+ @pyqtProperty(str, notify = globalContainerChanged)
+ def activeMachineAddress(self) -> str:
+ if not self._printer_output_devices:
+ return ""
+ return self._printer_output_devices[0].address
+
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
- def printerConnected(self):
+ def printerConnected(self) -> bool:
return bool(self._printer_output_devices)
- @pyqtProperty(str, notify = printerConnectedStatusChanged)
+ @pyqtProperty(bool, notify = printerConnectedStatusChanged)
+ def activeMachineHasRemoteConnection(self) -> bool:
+ if self._global_container_stack:
+ has_remote_connection = False
+
+ for connection_type in self._global_container_stack.configuredConnectionTypes:
+ has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value,
+ ConnectionType.CloudConnection.value]
+ return has_remote_connection
+ return False
+
+ @pyqtProperty("QVariantList", notify=globalContainerChanged)
+ def activeMachineConfiguredConnectionTypes(self):
+ if self._global_container_stack:
+ return self._global_container_stack.configuredConnectionTypes
+ return []
+
+ @pyqtProperty(bool, notify = printerConnectedStatusChanged)
+ def activeMachineIsGroup(self) -> bool:
+ return bool(self._printer_output_devices) and len(self._printer_output_devices[0].printers) > 1
+
+ @pyqtProperty(bool, notify = printerConnectedStatusChanged)
+ def activeMachineHasNetworkConnection(self) -> bool:
+ # A network connection is only available if any output device is actually a network connected device.
+ return any(d.connectionType == ConnectionType.NetworkConnection for d in self._printer_output_devices)
+
+ @pyqtProperty(bool, notify = printerConnectedStatusChanged)
+ def activeMachineHasCloudConnection(self) -> bool:
+ # A cloud connection is only available if any output device actually is a cloud connected device.
+ return any(d.connectionType == ConnectionType.CloudConnection for d in self._printer_output_devices)
+
+ @pyqtProperty(bool, notify = printerConnectedStatusChanged)
+ def activeMachineIsUsingCloudConnection(self) -> bool:
+ return self.activeMachineHasCloudConnection and not self.activeMachineHasNetworkConnection
+
def activeMachineNetworkKey(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("um_network_key", "")
@@ -522,7 +562,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = printerConnectedStatusChanged)
def activeMachineNetworkGroupName(self) -> str:
if self._global_container_stack:
- return self._global_container_stack.getMetaDataEntry("connect_group_name", "")
+ return self._global_container_stack.getMetaDataEntry("group_name", "")
return ""
@pyqtProperty(QObject, notify = globalContainerChanged)
@@ -590,7 +630,7 @@ class MachineManager(QObject):
def globalVariantName(self) -> str:
if self._global_container_stack:
variant = self._global_container_stack.variant
- if variant and not isinstance(variant, type(self._empty_variant_container)):
+ if variant and not isinstance(variant, type(empty_variant_container)):
return variant.getName()
return ""
@@ -610,6 +650,14 @@ class MachineManager(QObject):
is_supported = self._current_quality_group.is_available
return is_supported
+ @pyqtProperty(bool, notify = activeQualityGroupChanged)
+ def isActiveQualityExperimental(self) -> bool:
+ is_experimental = False
+ if self._global_container_stack:
+ if self._current_quality_group:
+ is_experimental = self._current_quality_group.is_experimental
+ return is_experimental
+
## Returns whether there is anything unsupported in the current set-up.
#
# The current set-up signifies the global stack and all extruder stacks,
@@ -638,9 +686,9 @@ class MachineManager(QObject):
if self._active_container_stack is None or self._global_container_stack is None:
return
new_value = self._active_container_stack.getProperty(key, "value")
- extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
+ extruder_stacks = [stack for stack in ExtruderManager.getInstance().getActiveExtruderStacks()]
- # check in which stack the value has to be replaced
+ # Check in which stack the value has to be replaced
for extruder_stack in extruder_stacks:
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved
@@ -656,7 +704,7 @@ class MachineManager(QObject):
for key in self._active_container_stack.userChanges.getAllKeys():
new_value = self._active_container_stack.getProperty(key, "value")
- # check if the value has to be replaced
+ # Check if the value has to be replaced
extruder_stack.userChanges.setProperty(key, "value", new_value)
@pyqtProperty(str, notify = activeVariantChanged)
@@ -725,7 +773,7 @@ class MachineManager(QObject):
# If the machine that is being removed is the currently active machine, set another machine as the active machine.
activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id)
- # activate a new machine before removing a machine because this is safer
+ # Activate a new machine before removing a machine because this is safer
if activate_new_machine:
machine_stacks = CuraContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine")
other_machine_stacks = [s for s in machine_stacks if s["id"] != machine_id]
@@ -733,7 +781,7 @@ class MachineManager(QObject):
self.setActiveMachine(other_machine_stacks[0]["id"])
metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
- network_key = metadata["um_network_key"] if "um_network_key" in metadata else None
+ network_key = metadata.get("um_network_key", None)
ExtruderManager.getInstance().removeMachineExtruders(machine_id)
containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers:
@@ -778,7 +826,7 @@ class MachineManager(QObject):
if not stack.isEnabled:
continue
material_container = stack.material
- if material_container == self._empty_material_container:
+ if material_container == empty_material_container:
continue
if material_container.getMetaDataEntry("buildplate_compatible"):
buildplate_compatible = buildplate_compatible and material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName]
@@ -800,7 +848,7 @@ class MachineManager(QObject):
extruder_stacks = self._global_container_stack.extruders.values()
for stack in extruder_stacks:
material_container = stack.material
- if material_container == self._empty_material_container:
+ if material_container == empty_material_container:
continue
buildplate_compatible = material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_compatible") else True
buildplate_usable = material_container.getMetaDataEntry("buildplate_recommended")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_recommended") else True
@@ -858,7 +906,7 @@ class MachineManager(QObject):
caution_message = Message(catalog.i18nc(
"@info:generic",
"Settings have been changed to match the current availability of extruders: [%s]" % ", ".join(add_user_changes)),
- lifetime=0,
+ lifetime = 0,
title = catalog.i18nc("@info:title", "Settings updated"))
caution_message.show()
@@ -870,7 +918,7 @@ class MachineManager(QObject):
extruder_manager = self._application.getExtruderManager()
definition_changes_container = self._global_container_stack.definitionChanges
- if not self._global_container_stack or definition_changes_container == self._empty_definition_changes_container:
+ if not self._global_container_stack or definition_changes_container == empty_definition_changes_container:
return
previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
@@ -890,7 +938,11 @@ class MachineManager(QObject):
extruder_nr = node.callDecoration("getActiveExtruderPosition")
if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
- node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
+ extruder = extruder_manager.getExtruderStack(extruder_count - 1)
+ if extruder is not None:
+ node.callDecoration("setActiveExtruder", extruder.getId())
+ else:
+ Logger.log("w", "Could not find extruder to set active.")
# Make sure one of the extruder stacks is active
extruder_manager.setActiveExtruderIndex(0)
@@ -899,21 +951,18 @@ class MachineManager(QObject):
# After CURA-4482 this should not be the case anymore, but we still want to support older project files.
global_user_container = self._global_container_stack.userChanges
- # Make sure extruder_stacks exists
- extruder_stacks = [] #type: List[ExtruderStack]
-
- if previous_extruder_count == 1:
- extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
- global_user_container = self._global_container_stack.userChanges
-
for setting_instance in global_user_container.findInstances():
setting_key = setting_instance.definition.key
settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
if settable_per_extruder:
limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
- extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
- extruder_stack.userChanges.setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
+ extruder_position = max(0, limit_to_extruder)
+ extruder_stack = self.getExtruder(extruder_position)
+ if extruder_stack:
+ extruder_stack.userChanges.setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
+ else:
+ Logger.log("e", "Unable to find extruder on position %s", extruder_position)
global_user_container.removeInstance(setting_key)
# Signal that the global stack has changed
@@ -922,10 +971,9 @@ class MachineManager(QObject):
@pyqtSlot(int, result = QObject)
def getExtruder(self, position: int) -> Optional[ExtruderStack]:
- extruder = None
if self._global_container_stack:
- extruder = self._global_container_stack.extruders.get(str(position))
- return extruder
+ return self._global_container_stack.extruders.get(str(position))
+ return None
def updateDefaultExtruder(self) -> None:
if self._global_container_stack is None:
@@ -991,12 +1039,12 @@ class MachineManager(QObject):
if not enabled and position == ExtruderManager.getInstance().activeExtruderIndex:
ExtruderManager.getInstance().setActiveExtruderIndex(int(self._default_extruder_position))
- # ensure that the quality profile is compatible with current combination, or choose a compatible one if available
+ # Ensure that the quality profile is compatible with current combination, or choose a compatible one if available
self._updateQualityWithMaterial()
self.extruderChanged.emit()
- # update material compatibility color
+ # Update material compatibility color
self.activeQualityGroupChanged.emit()
- # update items in SettingExtruder
+ # Update items in SettingExtruder
ExtruderManager.getInstance().extrudersChanged.emit(self._global_container_stack.getId())
# Make sure the front end reflects changes
self.forceUpdateAllSettings()
@@ -1065,12 +1113,11 @@ class MachineManager(QObject):
for stack in active_stacks:
variant_container = stack.variant
position = stack.getMetaDataEntry("position")
- if variant_container and variant_container != self._empty_variant_container:
+ if variant_container and variant_container != empty_variant_container:
result[position] = variant_container.getName()
return result
- #
# Sets all quality and quality_changes containers to empty_quality and empty_quality_changes containers
# for all stacks in the currently active machine.
#
@@ -1079,11 +1126,11 @@ class MachineManager(QObject):
return
self._current_quality_group = None
self._current_quality_changes_group = None
- self._global_container_stack.quality = self._empty_quality_container
- self._global_container_stack.qualityChanges = self._empty_quality_changes_container
+ self._global_container_stack.quality = empty_quality_container
+ self._global_container_stack.qualityChanges = empty_quality_changes_container
for extruder in self._global_container_stack.extruders.values():
- extruder.quality = self._empty_quality_container
- extruder.qualityChanges = self._empty_quality_changes_container
+ extruder.quality = empty_quality_container
+ extruder.qualityChanges = empty_quality_changes_container
self.activeQualityGroupChanged.emit()
self.activeQualityChangesGroupChanged.emit()
@@ -1108,13 +1155,13 @@ class MachineManager(QObject):
# Set quality and quality_changes for the GlobalStack
self._global_container_stack.quality = quality_group.node_for_global.getContainer()
if empty_quality_changes:
- self._global_container_stack.qualityChanges = self._empty_quality_changes_container
+ self._global_container_stack.qualityChanges = empty_quality_changes_container
# Set quality and quality_changes for each ExtruderStack
for position, node in quality_group.nodes_for_extruders.items():
self._global_container_stack.extruders[str(position)].quality = node.getContainer()
if empty_quality_changes:
- self._global_container_stack.extruders[str(position)].qualityChanges = self._empty_quality_changes_container
+ self._global_container_stack.extruders[str(position)].qualityChanges = empty_quality_changes_container
self.activeQualityGroupChanged.emit()
self.activeQualityChangesGroupChanged.emit()
@@ -1129,7 +1176,7 @@ class MachineManager(QObject):
def _setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
if self._global_container_stack is None:
- return #Can't change that.
+ return # Can't change that.
quality_type = quality_changes_group.quality_type
# A custom quality can be created based on "not supported".
# In that case, do not set quality containers to empty.
@@ -1140,8 +1187,8 @@ class MachineManager(QObject):
if quality_group is None:
self._fixQualityChangesGroupToNotSupported(quality_changes_group)
- quality_changes_container = self._empty_quality_changes_container
- quality_container = self._empty_quality_container
+ quality_changes_container = empty_quality_changes_container
+ quality_container = empty_quality_container # type: Optional[InstanceContainer]
if quality_changes_group.node_for_global and quality_changes_group.node_for_global.getContainer():
quality_changes_container = cast(InstanceContainer, quality_changes_group.node_for_global.getContainer())
if quality_group is not None and quality_group.node_for_global and quality_group.node_for_global.getContainer():
@@ -1156,8 +1203,8 @@ class MachineManager(QObject):
if quality_group is not None:
quality_node = quality_group.nodes_for_extruders.get(position)
- quality_changes_container = self._empty_quality_changes_container
- quality_container = self._empty_quality_container
+ quality_changes_container = empty_quality_changes_container
+ quality_container = empty_quality_container
if quality_changes_node and quality_changes_node.getContainer():
quality_changes_container = cast(InstanceContainer, quality_changes_node.getContainer())
if quality_node and quality_node.getContainer():
@@ -1191,7 +1238,7 @@ class MachineManager(QObject):
self._global_container_stack.extruders[position].material = container_node.getContainer()
root_material_id = container_node.getMetaDataEntry("base_file", None)
else:
- self._global_container_stack.extruders[position].material = self._empty_material_container
+ self._global_container_stack.extruders[position].material = empty_material_container
root_material_id = None
# The _current_root_material_id is used in the MaterialMenu to see which material is selected
if root_material_id != self._current_root_material_id[position]:
@@ -1199,7 +1246,7 @@ class MachineManager(QObject):
self.rootMaterialChanged.emit()
def activeMaterialsCompatible(self) -> bool:
- # check material - variant compatibility
+ # Check material - variant compatibility
if self._global_container_stack is not None:
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
for position, extruder in self._global_container_stack.extruders.items():
@@ -1266,14 +1313,10 @@ class MachineManager(QObject):
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
current_nozzle_name = None
- if extruder.variant.getId() != self._empty_variant_container.getId():
+ if extruder.variant.getId() != empty_variant_container.getId():
current_nozzle_name = extruder.variant.getMetaDataEntry("name")
- from UM.Settings.Interfaces import PropertyEvaluationContext
- from cura.Settings.CuraContainerStack import _ContainerIndexes
- context = PropertyEvaluationContext(extruder)
- context.context["evaluate_from_container_index"] = _ContainerIndexes.DefinitionChanges
- material_diameter = extruder.getProperty("material_diameter", "value", context)
+ material_diameter = extruder.getCompatibleMaterialDiameter()
candidate_materials = self._material_manager.getAvailableMaterials(
self._global_container_stack.definition,
current_nozzle_name,
@@ -1304,17 +1347,18 @@ class MachineManager(QObject):
# Get the definition id corresponding to this machine name
machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
# Try to find a machine with the same network key
- new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey})
+ new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey()})
# If there is no machine, then create a new one and set it to the non-hidden instance
if not new_machine:
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
if not new_machine:
return
- new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey)
- new_machine.setMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName)
+ new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey())
+ new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName)
new_machine.setMetaDataEntry("hidden", False)
+ new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type"))
else:
- Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey)
+ Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey())
new_machine.setMetaDataEntry("hidden", False)
# Set the current printer instance to hidden (the metadata entry must exist)
@@ -1329,36 +1373,83 @@ class MachineManager(QObject):
self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self.switchPrinterType(configuration.printerType)
+
+ disabled_used_extruder_position_set = set()
+ extruders_to_disable = set()
+
+ # If an extruder that's currently used to print a model gets disabled due to the syncing, we need to show
+ # a message explaining why.
+ need_to_show_message = False
+
+ for extruder_configuration in configuration.extruderConfigurations:
+ extruder_has_hotend = extruder_configuration.hotendID != ""
+ extruder_has_material = extruder_configuration.material.guid != ""
+
+ # If the machine doesn't have a hotend or material, disable this extruder
+ if not extruder_has_hotend or not extruder_has_material:
+ extruders_to_disable.add(extruder_configuration.position)
+
+ # If there's no material and/or nozzle on the printer, enable the first extruder and disable the rest.
+ if len(extruders_to_disable) == len(self._global_container_stack.extruders):
+ extruders_to_disable.remove(min(extruders_to_disable))
+
for extruder_configuration in configuration.extruderConfigurations:
position = str(extruder_configuration.position)
- variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), extruder_configuration.hotendID)
- material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack,
- position,
- extruder_configuration.hotendID,
- configuration.buildplateConfiguration,
- extruder_configuration.material.guid)
- if variant_container_node:
- self._setVariantNode(position, variant_container_node)
- else:
- self._global_container_stack.extruders[position].variant = self._empty_variant_container
+ # If the machine doesn't have a hotend or material, disable this extruder
+ if int(position) in extruders_to_disable:
+ self._global_container_stack.extruders[position].setEnabled(False)
+
+ need_to_show_message = True
+ disabled_used_extruder_position_set.add(int(position))
- if material_container_node:
- self._setMaterial(position, material_container_node)
else:
- self._global_container_stack.extruders[position].material = self._empty_material_container
- self.updateMaterialWithVariant(position)
+ variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(),
+ extruder_configuration.hotendID)
+ material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack,
+ position,
+ extruder_configuration.hotendID,
+ configuration.buildplateConfiguration,
+ extruder_configuration.material.guid)
+ if variant_container_node:
+ self._setVariantNode(position, variant_container_node)
+ else:
+ self._global_container_stack.extruders[position].variant = empty_variant_container
+
+ if material_container_node:
+ self._setMaterial(position, material_container_node)
+ else:
+ self._global_container_stack.extruders[position].material = empty_material_container
+ self._global_container_stack.extruders[position].setEnabled(True)
+ self.updateMaterialWithVariant(position)
+
+ self.updateNumberExtrudersEnabled()
if configuration.buildplateConfiguration is not None:
global_variant_container_node = self._variant_manager.getBuildplateVariantNode(self._global_container_stack.definition.getId(), configuration.buildplateConfiguration)
if global_variant_container_node:
self._setGlobalVariant(global_variant_container_node)
else:
- self._global_container_stack.variant = self._empty_variant_container
+ self._global_container_stack.variant = empty_variant_container
else:
- self._global_container_stack.variant = self._empty_variant_container
+ self._global_container_stack.variant = empty_variant_container
self._updateQualityWithMaterial()
+ if need_to_show_message:
+ msg_str = "{extruders} is disabled because there is no material loaded. Please load a material or use custom configurations."
+
+ # Show human-readable extruder names such as "Extruder Left", "Extruder Front" instead of "Extruder 1, 2, 3".
+ extruder_names = []
+ for extruder_position in sorted(disabled_used_extruder_position_set):
+ extruder_stack = self._global_container_stack.extruders[str(extruder_position)]
+ extruder_name = extruder_stack.definition.getName()
+ extruder_names.append(extruder_name)
+ extruders_str = ", ".join(extruder_names)
+ msg_str = msg_str.format(extruders = extruders_str)
+ message = Message(catalog.i18nc("@info:status", msg_str),
+ title = catalog.i18nc("@info:title", "Extruder(s) Disabled"))
+ message.show()
+
# See if we need to show the Discard or Keep changes screen
if self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges()
@@ -1374,13 +1465,13 @@ class MachineManager(QObject):
# After updating from 3.2 to 3.3 some group names may be temporary. If there is a mismatch in the name of the group
# then all the container stacks are updated, both the current and the hidden ones.
def checkCorrectGroupName(self, device_id: str, group_name: str) -> None:
- if self._global_container_stack and device_id == self.activeMachineNetworkKey:
- # Check if the connect_group_name is correct. If not, update all the containers connected to the same printer
+ if self._global_container_stack and device_id == self.activeMachineNetworkKey():
+ # Check if the group_name is correct. If not, update all the containers connected to the same printer
if self.activeMachineNetworkGroupName != group_name:
- metadata_filter = {"um_network_key": self.activeMachineNetworkKey}
+ metadata_filter = {"um_network_key": self.activeMachineNetworkKey()}
containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
for container in containers:
- container.setMetaDataEntry("connect_group_name", group_name)
+ container.setMetaDataEntry("group_name", group_name)
## This method checks if there is an instance connected to the given network_key
def existNetworkInstances(self, network_key: str) -> bool:
@@ -1408,12 +1499,12 @@ class MachineManager(QObject):
position = str(position)
extruder_stack = self._global_container_stack.extruders[position]
nozzle_name = extruder_stack.variant.getName()
- material_diameter = extruder_stack.approximateMaterialDiameter
+ material_diameter = extruder_stack.getApproximateMaterialDiameter()
material_node = self._material_manager.getMaterialNode(machine_definition_id, nozzle_name, buildplate_name,
material_diameter, root_material_id)
self.setMaterial(position, material_node)
- ## global_stack: if you want to provide your own global_stack instead of the current active one
+ ## Global_stack: if you want to provide your own global_stack instead of the current active one
# if you update an active machine, special measures have to be taken.
@pyqtSlot(str, "QVariant")
def setMaterial(self, position: str, container_node, global_stack: Optional["GlobalStack"] = None) -> None:
@@ -1474,7 +1565,7 @@ class MachineManager(QObject):
# This is not changing the quality for the active machine !!!!!!!!
global_stack.quality = quality_group.node_for_global.getContainer()
for extruder_nr, extruder_stack in global_stack.extruders.items():
- quality_container = self._empty_quality_container
+ quality_container = empty_quality_container
if extruder_nr in quality_group.nodes_for_extruders:
container = quality_group.nodes_for_extruders[extruder_nr].getContainer()
quality_container = container if container is not None else quality_container
@@ -1516,18 +1607,45 @@ class MachineManager(QObject):
def activeQualityChangesGroup(self) -> Optional["QualityChangesGroup"]:
return self._current_quality_changes_group
+ @pyqtProperty(bool, notify = activeQualityChangesGroupChanged)
+ def hasCustomQuality(self) -> bool:
+ return self._current_quality_changes_group is not None
+
@pyqtProperty(str, notify = activeQualityGroupChanged)
def activeQualityOrQualityChangesName(self) -> str:
- name = self._empty_quality_container.getName()
+ name = empty_quality_container.getName()
if self._current_quality_changes_group:
name = self._current_quality_changes_group.name
elif self._current_quality_group:
name = self._current_quality_group.name
return name
+ @pyqtProperty(bool, notify = activeQualityGroupChanged)
+ def hasNotSupportedQuality(self) -> bool:
+ return self._current_quality_group is None and self._current_quality_changes_group is None
+
def _updateUponMaterialMetadataChange(self) -> None:
if self._global_container_stack is None:
return
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self.updateMaterialWithVariant(None)
self._updateQualityWithMaterial()
+
+ ## This function will translate any printer type name to an abbreviated printer type name
+ @pyqtSlot(str, result = str)
+ def getAbbreviatedMachineName(self, machine_type_name: str) -> str:
+ abbr_machine = ""
+ for word in re.findall(r"[\w']+", machine_type_name):
+ if word.lower() == "ultimaker":
+ abbr_machine += "UM"
+ elif word.isdigit():
+ abbr_machine += word
+ else:
+ stripped_word = "".join(char for char in unicodedata.normalize("NFD", word.upper()) if unicodedata.category(char) != "Mn")
+ # - use only the first character if the word is too long (> 3 characters)
+ # - use the whole word if it's not too long (<= 3 characters)
+ if len(stripped_word) > 3:
+ stripped_word = stripped_word[0]
+ abbr_machine += stripped_word
+
+ return abbr_machine
diff --git a/cura/Settings/MachineNameValidator.py b/cura/Settings/MachineNameValidator.py
index 19b29feac4..acdda4b0a0 100644
--- a/cura/Settings/MachineNameValidator.py
+++ b/cura/Settings/MachineNameValidator.py
@@ -35,10 +35,9 @@ class MachineNameValidator(QObject):
## Check if a specified machine name is allowed.
#
# \param name The machine name to check.
- # \param position The current position of the cursor in the text box.
# \return ``QValidator.Invalid`` if it's disallowed, or
# ``QValidator.Acceptable`` if it's allowed.
- def validate(self, name, position):
+ def validate(self, name):
#Check for file name length of the current settings container (which is the longest file we're saving with the name).
try:
filename_max_length = os.statvfs(Resources.getDataStoragePath()).f_namemax
@@ -54,7 +53,7 @@ class MachineNameValidator(QObject):
## Updates the validation state of a machine name text field.
@pyqtSlot(str)
def updateValidation(self, new_name):
- is_valid = self.validate(new_name, 0)
+ is_valid = self.validate(new_name)
if is_valid == QValidator.Acceptable:
self.validation_regex = "^.*$" #Matches anything.
else:
diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py
index 9cd24558b7..12b541c3d8 100644
--- a/cura/Settings/SettingInheritanceManager.py
+++ b/cura/Settings/SettingInheritanceManager.py
@@ -1,6 +1,6 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import List
+from typing import List, Optional, TYPE_CHECKING
from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
from UM.FlameProfiler import pyqtSlot
@@ -20,13 +20,18 @@ from UM.Settings.SettingInstance import InstanceState
from cura.Settings.ExtruderManager import ExtruderManager
+if TYPE_CHECKING:
+ from cura.Settings.ExtruderStack import ExtruderStack
+ from UM.Settings.SettingDefinition import SettingDefinition
+
+
class SettingInheritanceManager(QObject):
- def __init__(self, parent = None):
+ def __init__(self, parent = None) -> None:
super().__init__(parent)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
- self._global_container_stack = None
- self._settings_with_inheritance_warning = []
- self._active_container_stack = None
+ self._global_container_stack = None # type: Optional[ContainerStack]
+ self._settings_with_inheritance_warning = [] # type: List[str]
+ self._active_container_stack = None # type: Optional[ExtruderStack]
self._onGlobalContainerChanged()
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
@@ -41,7 +46,9 @@ class SettingInheritanceManager(QObject):
## Get the keys of all children settings with an override.
@pyqtSlot(str, result = "QStringList")
- def getChildrenKeysWithOverride(self, key):
+ def getChildrenKeysWithOverride(self, key: str) -> List[str]:
+ if self._global_container_stack is None:
+ return []
definitions = self._global_container_stack.definition.findDefinitions(key=key)
if not definitions:
Logger.log("w", "Could not find definition for key [%s]", key)
@@ -53,9 +60,11 @@ class SettingInheritanceManager(QObject):
return result
@pyqtSlot(str, str, result = "QStringList")
- def getOverridesForExtruder(self, key, extruder_index):
- result = []
+ def getOverridesForExtruder(self, key: str, extruder_index: str) -> List[str]:
+ if self._global_container_stack is None:
+ return []
+ result = [] # type: List[str]
extruder_stack = ExtruderManager.getInstance().getExtruderStack(extruder_index)
if not extruder_stack:
Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index)
@@ -73,16 +82,16 @@ class SettingInheritanceManager(QObject):
return result
@pyqtSlot(str)
- def manualRemoveOverride(self, key):
+ def manualRemoveOverride(self, key: str) -> None:
if key in self._settings_with_inheritance_warning:
self._settings_with_inheritance_warning.remove(key)
self.settingsWithIntheritanceChanged.emit()
@pyqtSlot()
- def forceUpdate(self):
+ def forceUpdate(self) -> None:
self._update()
- def _onActiveExtruderChanged(self):
+ def _onActiveExtruderChanged(self) -> None:
new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack()
if not new_active_stack:
self._active_container_stack = None
@@ -94,13 +103,14 @@ class SettingInheritanceManager(QObject):
self._active_container_stack.containersChanged.disconnect(self._onContainersChanged)
self._active_container_stack = new_active_stack
- self._active_container_stack.propertyChanged.connect(self._onPropertyChanged)
- self._active_container_stack.containersChanged.connect(self._onContainersChanged)
+ if self._active_container_stack is not None:
+ self._active_container_stack.propertyChanged.connect(self._onPropertyChanged)
+ self._active_container_stack.containersChanged.connect(self._onContainersChanged)
self._update() # Ensure that the settings_with_inheritance_warning list is populated.
- def _onPropertyChanged(self, key, property_name):
+ def _onPropertyChanged(self, key: str, property_name: str) -> None:
if (property_name == "value" or property_name == "enabled") and self._global_container_stack:
- definitions = self._global_container_stack.definition.findDefinitions(key = key)
+ definitions = self._global_container_stack.definition.findDefinitions(key = key) # type: List["SettingDefinition"]
if not definitions:
return
@@ -139,7 +149,7 @@ class SettingInheritanceManager(QObject):
if settings_with_inheritance_warning_changed:
self.settingsWithIntheritanceChanged.emit()
- def _recursiveCheck(self, definition):
+ def _recursiveCheck(self, definition: "SettingDefinition") -> bool:
for child in definition.children:
if child.key in self._settings_with_inheritance_warning:
return True
@@ -149,7 +159,7 @@ class SettingInheritanceManager(QObject):
return False
@pyqtProperty("QVariantList", notify = settingsWithIntheritanceChanged)
- def settingsWithInheritanceWarning(self):
+ def settingsWithInheritanceWarning(self) -> List[str]:
return self._settings_with_inheritance_warning
## Check if a setting has an inheritance function that is overwritten
@@ -157,9 +167,14 @@ class SettingInheritanceManager(QObject):
has_setting_function = False
if not stack:
stack = self._active_container_stack
- if not stack: #No active container stack yet!
+ if not stack: # No active container stack yet!
return False
- containers = [] # type: List[ContainerInterface]
+
+ if self._active_container_stack is None:
+ return False
+ all_keys = self._active_container_stack.getAllKeys()
+
+ containers = [] # type: List[ContainerInterface]
## Check if the setting has a user state. If not, it is never overwritten.
has_user_state = stack.getProperty(key, "state") == InstanceState.User
@@ -190,8 +205,8 @@ class SettingInheritanceManager(QObject):
has_setting_function = isinstance(value, SettingFunction)
if has_setting_function:
for setting_key in value.getUsedSettingKeys():
- if setting_key in self._active_container_stack.getAllKeys():
- break # We found an actual setting. So has_setting_function can remain true
+ if setting_key in all_keys:
+ break # We found an actual setting. So has_setting_function can remain true
else:
# All of the setting_keys turned out to not be setting keys at all!
# This can happen due enum keys also being marked as settings.
@@ -205,7 +220,7 @@ class SettingInheritanceManager(QObject):
break # There is a setting function somewhere, stop looking deeper.
return has_setting_function and has_non_function_value
- def _update(self):
+ def _update(self) -> None:
self._settings_with_inheritance_warning = [] # Reset previous data.
# Make sure that the GlobalStack is not None. sometimes the globalContainerChanged signal gets here late.
@@ -226,7 +241,7 @@ class SettingInheritanceManager(QObject):
# Notify others that things have changed.
self.settingsWithIntheritanceChanged.emit()
- def _onGlobalContainerChanged(self):
+ def _onGlobalContainerChanged(self) -> None:
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
self._global_container_stack.containersChanged.disconnect(self._onContainersChanged)
diff --git a/cura/Settings/SettingVisibilityPreset.py b/cura/Settings/SettingVisibilityPreset.py
new file mode 100644
index 0000000000..e8a4211d69
--- /dev/null
+++ b/cura/Settings/SettingVisibilityPreset.py
@@ -0,0 +1,90 @@
+import os
+import urllib.parse
+from configparser import ConfigParser
+from typing import List
+
+from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
+
+from UM.Logger import Logger
+from UM.MimeTypeDatabase import MimeTypeDatabase
+
+
+class SettingVisibilityPreset(QObject):
+ onSettingsChanged = pyqtSignal()
+ onNameChanged = pyqtSignal()
+ onWeightChanged = pyqtSignal()
+ onIdChanged = pyqtSignal()
+
+ def __init__(self, preset_id: str = "", name: str = "", weight: int = 0, parent = None) -> None:
+ super().__init__(parent)
+ self._settings = [] # type: List[str]
+ self._id = preset_id
+ self._weight = weight
+ self._name = name
+
+ @pyqtProperty("QStringList", notify = onSettingsChanged)
+ def settings(self) -> List[str]:
+ return self._settings
+
+ @pyqtProperty(str, notify = onIdChanged)
+ def presetId(self) -> str:
+ return self._id
+
+ @pyqtProperty(int, notify = onWeightChanged)
+ def weight(self) -> int:
+ return self._weight
+
+ @pyqtProperty(str, notify = onNameChanged)
+ def name(self) -> str:
+ return self._name
+
+ def setName(self, name: str) -> None:
+ if name != self._name:
+ self._name = name
+ self.onNameChanged.emit()
+
+ def setId(self, id: str) -> None:
+ if id != self._id:
+ self._id = id
+ self.onIdChanged.emit()
+
+ def setWeight(self, weight: int) -> None:
+ if weight != self._weight:
+ self._weight = weight
+ self.onWeightChanged.emit()
+
+ def setSettings(self, settings: List[str]) -> None:
+ if set(settings) != set(self._settings):
+ self._settings = list(set(settings)) # filter out non unique
+ self.onSettingsChanged.emit()
+
+ # Load a preset from file. We expect a file that can be parsed by means of the config parser.
+ # The sections indicate the categories and the parameters placed in it (which don't need values) are the settings
+ # that should be considered visible.
+ def loadFromFile(self, file_path: str) -> None:
+ mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
+
+ item_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_path)))
+ if not os.path.isfile(file_path):
+ Logger.log("e", "[%s] is not a file", file_path)
+ return None
+
+ parser = ConfigParser(interpolation = None, allow_no_value = True) # Accept options without any value,
+
+ parser.read([file_path])
+ if not parser.has_option("general", "name") or not parser.has_option("general", "weight"):
+ return None
+
+ settings = [] # type: List[str]
+ for section in parser.sections():
+ if section == "general":
+ continue
+
+ settings.append(section)
+ for option in parser[section].keys():
+ settings.append(option)
+ self.setSettings(settings)
+ self.setId(item_id)
+ self.setName(parser["general"]["name"])
+ self.setWeight(int(parser["general"]["weight"]))
+
diff --git a/cura/Settings/SidebarCustomMenuItemsModel.py b/cura/Settings/SidebarCustomMenuItemsModel.py
index ec926363f5..7177d26923 100644
--- a/cura/Settings/SidebarCustomMenuItemsModel.py
+++ b/cura/Settings/SidebarCustomMenuItemsModel.py
@@ -18,7 +18,7 @@ class SidebarCustomMenuItemsModel(ListModel):
self.addRoleName(self.name_role, "name")
self.addRoleName(self.actions_role, "actions")
self.addRoleName(self.menu_item_role, "menu_item")
- self.addRoleName(self.menu_item_icon_name_role, "iconName")
+ self.addRoleName(self.menu_item_icon_name_role, "icon_name")
self._updateExtensionList()
def _updateExtensionList(self)-> None:
diff --git a/cura/Settings/SimpleModeSettingsManager.py b/cura/Settings/SimpleModeSettingsManager.py
index fce43243bd..b1896a9205 100644
--- a/cura/Settings/SimpleModeSettingsManager.py
+++ b/cura/Settings/SimpleModeSettingsManager.py
@@ -1,7 +1,8 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Set
-from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
+from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
from UM.Application import Application
@@ -16,15 +17,11 @@ class SimpleModeSettingsManager(QObject):
self._is_profile_user_created = False # True when profile was custom created by user
self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized)
- self._machine_manager.activeQualityGroupChanged.connect(self._updateIsProfileUserCreated)
- self._machine_manager.activeQualityChangesGroupChanged.connect(self._updateIsProfileUserCreated)
# update on create as the activeQualityChanged signal is emitted before this manager is created when Cura starts
self._updateIsProfileCustomized()
- self._updateIsProfileUserCreated()
isProfileCustomizedChanged = pyqtSignal()
- isProfileUserCreatedChanged = pyqtSignal()
@pyqtProperty(bool, notify = isProfileCustomizedChanged)
def isProfileCustomized(self):
@@ -57,33 +54,6 @@ class SimpleModeSettingsManager(QObject):
self._is_profile_customized = has_customized_user_settings
self.isProfileCustomizedChanged.emit()
- @pyqtProperty(bool, notify = isProfileUserCreatedChanged)
- def isProfileUserCreated(self):
- return self._is_profile_user_created
-
- def _updateIsProfileUserCreated(self):
- quality_changes_keys = set()
-
- if not self._machine_manager.activeMachine:
- return False
-
- global_stack = self._machine_manager.activeMachine
-
- # check quality changes settings in the global stack
- quality_changes_keys.update(global_stack.qualityChanges.getAllKeys())
-
- # check quality changes settings in the extruder stacks
- if global_stack.extruders:
- for extruder_stack in global_stack.extruders.values():
- quality_changes_keys.update(extruder_stack.qualityChanges.getAllKeys())
-
- # check if the qualityChanges container is not empty (meaning it is a user created profile)
- has_quality_changes = len(quality_changes_keys) > 0
-
- if has_quality_changes != self._is_profile_user_created:
- self._is_profile_user_created = has_quality_changes
- self.isProfileUserCreatedChanged.emit()
-
# These are the settings included in the Simple ("Recommended") Mode, so only when the other settings have been
# changed, we consider it as a user customized profile in the Simple ("Recommended") Mode.
__ignored_custom_setting_keys = ["support_enable",
diff --git a/cura/Settings/UserChangesModel.py b/cura/Settings/UserChangesModel.py
index 93274d61c9..9a26e5607e 100644
--- a/cura/Settings/UserChangesModel.py
+++ b/cura/Settings/UserChangesModel.py
@@ -1,15 +1,17 @@
-from UM.Qt.ListModel import ListModel
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import os
+from collections import OrderedDict
from PyQt5.QtCore import pyqtSlot, Qt
+
from UM.Application import Application
-from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
from UM.Settings.SettingFunction import SettingFunction
-from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
-from collections import OrderedDict
-import os
+from UM.Qt.ListModel import ListModel
class UserChangesModel(ListModel):
@@ -38,12 +40,18 @@ class UserChangesModel(ListModel):
self._update()
def _update(self):
+ application = Application.getInstance()
+ machine_manager = application.getMachineManager()
+ cura_formula_functions = application.getCuraFormulaFunctions()
+
item_dict = OrderedDict()
item_list = []
- global_stack = Application.getInstance().getGlobalContainerStack()
+ global_stack = machine_manager.activeMachine
if not global_stack:
return
- stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
+
+ stacks = [global_stack]
+ stacks.extend(global_stack.extruders.values())
# Check if the definition container has a translation file and ensure it's loaded.
definition = global_stack.getBottom()
@@ -69,13 +77,7 @@ class UserChangesModel(ListModel):
# Override "getExtruderValue" with "getDefaultExtruderValue" so we can get the default values
user_changes = containers.pop(0)
- default_value_resolve_context = PropertyEvaluationContext(stack)
- default_value_resolve_context.context["evaluate_from_container_index"] = 1 # skip the user settings container
- default_value_resolve_context.context["override_operators"] = {
- "extruderValue": ExtruderManager.getDefaultExtruderValue,
- "extruderValues": ExtruderManager.getDefaultExtruderValues,
- "resolveOrValue": ExtruderManager.getDefaultResolveOrValue
- }
+ default_value_resolve_context = cura_formula_functions.createContextForDefaultValueEvaluation(stack)
for setting_key in user_changes.getAllKeys():
original_value = None
diff --git a/cura/Stages/CuraStage.py b/cura/Stages/CuraStage.py
index b2f6d61799..844b0d0768 100644
--- a/cura/Stages/CuraStage.py
+++ b/cura/Stages/CuraStage.py
@@ -1,23 +1,29 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+
from PyQt5.QtCore import pyqtProperty, QUrl
from UM.Stage import Stage
+# Since Cura has a few pre-defined "space claims" for the locations of certain components, we've provided some structure
+# to indicate this.
+# * The StageMenuComponent is the horizontal area below the stage bar. This should be used to show stage specific
+# buttons and elements. This component will be drawn over the bar & main component.
+# * The MainComponent is the component that will be drawn starting from the bottom of the stageBar and fills the rest
+# of the screen.
class CuraStage(Stage):
-
- def __init__(self, parent = None):
+ def __init__(self, parent = None) -> None:
super().__init__(parent)
@pyqtProperty(str, constant = True)
- def stageId(self):
+ def stageId(self) -> str:
return self.getPluginId()
@pyqtProperty(QUrl, constant = True)
- def mainComponent(self):
+ def mainComponent(self) -> QUrl:
return self.getDisplayComponent("main")
@pyqtProperty(QUrl, constant = True)
- def sidebarComponent(self):
- return self.getDisplayComponent("sidebar")
+ def stageMenuComponent(self) -> QUrl:
+ return self.getDisplayComponent("menu")
\ No newline at end of file
diff --git a/cura/UltimakerCloudAuthentication.py b/cura/UltimakerCloudAuthentication.py
new file mode 100644
index 0000000000..c8346e5c4e
--- /dev/null
+++ b/cura/UltimakerCloudAuthentication.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+# ---------
+# Constants used for the Cloud API
+# ---------
+DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str
+DEFAULT_CLOUD_API_VERSION = "1" # type: str
+DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str
+
+try:
+ from cura.CuraVersion import CuraCloudAPIRoot # type: ignore
+ if CuraCloudAPIRoot == "":
+ CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT
+except ImportError:
+ CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT
+
+try:
+ from cura.CuraVersion import CuraCloudAPIVersion # type: ignore
+ if CuraCloudAPIVersion == "":
+ CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION
+except ImportError:
+ CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION
+
+try:
+ from cura.CuraVersion import CuraCloudAccountAPIRoot # type: ignore
+ if CuraCloudAccountAPIRoot == "":
+ CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT
+except ImportError:
+ CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT
diff --git a/cura/Utils/Threading.py b/cura/Utils/Threading.py
index 3cd6200513..550a5421ff 100644
--- a/cura/Utils/Threading.py
+++ b/cura/Utils/Threading.py
@@ -1,3 +1,4 @@
+import functools
import threading
from cura.CuraApplication import CuraApplication
@@ -6,7 +7,7 @@ from cura.CuraApplication import CuraApplication
#
# HACK:
#
-# In project loading, when override the existing machine is selected, the stacks and containers that are correctly
+# In project loading, when override the existing machine is selected, the stacks and containers that are currently
# active in the system will be overridden at runtime. Because the project loading is done in a different thread than
# the Qt thread, something else can kick in the middle of the process. One of them is the rendering. It will access
# the current stacks and container, which have not completely been updated yet, so Cura will crash in this case.
@@ -22,7 +23,13 @@ class InterCallObject:
def call_on_qt_thread(func):
+ @functools.wraps(func)
def _call_on_qt_thread_wrapper(*args, **kwargs):
+ # If the current thread is the main thread, which is the Qt thread, directly call the function.
+ current_thread = threading.current_thread()
+ if isinstance(current_thread, threading._MainThread):
+ return func(*args, **kwargs)
+
def _handle_call(ico, *args, **kwargs):
ico.result = func(*args, **kwargs)
ico.finish_event.set()
diff --git a/cura_app.py b/cura_app.py
index 164e32e738..3224a5b99b 100755
--- a/cura_app.py
+++ b/cura_app.py
@@ -9,6 +9,7 @@ import os
import sys
from UM.Platform import Platform
+from cura.ApplicationMetadata import CuraAppName
parser = argparse.ArgumentParser(prog = "cura",
add_help = False)
@@ -17,22 +18,16 @@ parser.add_argument("--debug",
default = False,
help = "Turn on the debug mode by setting this option."
)
-parser.add_argument("--trigger-early-crash",
- dest = "trigger_early_crash",
- action = "store_true",
- default = False,
- help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog."
- )
known_args = vars(parser.parse_known_args()[0])
if not known_args["debug"]:
def get_cura_dir_path():
if Platform.isWindows():
- return os.path.expanduser("~/AppData/Roaming/cura")
+ return os.path.expanduser("~/AppData/Roaming/" + CuraAppName)
elif Platform.isLinux():
- return os.path.expanduser("~/.local/share/cura")
+ return os.path.expanduser("~/.local/share/" + CuraAppName)
elif Platform.isOSX():
- return os.path.expanduser("~/Library/Logs/cura")
+ return os.path.expanduser("~/Library/Logs/" + CuraAppName)
if hasattr(sys, "frozen"):
dirpath = get_cura_dir_path()
diff --git a/installer.nsi b/installer.nsi
deleted file mode 100644
index 7516f733a1..0000000000
--- a/installer.nsi
+++ /dev/null
@@ -1,156 +0,0 @@
-!ifndef VERSION
- !define VERSION '15.09.80'
-!endif
-
-; The name of the installer
-Name "Cura ${VERSION}"
-
-; The file to write
-OutFile "Cura_${VERSION}.exe"
-
-; The default installation directory
-InstallDir $PROGRAMFILES\Cura_${VERSION}
-
-; Registry key to check for directory (so if you install again, it will
-; overwrite the old one automatically)
-InstallDirRegKey HKLM "Software\Cura_${VERSION}" "Install_Dir"
-
-; Request application privileges for Windows Vista
-RequestExecutionLevel admin
-
-; Set the LZMA compressor to reduce size.
-SetCompressor /SOLID lzma
-;--------------------------------
-
-!include "MUI2.nsh"
-!include "Library.nsh"
-
-; !define MUI_ICON "dist/resources/cura.ico"
-!define MUI_BGCOLOR FFFFFF
-
-; Directory page defines
-!define MUI_DIRECTORYPAGE_VERIFYONLEAVE
-
-; Header
-; Don't show the component description box
-!define MUI_COMPONENTSPAGE_NODESC
-
-;Do not leave (Un)Installer page automaticly
-!define MUI_FINISHPAGE_NOAUTOCLOSE
-!define MUI_UNFINISHPAGE_NOAUTOCLOSE
-
-;Run Cura after installing
-!define MUI_FINISHPAGE_RUN
-!define MUI_FINISHPAGE_RUN_TEXT "Start Cura ${VERSION}"
-!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink"
-
-;Add an option to show release notes
-!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\plugins\ChangeLogPlugin\changelog.txt"
-
-; Pages
-;!insertmacro MUI_PAGE_WELCOME
-!insertmacro MUI_PAGE_DIRECTORY
-!insertmacro MUI_PAGE_COMPONENTS
-!insertmacro MUI_PAGE_INSTFILES
-!insertmacro MUI_PAGE_FINISH
-!insertmacro MUI_UNPAGE_CONFIRM
-!insertmacro MUI_UNPAGE_INSTFILES
-!insertmacro MUI_UNPAGE_FINISH
-
-; Languages
-!insertmacro MUI_LANGUAGE "English"
-
-; Reserve Files
-!insertmacro MUI_RESERVEFILE_LANGDLL
-ReserveFile '${NSISDIR}\Plugins\InstallOptions.dll'
-
-;--------------------------------
-
-; The stuff to install
-Section "Cura ${VERSION}"
-
- SectionIn RO
-
- ; Set output path to the installation directory.
- SetOutPath $INSTDIR
-
- ; Put file there
- File /r "dist\"
-
- ; Write the installation path into the registry
- WriteRegStr HKLM "SOFTWARE\Cura_${VERSION}" "Install_Dir" "$INSTDIR"
-
- ; Write the uninstall keys for Windows
- WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cura_${VERSION}" "DisplayName" "Cura ${VERSION}"
- WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cura_${VERSION}" "UninstallString" '"$INSTDIR\uninstall.exe"'
- WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cura_${VERSION}" "NoModify" 1
- WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cura_${VERSION}" "NoRepair" 1
- WriteUninstaller "uninstall.exe"
-
- ; Write start menu entries for all users
- SetShellVarContext all
-
- CreateDirectory "$SMPROGRAMS\Cura ${VERSION}"
- CreateShortCut "$SMPROGRAMS\Cura ${VERSION}\Uninstall Cura ${VERSION}.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0
- CreateShortCut "$SMPROGRAMS\Cura ${VERSION}\Cura ${VERSION}.lnk" "$INSTDIR\Cura.exe" '' "$INSTDIR\Cura.exe" 0
-
-SectionEnd
-
-Function LaunchLink
- ; Write start menu entries for all users
- SetShellVarContext all
- Exec '"$WINDIR\explorer.exe" "$SMPROGRAMS\Cura ${VERSION}\Cura ${VERSION}.lnk"'
-FunctionEnd
-
-Section "Install Visual Studio 2010 Redistributable"
- SetOutPath "$INSTDIR"
- File "vcredist_2010_20110908_x86.exe"
-
- IfSilent +2
- ExecWait '"$INSTDIR\vcredist_2010_20110908_x86.exe" /q /norestart'
-
-SectionEnd
-
-Section "Install Arduino Drivers"
- ; Set output path to the driver directory.
- SetOutPath "$INSTDIR\drivers\"
- File /r "drivers\"
-
- ${If} ${RunningX64}
- IfSilent +2
- ExecWait '"$INSTDIR\drivers\dpinst64.exe" /lm'
- ${Else}
- IfSilent +2
- ExecWait '"$INSTDIR\drivers\dpinst32.exe" /lm'
- ${EndIf}
-SectionEnd
-
-Section "Open STL files with Cura"
- ${registerExtension} "$INSTDIR\Cura.exe" ".stl" "STL_File"
-SectionEnd
-
-Section /o "Open OBJ files with Cura"
- WriteRegStr HKCR .obj "" "Cura OBJ model file"
- DeleteRegValue HKCR .obj "Content Type"
- WriteRegStr HKCR "Cura OBJ model file\DefaultIcon" "" "$INSTDIR\Cura.exe,0"
- WriteRegStr HKCR "Cura OBJ model file\shell" "" "open"
- WriteRegStr HKCR "Cura OBJ model file\shell\open\command" "" '"$INSTDIR\Cura.exe" "%1"'
-SectionEnd
-
-;--------------------------------
-
-; Uninstaller
-
-Section "Uninstall"
-
- ; Remove registry keys
- DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cura_${VERSION}"
- DeleteRegKey HKLM "SOFTWARE\Cura_${VERSION}"
-
- ; Write start menu entries for all users
- SetShellVarContext all
- ; Remove directories used
- RMDir /r "$SMPROGRAMS\Cura ${VERSION}"
- RMDir /r "$INSTDIR"
-
-SectionEnd
diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py
index 9ba82364e8..49c6995d18 100755
--- a/plugins/3MFReader/ThreeMFReader.py
+++ b/plugins/3MFReader/ThreeMFReader.py
@@ -225,7 +225,7 @@ class ThreeMFReader(MeshReader):
except Exception:
Logger.logException("e", "An exception occurred in 3mf reader.")
- return []
+ return None
return result
diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py
index 16ab22473b..bf190f7e39 100755
--- a/plugins/3MFReader/ThreeMFWorkspaceReader.py
+++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py
@@ -85,14 +85,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
def __init__(self) -> None:
super().__init__()
- MimeTypeDatabase.addMimeType(
- MimeType(
- name="application/x-curaproject+xml",
- comment="Cura Project File",
- suffixes=["curaproject.3mf"]
- )
- )
-
self._supported_extensions = [".3mf"]
self._dialog = WorkspaceDialog()
self._3mf_mesh_reader = None
@@ -306,7 +298,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
values = parser["values"] if parser.has_section("values") else dict()
num_settings_overriden_by_quality_changes += len(values)
# Check if quality changes already exists.
- quality_changes = self._container_registry.findInstanceContainers(id = container_id)
+ quality_changes = self._container_registry.findInstanceContainers(name = custom_quality_name,
+ type = "quality_changes")
if quality_changes:
containers_found_dict["quality_changes"] = True
# Check if there really is a conflict by comparing the values
@@ -507,7 +500,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
is_printer_group = False
if machine_conflict:
- group_name = existing_global_stack.getMetaDataEntry("connect_group_name")
+ group_name = existing_global_stack.getMetaDataEntry("group_name")
if group_name is not None:
is_printer_group = True
machine_name = group_name
@@ -726,8 +719,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
nodes = []
base_file_name = os.path.basename(file_name)
- if base_file_name.endswith(".curaproject.3mf"):
- base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
self.setWorkspaceName(base_file_name)
return nodes
@@ -803,7 +794,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Clear all existing containers
quality_changes_info.global_info.container.clear()
for container_info in quality_changes_info.extruder_info_dict.values():
- container_info.container.clear()
+ if container_info.container:
+ container_info.container.clear()
# Loop over everything and override the existing containers
global_info = quality_changes_info.global_info
@@ -936,7 +928,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
build_plate_id = global_stack.variant.getId()
# get material diameter of this extruder
- machine_material_diameter = extruder_stack.materialDiameter
+ machine_material_diameter = extruder_stack.getCompatibleMaterialDiameter()
material_node = material_manager.getMaterialNode(global_stack.definition.getId(),
extruder_stack.variant.getName(),
build_plate_id,
@@ -944,7 +936,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
root_material_id)
if material_node is not None and material_node.getContainer() is not None:
- extruder_stack.material = material_node.getContainer()
+ extruder_stack.material = material_node.getContainer() # type: InstanceContainer
def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
# Clear all first
@@ -1022,7 +1014,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
## Get the list of ID's of all containers in a container stack by partially parsing it's serialized data.
def _getContainerIdListFromSerialized(self, serialized):
- parser = ConfigParser(interpolation=None, empty_lines_in_values=False)
+ parser = ConfigParser(interpolation = None, empty_lines_in_values = False)
parser.read_string(serialized)
container_ids = []
@@ -1043,7 +1035,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return container_ids
def _getMachineNameFromSerializedStack(self, serialized):
- parser = ConfigParser(interpolation=None, empty_lines_in_values=False)
+ parser = ConfigParser(interpolation = None, empty_lines_in_values = False)
parser.read_string(serialized)
return parser["general"].get("name", "")
diff --git a/plugins/3MFReader/__init__.py b/plugins/3MFReader/__init__.py
index 3a4fde4ab8..ce94bbe69c 100644
--- a/plugins/3MFReader/__init__.py
+++ b/plugins/3MFReader/__init__.py
@@ -18,11 +18,7 @@ catalog = i18nCatalog("cura")
def getMetaData() -> Dict:
- # Workaround for osx not supporting double file extensions correctly.
- if Platform.isOSX():
- workspace_extension = "3mf"
- else:
- workspace_extension = "curaproject.3mf"
+ workspace_extension = "3mf"
metaData = {}
if "3MFReader.ThreeMFReader" in sys.modules:
diff --git a/plugins/3MFReader/plugin.json b/plugins/3MFReader/plugin.json
index 5d15123017..5af21a7033 100644
--- a/plugins/3MFReader/plugin.json
+++ b/plugins/3MFReader/plugin.json
@@ -1,8 +1,8 @@
{
"name": "3MF Reader",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides support for reading 3MF files.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/3MFWriter/__init__.py b/plugins/3MFWriter/__init__.py
index e779628f7e..eff1648489 100644
--- a/plugins/3MFWriter/__init__.py
+++ b/plugins/3MFWriter/__init__.py
@@ -12,14 +12,10 @@ from . import ThreeMFWorkspaceWriter
from UM.i18n import i18nCatalog
from UM.Platform import Platform
-i18n_catalog = i18nCatalog("uranium")
+i18n_catalog = i18nCatalog("cura")
def getMetaData():
- # Workarround for osx not supporting double file extensions correctly.
- if Platform.isOSX():
- workspace_extension = "3mf"
- else:
- workspace_extension = "curaproject.3mf"
+ workspace_extension = "3mf"
metaData = {}
@@ -36,7 +32,7 @@ def getMetaData():
"output": [{
"extension": workspace_extension,
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
- "mime_type": "application/x-curaproject+xml",
+ "mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
}]
}
diff --git a/plugins/3MFWriter/plugin.json b/plugins/3MFWriter/plugin.json
index 22d18b2cf1..3820ebd2e7 100644
--- a/plugins/3MFWriter/plugin.json
+++ b/plugins/3MFWriter/plugin.json
@@ -1,8 +1,8 @@
{
"name": "3MF Writer",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides support for writing 3MF files.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/ChangeLogPlugin/ChangeLog.py b/plugins/ChangeLogPlugin/ChangeLog.py
index 723c83a021..eeec5edf9b 100644
--- a/plugins/ChangeLogPlugin/ChangeLog.py
+++ b/plugins/ChangeLogPlugin/ChangeLog.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.i18n import i18nCatalog
@@ -29,6 +29,7 @@ class ChangeLog(Extension, QObject,):
self._change_logs = None
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
Application.getInstance().getPreferences().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium
+ self.setMenuName(catalog.i18nc("@item:inmenu", "Changelog"))
self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog)
def getChangeLogs(self):
diff --git a/plugins/ChangeLogPlugin/ChangeLog.txt b/plugins/ChangeLogPlugin/ChangeLog.txt
index aefeb92ce5..651abb0cac 100755
--- a/plugins/ChangeLogPlugin/ChangeLog.txt
+++ b/plugins/ChangeLogPlugin/ChangeLog.txt
@@ -1,3 +1,178 @@
+[3.6.0]
+*Gyroid infill
+New infill pattern with enhanced strength properties. Gyroid infill is one of the strongest infill types for a given weight, has isotropic properties, and prints relatively fast with reduced material use and a fully connected part interior. Note: Slicing time can increase up to 40 seconds or more, depending on the model. Contributed by smartavionics.
+
+*Support brim
+New setting that integrates the first layer of support material with the brim’s geometry. This significantly improves adhesion when printing with support material. Contributed by BagelOrb.
+
+*Cooling fan number
+It is now possible to specify the cooling fan to use if your printer has multiple fans. This is implemented under Machine settings in the Extruder tab. Contributed by smartavionics.
+
+*Settings refactor
+The CuraEngine has been refactored to create a more testable, future-proof way of storing and representing settings. This makes slicing faster, and future development easier.
+
+*Print core CC 0.6
+The new print core CC 0.6 is selectable when the Ultimaker S5 profile is active. This print core is optimized for use with abrasive materials and composites.
+
+*File name and layer display
+Added M117 commands to GCODE to give real-time information about the print job file name and layer number shown on the printer’s display when printing via USB. Contributed by adecastilho.
+
+*Firmware checker/Ultimaker S5
+The update checker code has been improved and tested for more reliable firmware update notifications in Ultimaker Cura. The Ultimaker S5 is now included.
+
+*Fullscreen mode shortcuts
+Fullscreen mode can be toggled using the View menu or with the keyboard shortcuts: Command + Control + F (macOS), or F11 (Windows and Linux). Contributed by KangDroid.
+
+*Configuration error message
+In previous versions, Ultimaker Cura would display an error dialog explaining when something happened to user configuration files, including the option to reset to factory defaults. This would not warn about losing the current printer and print profile settings, so this information has been added.
+
+*Rename Toolbox to Marketplace
+The entry points to the Toolbox are now renamed to Marketplace.
+
+*Materials in the Marketplace
+A new tab has been added to the Marketplace that includes downloadable material profiles, to quickly and easily prepare models for a range of third-party materials.
+
+*New third-party definitions
+New profiles added for Anycube 4MAx and Tizyx K25. Contributed by jscurtu and ValentinPitre respectively.
+
+*Improved definitions for Ender-3
+The Ender-3 build plate size has been adjusted to the correct size of 235 x 235 mm, corrected the start-up sequence, and the printhead position has been adjusted when prints are purged or completed. Contributed by stelgenhof.
+
+*Add mesh names to slicing message
+Added comment generation to indicate which mesh the GCODE after this comment is constructing. Contributed by paukstelis.
+
+*Bug fixes
+- The active material is highlighted in Ultimaker Cura’s material manager list. This behavior is now consistent with the profile and machine manager.
+- The option to use 1.75 mm diameter filament with third-party 3D printers is now fixed and does not revert back to 2.85 mm. This fix also applies the appropriate a Z-axis speed change for 1.75 mm filament printers. Contributed by kaleidoscopeit.
+- A fix was created to handle OSX version 10.10, but due to the QT upgrade, users with older versions won’t be able to run Ultimaker Cura on their system without a system update. This applies to OSX version 10.09 and 10.08.
+- Fixed a memory leak when leaving the “Monitor” page open.
+- Added performance improvements to the PolygonConnector to efficiently connect polygons that are close to each other. This also reduces the chances of the print head collide with previously printed things. Contributed by BagelOrb.
+- Fixed a bug where the GCODE reader didn’t show retractions.
+- Changes the USBPrinting update thread to prevent flooding the printer with M105 temperature update requests. Contributed by fieldOfView.
+- Fix the behavior of the "manage visible settings" button, when pressing the "cog" icon of a particular category. Contributed by fieldOfView.
+- Add a new post processing script that pauses the print at a certain height that works with RepRap printers. Contributed by Kriechi.
+- Fix updates to the print monitor temperatures while preheating. Contributed by fieldOfView.
+- Fixed a bug where material cost is not shown unless weight is changed.
+- Fixed bugs crashing the CuraEngine when TreeSupport is enabled.
+- Fixed a bug where Ultimaker Cura would upload the wrong firmware after switching printers in the UI.
+- Fixed a bug where the layer view was missing if the first layer was empty.
+- Fixed a bug where erroneous combing movements were taking place.
+- Fixed a bug where the initial layer temperature is set correctly for the first object but then never again.
+- Fixed a bug where clicking the fx icon didn’t respond.
+
+[3.5.1]
+*Bug fixes
+- Fixed M104 temperature commands giving inaccurate results.
+- Fixed crashes caused by loading files from USB stick on Windows platforms.
+- Fixed several issues with configuration files that missed the type in the metadata.
+- Fixed issues caused by skin/infill optimization.
+- Fixed several issues related to missing definition files for third-party printers.
+- Fixed an issue where combing path generation cuts corners.
+- Fixed a range of crashes caused by lock files.
+- Fixed issues with remembering save directories on MacOS.
+- Fixed an issue where CuraEngine uses incorrect material settings.
+- Fixed an issue where some support layers don't have support infill.
+
+[3.5.0]
+*Monitor page
+The monitor page of Ultimaker Cura has been remodeled for better consistency with the Cura Connect ‘Print jobs’ interface. This means less switching between interfaces, and more control from within Ultimaker Cura.
+
+*Open recent projects
+Project files can now be found in the ‘Open Recent’ menu.
+
+*New tool hotkeys
+New hotkeys have been assigned for quick toggling between the translate (T), scale (S), rotate (R) and mirror (M) tools.
+
+*Project files use 3MF only
+A 3MF extension is now used for project files. The ‘.curaproject’ extension is no longer used.
+
+*Camera maximum zoom
+The maximum zoom has been adjusted to scale with the size of the selected printer. This fixes third-party printers with huge build volumes to be correctly visible.
+
+*Corrected width of layer number box
+The layer number indicator in the layer view now displays numbers above 999 correctly.
+
+*Materials preferences
+This screen has been redesigned to improve user experience. Materials can now be set as a favorites, so they can be easily accessed in the material selection panel at the top-right of the screen.
+
+*Installed packages checkmark
+Packages that are already installed in the Toolbox are now have a checkmark for easy reference.
+
+*Mac OSX save dialog
+The save dialog has been restored to its native behavior and bugs have been fixed.
+
+*Removed .gz extension
+Saving compressed g-code files from the save dialog has been removed because of incompatibility with MacOS. If sending jobs over Wi-Fi, g-code is still compressed.
+
+*Updates to Chinese translations
+Improved and updated Chinese translations. Contributed by MarmaladeForMeat.
+
+*Save project
+Saving the project no longer triggers the project to reslice.
+
+*File menu
+The Save option in the file menu now saves project files. The export option now saves other types of files, such as STL.
+
+*Improved processing of overhang walls
+Overhang walls are detected and printed with different speeds. It will not start a perimeter on an overhanging wall. The quality of overhanging walls may be improved by printing those at a different speed. Contributed by smartavionics.
+
+*Prime tower reliability
+The prime tower has been improved for better reliability. This is especially useful when printing with two materials that do not adhere well.
+
+*Support infill line direction
+The support infill lines can now be rotated to increase the supporting capabilities and reduce artifacts on the model. This setting rotates existing patterns, like triangle support infill. Contributed by fieldOfView.
+
+*Minimum polygon circumference
+Polygons in sliced layers that have a circumference smaller than the setting value will be filtered out. Lower values lead to higher resolution meshes at the cost of increased slicing time. This setting is ideal for very tiny prints with a lot of detail, or for SLA printers. Contributed by cubiq.
+
+*Initial layer support line distance
+This setting enables the user to reduce or increase the density of the support initial layer in order to increase or reduce adhesion to the build plate and the overall strength.
+
+*Extra infill wall line count
+Adds extra walls around infill. Contributed by BagelOrb.
+
+*Multiply infill
+Creates multiple infill lines on the same pattern for sturdier infill. Contributed by BagelOrb.
+
+*Connected infill polygons
+Connecting infill lines now also works with concentric and cross infill patterns. The benefit would be stronger infill and more consistent material flow/saving retractions. Contributed by BagelOrb.
+
+*Fan speed override
+New setting to modify the fan speed of supported areas. This setting can be found in Support settings > Fan Speed Override when support is enabled. Contributed by smartavionics.
+
+*Minimum wall flow
+New setting to define a minimum flow for thin printed walls. Contributed by smartavionics.
+
+*Custom support plugin
+A tool downloadable from the toolbox, similar to the support blocker, that adds cubes of support to the model manually by clicking parts of it. Contributed by Lokster.
+
+*Quickly toggle autoslicing
+Adds a pause/play button to the progress bar to quickly toggle autoslicing. Contributed by fieldOfview.
+
+*Cura-DuetRRFPlugin
+Adds output devices for a Duet RepRapFirmware printer: "Print", "Simulate", and "Upload". Contributed by Kriechi.
+
+*Dremel 3D20
+This plugin adds the Dremel printer to Ultimaker Cura. Contributed by Kriechi.
+
+*Bug fixes
+- Removed extra M109 commands. Older versions would generate superfluous M109 commands. This has been fixed for better temperature stability when printing.
+- Fixed minor mesh handling bugs. A few combinations of modifier meshes now lead to expected behavior.
+- Removed unnecessary travels. Connected infill lines are now always printed completely connected, without unnecessary travel moves.
+- Removed concentric 3D infill. This infill type has been removed due to lack of reliability.
+- Extra skin wall count. Fixed an issue that caused extra print moves with this setting enabled.
+- Concentric skin. Small gaps in concentric skin are now filled correctly.
+- Order of printed models. The order of a large batch of printed models is now more consistent, instead of random.
+
+*Third party printers
+- TiZYX
+- Winbo
+- Tevo Tornado
+- Creality CR-10S
+- Wanhao Duplicator
+- Deltacomb (update)
+- Dacoma (update)
+
[3.4.1]
*Bug fixes
- Fixed an issue that would occasionally cause an unnecessary extra skin wall to be printed, which increased print time.
@@ -768,7 +943,7 @@ This release adds support for printers with elliptic buildplates. This feature h
*AppImage for Linux
The Linux distribution is now in AppImage format, which makes Cura easier to install.
-*bugfixes
+*Bugfixes
The user is now notified when a new version of Cura is available.
When searching in the setting visibility preferences, the category for each setting is always displayed.
3MF files are now saved and loaded correctly.
diff --git a/plugins/ChangeLogPlugin/__init__.py b/plugins/ChangeLogPlugin/__init__.py
index 97d9e411e5..a5452b60c8 100644
--- a/plugins/ChangeLogPlugin/__init__.py
+++ b/plugins/ChangeLogPlugin/__init__.py
@@ -3,8 +3,6 @@
from . import ChangeLog
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
def getMetaData():
return {}
diff --git a/plugins/ChangeLogPlugin/plugin.json b/plugins/ChangeLogPlugin/plugin.json
index e9414b9b71..92041d1543 100644
--- a/plugins/ChangeLogPlugin/plugin.json
+++ b/plugins/ChangeLogPlugin/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Changelog",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Shows changes since latest checked version.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/CuraDrive/__init__.py b/plugins/CuraDrive/__init__.py
new file mode 100644
index 0000000000..eeb6b78689
--- /dev/null
+++ b/plugins/CuraDrive/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from .src.DrivePluginExtension import DrivePluginExtension
+
+
+def getMetaData():
+ return {}
+
+
+def register(app):
+ return {"extension": DrivePluginExtension()}
diff --git a/plugins/CuraDrive/plugin.json b/plugins/CuraDrive/plugin.json
new file mode 100644
index 0000000000..d1cab39ca5
--- /dev/null
+++ b/plugins/CuraDrive/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Cura Backups",
+ "author": "Ultimaker B.V.",
+ "description": "Backup and restore your configuration.",
+ "version": "1.2.0",
+ "api": 6,
+ "i18n-catalog": "cura"
+}
diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py
new file mode 100644
index 0000000000..6a828e32d6
--- /dev/null
+++ b/plugins/CuraDrive/src/DriveApiService.py
@@ -0,0 +1,171 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import base64
+import hashlib
+from datetime import datetime
+from tempfile import NamedTemporaryFile
+from typing import Any, Optional, List, Dict
+
+import requests
+
+from UM.Logger import Logger
+from UM.Message import Message
+from UM.Signal import Signal, signalemitter
+from cura.CuraApplication import CuraApplication
+
+from .UploadBackupJob import UploadBackupJob
+from .Settings import Settings
+
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+
+
+## The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling.
+@signalemitter
+class DriveApiService:
+ BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL)
+
+ # Emit signal when restoring backup started or finished.
+ restoringStateChanged = Signal()
+
+ # Emit signal when creating backup started or finished.
+ creatingStateChanged = Signal()
+
+ def __init__(self) -> None:
+ self._cura_api = CuraApplication.getInstance().getCuraAPI()
+
+ def getBackups(self) -> List[Dict[str, Any]]:
+ access_token = self._cura_api.account.accessToken
+ if not access_token:
+ Logger.log("w", "Could not get access token.")
+ return []
+ try:
+ backup_list_request = requests.get(self.BACKUP_URL, headers = {
+ "Authorization": "Bearer {}".format(access_token)
+ })
+ except requests.exceptions.ConnectionError:
+ Logger.log("w", "Unable to connect with the server.")
+ return []
+
+ # HTTP status 300s mean redirection. 400s and 500s are errors.
+ # Technically 300s are not errors, but the use case here relies on "requests" to handle redirects automatically.
+ if backup_list_request.status_code >= 300:
+ Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text)
+ Message(catalog.i18nc("@info:backup_status", "There was an error listing your backups."), title = catalog.i18nc("@info:title", "Backup")).show()
+ return []
+ return backup_list_request.json()["data"]
+
+ def createBackup(self) -> None:
+ self.creatingStateChanged.emit(is_creating = True)
+
+ # Create the backup.
+ backup_zip_file, backup_meta_data = self._cura_api.backups.createBackup()
+ if not backup_zip_file or not backup_meta_data:
+ self.creatingStateChanged.emit(is_creating = False, error_message ="Could not create backup.")
+ return
+
+ # Create an upload entry for the backup.
+ timestamp = datetime.now().isoformat()
+ backup_meta_data["description"] = "{}.backup.{}.cura.zip".format(timestamp, backup_meta_data["cura_release"])
+ backup_upload_url = self._requestBackupUpload(backup_meta_data, len(backup_zip_file))
+ if not backup_upload_url:
+ self.creatingStateChanged.emit(is_creating = False, error_message ="Could not upload backup.")
+ return
+
+ # Upload the backup to storage.
+ upload_backup_job = UploadBackupJob(backup_upload_url, backup_zip_file)
+ upload_backup_job.finished.connect(self._onUploadFinished)
+ upload_backup_job.start()
+
+ def _onUploadFinished(self, job: "UploadBackupJob") -> None:
+ if job.backup_upload_error_message != "":
+ # If the job contains an error message we pass it along so the UI can display it.
+ self.creatingStateChanged.emit(is_creating = False, error_message = job.backup_upload_error_message)
+ else:
+ self.creatingStateChanged.emit(is_creating = False)
+
+ def restoreBackup(self, backup: Dict[str, Any]) -> None:
+ self.restoringStateChanged.emit(is_restoring = True)
+ download_url = backup.get("download_url")
+ if not download_url:
+ # If there is no download URL, we can't restore the backup.
+ return self._emitRestoreError()
+
+ download_package = requests.get(download_url, stream = True)
+ if download_package.status_code >= 300:
+ # Something went wrong when attempting to download the backup.
+ Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
+ return self._emitRestoreError()
+
+ # We store the file in a temporary path fist to ensure integrity.
+ temporary_backup_file = NamedTemporaryFile(delete = False)
+ with open(temporary_backup_file.name, "wb") as write_backup:
+ for chunk in download_package:
+ write_backup.write(chunk)
+
+ if not self._verifyMd5Hash(temporary_backup_file.name, backup.get("md5_hash", "")):
+ # Don't restore the backup if the MD5 hashes do not match.
+ # This can happen if the download was interrupted.
+ Logger.log("w", "Remote and local MD5 hashes do not match, not restoring backup.")
+ return self._emitRestoreError()
+
+ # Tell Cura to place the backup back in the user data folder.
+ with open(temporary_backup_file.name, "rb") as read_backup:
+ self._cura_api.backups.restoreBackup(read_backup.read(), backup.get("metadata", {}))
+ self.restoringStateChanged.emit(is_restoring = False)
+
+ def _emitRestoreError(self) -> None:
+ self.restoringStateChanged.emit(is_restoring = False,
+ error_message = catalog.i18nc("@info:backup_status",
+ "There was an error trying to restore your backup."))
+
+ # Verify the MD5 hash of a file.
+ # \param file_path Full path to the file.
+ # \param known_hash The known MD5 hash of the file.
+ # \return: Success or not.
+ @staticmethod
+ def _verifyMd5Hash(file_path: str, known_hash: str) -> bool:
+ with open(file_path, "rb") as read_backup:
+ local_md5_hash = base64.b64encode(hashlib.md5(read_backup.read()).digest(), altchars = b"_-").decode("utf-8")
+ return known_hash == local_md5_hash
+
+ def deleteBackup(self, backup_id: str) -> bool:
+ access_token = self._cura_api.account.accessToken
+ if not access_token:
+ Logger.log("w", "Could not get access token.")
+ return False
+
+ delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
+ "Authorization": "Bearer {}".format(access_token)
+ })
+ if delete_backup.status_code >= 300:
+ Logger.log("w", "Could not delete backup: %s", delete_backup.text)
+ return False
+ return True
+
+ # Request a backup upload slot from the API.
+ # \param backup_metadata: A dict containing some meta data about the backup.
+ # \param backup_size The size of the backup file in bytes.
+ # \return: The upload URL for the actual backup file if successful, otherwise None.
+ def _requestBackupUpload(self, backup_metadata: Dict[str, Any], backup_size: int) -> Optional[str]:
+ access_token = self._cura_api.account.accessToken
+ if not access_token:
+ Logger.log("w", "Could not get access token.")
+ return None
+
+ backup_upload_request = requests.put(self.BACKUP_URL, json = {
+ "data": {
+ "backup_size": backup_size,
+ "metadata": backup_metadata
+ }
+ }, headers = {
+ "Authorization": "Bearer {}".format(access_token)
+ })
+
+ # Any status code of 300 or above indicates an error.
+ if backup_upload_request.status_code >= 300:
+ Logger.log("w", "Could not request backup upload: %s", backup_upload_request.text)
+ return None
+
+ return backup_upload_request.json()["data"]["upload_url"]
diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py
new file mode 100644
index 0000000000..bcc326a133
--- /dev/null
+++ b/plugins/CuraDrive/src/DrivePluginExtension.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import os
+from datetime import datetime
+from typing import Any, cast, Dict, List, Optional
+
+from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
+
+from UM.Extension import Extension
+from UM.Logger import Logger
+from UM.Message import Message
+from cura.CuraApplication import CuraApplication
+
+from .Settings import Settings
+from .DriveApiService import DriveApiService
+
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+
+
+# The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud.
+class DrivePluginExtension(QObject, Extension):
+
+ # Signal emitted when the list of backups changed.
+ backupsChanged = pyqtSignal()
+
+ # Signal emitted when restoring has started. Needed to prevent parallel restoring.
+ restoringStateChanged = pyqtSignal()
+
+ # Signal emitted when creating has started. Needed to prevent parallel creation of backups.
+ creatingStateChanged = pyqtSignal()
+
+ # Signal emitted when preferences changed (like auto-backup).
+ preferencesChanged = pyqtSignal()
+
+ DATE_FORMAT = "%d/%m/%Y %H:%M:%S"
+
+ def __init__(self) -> None:
+ QObject.__init__(self, None)
+ Extension.__init__(self)
+
+ # Local data caching for the UI.
+ self._drive_window = None # type: Optional[QObject]
+ self._backups = [] # type: List[Dict[str, Any]]
+ self._is_restoring_backup = False
+ self._is_creating_backup = False
+
+ # Initialize services.
+ preferences = CuraApplication.getInstance().getPreferences()
+ self._drive_api_service = DriveApiService()
+
+ # Attach signals.
+ CuraApplication.getInstance().getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged)
+ self._drive_api_service.restoringStateChanged.connect(self._onRestoringStateChanged)
+ self._drive_api_service.creatingStateChanged.connect(self._onCreatingStateChanged)
+
+ # Register preferences.
+ preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False)
+ preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY,
+ datetime.now().strftime(self.DATE_FORMAT))
+
+ # Register the menu item
+ self.addMenuItem(catalog.i18nc("@item:inmenu", "Manage backups"), self.showDriveWindow)
+
+ # Make auto-backup on boot if required.
+ CuraApplication.getInstance().engineCreatedSignal.connect(self._autoBackup)
+
+ def showDriveWindow(self) -> None:
+ if not self._drive_window:
+ plugin_dir_path = cast(str, CuraApplication.getInstance().getPluginRegistry().getPluginPath(self.getPluginId())) # We know this plug-in exists because that's us, so this always returns str.
+ path = os.path.join(plugin_dir_path, "src", "qml", "main.qml")
+ self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self})
+ self.refreshBackups()
+ if self._drive_window:
+ self._drive_window.show()
+
+ def _autoBackup(self) -> None:
+ preferences = CuraApplication.getInstance().getPreferences()
+ if preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo():
+ self.createBackup()
+
+ def _isLastBackupTooLongAgo(self) -> bool:
+ current_date = datetime.now()
+ last_backup_date = self._getLastBackupDate()
+ date_diff = current_date - last_backup_date
+ return date_diff.days > 1
+
+ def _getLastBackupDate(self) -> "datetime":
+ preferences = CuraApplication.getInstance().getPreferences()
+ last_backup_date = preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY)
+ return datetime.strptime(last_backup_date, self.DATE_FORMAT)
+
+ def _storeBackupDate(self) -> None:
+ backup_date = datetime.now().strftime(self.DATE_FORMAT)
+ preferences = CuraApplication.getInstance().getPreferences()
+ preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date)
+
+ def _onLoginStateChanged(self, logged_in: bool = False) -> None:
+ if logged_in:
+ self.refreshBackups()
+
+ def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: str = None) -> None:
+ self._is_restoring_backup = is_restoring
+ self.restoringStateChanged.emit()
+ if error_message:
+ Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
+
+ def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None:
+ self._is_creating_backup = is_creating
+ self.creatingStateChanged.emit()
+ if error_message:
+ Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
+ else:
+ self._storeBackupDate()
+ if not is_creating and not error_message:
+ # We've finished creating a new backup, to the list has to be updated.
+ self.refreshBackups()
+
+ @pyqtSlot(bool, name = "toggleAutoBackup")
+ def toggleAutoBackup(self, enabled: bool) -> None:
+ preferences = CuraApplication.getInstance().getPreferences()
+ preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled)
+
+ @pyqtProperty(bool, notify = preferencesChanged)
+ def autoBackupEnabled(self) -> bool:
+ preferences = CuraApplication.getInstance().getPreferences()
+ return bool(preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY))
+
+ @pyqtProperty("QVariantList", notify = backupsChanged)
+ def backups(self) -> List[Dict[str, Any]]:
+ return self._backups
+
+ @pyqtSlot(name = "refreshBackups")
+ def refreshBackups(self) -> None:
+ self._backups = self._drive_api_service.getBackups()
+ self.backupsChanged.emit()
+
+ @pyqtProperty(bool, notify = restoringStateChanged)
+ def isRestoringBackup(self) -> bool:
+ return self._is_restoring_backup
+
+ @pyqtProperty(bool, notify = creatingStateChanged)
+ def isCreatingBackup(self) -> bool:
+ return self._is_creating_backup
+
+ @pyqtSlot(str, name = "restoreBackup")
+ def restoreBackup(self, backup_id: str) -> None:
+ for backup in self._backups:
+ if backup.get("backup_id") == backup_id:
+ self._drive_api_service.restoreBackup(backup)
+ return
+ Logger.log("w", "Unable to find backup with the ID %s", backup_id)
+
+ @pyqtSlot(name = "createBackup")
+ def createBackup(self) -> None:
+ self._drive_api_service.createBackup()
+
+ @pyqtSlot(str, name = "deleteBackup")
+ def deleteBackup(self, backup_id: str) -> None:
+ self._drive_api_service.deleteBackup(backup_id)
+ self.refreshBackups()
diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py
new file mode 100644
index 0000000000..abe64e0acd
--- /dev/null
+++ b/plugins/CuraDrive/src/Settings.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from cura import UltimakerCloudAuthentication
+
+
+class Settings:
+ # Keeps the plugin settings.
+ DRIVE_API_VERSION = 1
+ DRIVE_API_URL = "{}/cura-drive/v{}".format(UltimakerCloudAuthentication.CuraCloudAPIRoot, str(DRIVE_API_VERSION))
+
+ AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled"
+ AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date"
diff --git a/plugins/CuraDrive/src/UploadBackupJob.py b/plugins/CuraDrive/src/UploadBackupJob.py
new file mode 100644
index 0000000000..2e76ed9b4b
--- /dev/null
+++ b/plugins/CuraDrive/src/UploadBackupJob.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import requests
+
+from UM.Job import Job
+from UM.Logger import Logger
+from UM.Message import Message
+
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+
+
+class UploadBackupJob(Job):
+ MESSAGE_TITLE = catalog.i18nc("@info:title", "Backups")
+
+ # This job is responsible for uploading the backup file to cloud storage.
+ # As it can take longer than some other tasks, we schedule this using a Cura Job.
+ def __init__(self, signed_upload_url: str, backup_zip: bytes) -> None:
+ super().__init__()
+ self._signed_upload_url = signed_upload_url
+ self._backup_zip = backup_zip
+ self._upload_success = False
+ self.backup_upload_error_message = ""
+
+ def run(self) -> None:
+ upload_message = Message(catalog.i18nc("@info:backup_status", "Uploading your backup..."), title = self.MESSAGE_TITLE, progress = -1)
+ upload_message.show()
+
+ backup_upload = requests.put(self._signed_upload_url, data = self._backup_zip)
+ upload_message.hide()
+
+ if backup_upload.status_code >= 300:
+ self.backup_upload_error_message = backup_upload.text
+ Logger.log("w", "Could not upload backup file: %s", backup_upload.text)
+ Message(catalog.i18nc("@info:backup_status", "There was an error while uploading your backup."), title = self.MESSAGE_TITLE).show()
+ else:
+ self._upload_success = True
+ Message(catalog.i18nc("@info:backup_status", "Your backup has finished uploading."), title = self.MESSAGE_TITLE).show()
+
+ self.finished.emit(self)
diff --git a/plugins/CuraDrive/src/__init__.py b/plugins/CuraDrive/src/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/CuraDrive/src/qml/components/BackupList.qml b/plugins/CuraDrive/src/qml/components/BackupList.qml
new file mode 100644
index 0000000000..a4a460a885
--- /dev/null
+++ b/plugins/CuraDrive/src/qml/components/BackupList.qml
@@ -0,0 +1,39 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+import UM 1.1 as UM
+
+ScrollView
+{
+ property alias model: backupList.model
+ width: parent.width
+ clip: true
+ ListView
+ {
+ id: backupList
+ width: parent.width
+ delegate: Item
+ {
+ // Add a margin, otherwise the scrollbar is on top of the right most component
+ width: parent.width - UM.Theme.getSize("default_margin").width
+ height: childrenRect.height
+
+ BackupListItem
+ {
+ id: backupListItem
+ width: parent.width
+ }
+
+ Rectangle
+ {
+ id: divider
+ color: UM.Theme.getColor("lining")
+ height: UM.Theme.getSize("default_lining").height
+ }
+ }
+ }
+}
diff --git a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml
new file mode 100644
index 0000000000..8decdc5c27
--- /dev/null
+++ b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml
@@ -0,0 +1,46 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.3
+
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+import "../components"
+
+RowLayout
+{
+ id: backupListFooter
+ width: parent.width
+ property bool showInfoButton: false
+
+ Cura.PrimaryButton
+ {
+ id: infoButton
+ text: catalog.i18nc("@button", "Want more?")
+ iconSource: UM.Theme.getIcon("info")
+ onClicked: Qt.openUrlExternally("https://goo.gl/forms/QACEP8pP3RV60QYG2")
+ visible: backupListFooter.showInfoButton
+ }
+
+ Cura.PrimaryButton
+ {
+ id: createBackupButton
+ text: catalog.i18nc("@button", "Backup Now")
+ iconSource: UM.Theme.getIcon("plus")
+ enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
+ onClicked: CuraDrive.createBackup()
+ busy: CuraDrive.isCreatingBackup
+ }
+
+ Cura.CheckBoxWithTooltip
+ {
+ id: autoBackupEnabled
+ checked: CuraDrive.autoBackupEnabled
+ onClicked: CuraDrive.toggleAutoBackup(autoBackupEnabled.checked)
+ text: catalog.i18nc("@checkbox:description", "Auto Backup")
+ tooltip: catalog.i18nc("@checkbox:description", "Automatically create a backup each day that Cura is started.")
+ }
+}
diff --git a/plugins/CuraDrive/src/qml/components/BackupListItem.qml b/plugins/CuraDrive/src/qml/components/BackupListItem.qml
new file mode 100644
index 0000000000..5cdb500b4e
--- /dev/null
+++ b/plugins/CuraDrive/src/qml/components/BackupListItem.qml
@@ -0,0 +1,113 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.3
+import QtQuick.Dialogs 1.1
+
+import UM 1.1 as UM
+import Cura 1.0 as Cura
+
+Item
+{
+ id: backupListItem
+ width: parent.width
+ height: showDetails ? dataRow.height + backupDetails.height : dataRow.height
+ property bool showDetails: false
+
+ // Backup details toggle animation.
+ Behavior on height
+ {
+ PropertyAnimation
+ {
+ duration: 70
+ }
+ }
+
+ RowLayout
+ {
+ id: dataRow
+ spacing: UM.Theme.getSize("wide_margin").width
+ width: parent.width
+ height: 50 * screenScaleFactor
+
+ UM.SimpleButton
+ {
+ width: UM.Theme.getSize("section_icon").width
+ height: UM.Theme.getSize("section_icon").height
+ color: UM.Theme.getColor("small_button_text")
+ hoverColor: UM.Theme.getColor("small_button_text_hover")
+ iconSource: UM.Theme.getIcon("info")
+ onClicked: backupListItem.showDetails = !backupListItem.showDetails
+ }
+
+ Label
+ {
+ text: new Date(modelData.generated_time).toLocaleString(UM.Preferences.getValue("general/language"))
+ color: UM.Theme.getColor("text")
+ elide: Text.ElideRight
+ Layout.minimumWidth: 100 * screenScaleFactor
+ Layout.maximumWidth: 500 * screenScaleFactor
+ Layout.fillWidth: true
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ }
+
+ Label
+ {
+ text: modelData.metadata.description
+ color: UM.Theme.getColor("text")
+ elide: Text.ElideRight
+ Layout.minimumWidth: 100 * screenScaleFactor
+ Layout.maximumWidth: 500 * screenScaleFactor
+ Layout.fillWidth: true
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ }
+
+ Cura.SecondaryButton
+ {
+ text: catalog.i18nc("@button", "Restore")
+ enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
+ onClicked: confirmRestoreDialog.visible = true
+ }
+
+ UM.SimpleButton
+ {
+ width: UM.Theme.getSize("message_close").width
+ height: UM.Theme.getSize("message_close").height
+ color: UM.Theme.getColor("small_button_text")
+ hoverColor: UM.Theme.getColor("small_button_text_hover")
+ iconSource: UM.Theme.getIcon("cross1")
+ onClicked: confirmDeleteDialog.visible = true
+ }
+ }
+
+ BackupListItemDetails
+ {
+ id: backupDetails
+ backupDetailsData: modelData
+ width: parent.width
+ visible: parent.showDetails
+ anchors.top: dataRow.bottom
+ }
+
+ MessageDialog
+ {
+ id: confirmDeleteDialog
+ title: catalog.i18nc("@dialog:title", "Delete Backup")
+ text: catalog.i18nc("@dialog:info", "Are you sure you want to delete this backup? This cannot be undone.")
+ standardButtons: StandardButton.Yes | StandardButton.No
+ onYes: CuraDrive.deleteBackup(modelData.backup_id)
+ }
+
+ MessageDialog
+ {
+ id: confirmRestoreDialog
+ title: catalog.i18nc("@dialog:title", "Restore Backup")
+ text: catalog.i18nc("@dialog:info", "You will need to restart Cura before your backup is restored. Do you want to close Cura now?")
+ standardButtons: StandardButton.Yes | StandardButton.No
+ onYes: CuraDrive.restoreBackup(modelData.backup_id)
+ }
+}
diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml
new file mode 100644
index 0000000000..4da15c6f16
--- /dev/null
+++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml
@@ -0,0 +1,63 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.3
+
+import UM 1.1 as UM
+
+ColumnLayout
+{
+ id: backupDetails
+ width: parent.width
+ spacing: UM.Theme.getSize("default_margin").width
+ property var backupDetailsData
+
+ // Cura version
+ BackupListItemDetailsRow
+ {
+ iconSource: UM.Theme.getIcon("application")
+ label: catalog.i18nc("@backuplist:label", "Cura Version")
+ value: backupDetailsData.metadata.cura_release
+ }
+
+ // Machine count.
+ BackupListItemDetailsRow
+ {
+ iconSource: UM.Theme.getIcon("printer_single")
+ label: catalog.i18nc("@backuplist:label", "Machines")
+ value: backupDetailsData.metadata.machine_count
+ }
+
+ // Material count
+ BackupListItemDetailsRow
+ {
+ iconSource: UM.Theme.getIcon("category_material")
+ label: catalog.i18nc("@backuplist:label", "Materials")
+ value: backupDetailsData.metadata.material_count
+ }
+
+ // Profile count.
+ BackupListItemDetailsRow
+ {
+ iconSource: UM.Theme.getIcon("settings")
+ label: catalog.i18nc("@backuplist:label", "Profiles")
+ value: backupDetailsData.metadata.profile_count
+ }
+
+ // Plugin count.
+ BackupListItemDetailsRow
+ {
+ iconSource: UM.Theme.getIcon("plugin")
+ label: catalog.i18nc("@backuplist:label", "Plugins")
+ value: backupDetailsData.metadata.plugin_count
+ }
+
+ // Spacer.
+ Item
+ {
+ width: parent.width
+ height: UM.Theme.getSize("default_margin").height
+ }
+}
diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml
new file mode 100644
index 0000000000..9e4612fcf8
--- /dev/null
+++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml
@@ -0,0 +1,52 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.3
+
+import UM 1.3 as UM
+
+RowLayout
+{
+ id: detailsRow
+ width: parent.width
+ height: 40 * screenScaleFactor
+
+ property alias iconSource: icon.source
+ property alias label: detailName.text
+ property alias value: detailValue.text
+
+ UM.RecolorImage
+ {
+ id: icon
+ width: 18 * screenScaleFactor
+ height: width
+ source: ""
+ color: UM.Theme.getColor("text")
+ }
+
+ Label
+ {
+ id: detailName
+ color: UM.Theme.getColor("text")
+ elide: Text.ElideRight
+ Layout.minimumWidth: 50 * screenScaleFactor
+ Layout.maximumWidth: 100 * screenScaleFactor
+ Layout.fillWidth: true
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ }
+
+ Label
+ {
+ id: detailValue
+ color: UM.Theme.getColor("text")
+ elide: Text.ElideRight
+ Layout.minimumWidth: 50 * screenScaleFactor
+ Layout.maximumWidth: 100 * screenScaleFactor
+ Layout.fillWidth: true
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ }
+}
diff --git a/plugins/CuraDrive/src/qml/images/icon.png b/plugins/CuraDrive/src/qml/images/icon.png
new file mode 100644
index 0000000000..3f75491786
Binary files /dev/null and b/plugins/CuraDrive/src/qml/images/icon.png differ
diff --git a/plugins/CuraDrive/src/qml/main.qml b/plugins/CuraDrive/src/qml/main.qml
new file mode 100644
index 0000000000..48bf3b6ea4
--- /dev/null
+++ b/plugins/CuraDrive/src/qml/main.qml
@@ -0,0 +1,44 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.1
+import QtQuick.Window 2.2
+
+import UM 1.3 as UM
+import Cura 1.1 as Cura
+
+import "components"
+import "pages"
+
+Window
+{
+ id: curaDriveDialog
+ minimumWidth: Math.round(UM.Theme.getSize("modal_window_minimum").width)
+ minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height)
+ maximumWidth: Math.round(minimumWidth * 1.2)
+ maximumHeight: Math.round(minimumHeight * 1.2)
+ width: minimumWidth
+ height: minimumHeight
+ color: UM.Theme.getColor("main_background")
+ title: catalog.i18nc("@title:window", "Cura Backups")
+
+ // Globally available.
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
+
+ WelcomePage
+ {
+ id: welcomePage
+ visible: !Cura.API.account.isLoggedIn
+ }
+
+ BackupsPage
+ {
+ id: backupsPage
+ visible: Cura.API.account.isLoggedIn
+ }
+}
diff --git a/plugins/CuraDrive/src/qml/pages/BackupsPage.qml b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml
new file mode 100644
index 0000000000..c337294744
--- /dev/null
+++ b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml
@@ -0,0 +1,75 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.3
+
+import UM 1.3 as UM
+import Cura 1.1 as Cura
+
+import "../components"
+
+Item
+{
+ id: backupsPage
+ anchors.fill: parent
+ anchors.margins: UM.Theme.getSize("wide_margin").width
+
+ ColumnLayout
+ {
+ spacing: UM.Theme.getSize("wide_margin").height
+ width: parent.width
+ anchors.fill: parent
+
+ Label
+ {
+ id: backupTitle
+ text: catalog.i18nc("@title", "My Backups")
+ font: UM.Theme.getFont("large")
+ color: UM.Theme.getColor("text")
+ Layout.fillWidth: true
+ renderType: Text.NativeRendering
+ }
+
+ Label
+ {
+ text: catalog.i18nc("@empty_state",
+ "You don't have any backups currently. Use the 'Backup Now' button to create one.")
+ width: parent.width
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ wrapMode: Label.WordWrap
+ visible: backupList.model.length == 0
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ renderType: Text.NativeRendering
+ }
+
+ BackupList
+ {
+ id: backupList
+ model: CuraDrive.backups
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+
+ Label
+ {
+ text: catalog.i18nc("@backup_limit_info",
+ "During the preview phase, you'll be limited to 5 visible backups. Remove a backup to see older ones.")
+ width: parent.width
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ wrapMode: Label.WordWrap
+ visible: backupList.model.length > 4
+ renderType: Text.NativeRendering
+ }
+
+ BackupListFooter
+ {
+ id: backupListFooter
+ showInfoButton: backupList.model.length > 4
+ }
+ }
+}
diff --git a/plugins/CuraDrive/src/qml/pages/WelcomePage.qml b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml
new file mode 100644
index 0000000000..0b207bc170
--- /dev/null
+++ b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml
@@ -0,0 +1,56 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.1
+import QtQuick.Window 2.2
+
+import UM 1.3 as UM
+import Cura 1.1 as Cura
+
+import "../components"
+
+
+Column
+{
+ id: welcomePage
+ spacing: UM.Theme.getSize("wide_margin").height
+ width: parent.width
+ height: childrenRect.height
+ anchors.centerIn: parent
+
+ Image
+ {
+ id: profileImage
+ fillMode: Image.PreserveAspectFit
+ source: "../images/icon.png"
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.round(parent.width / 4)
+ }
+
+ Label
+ {
+ id: welcomeTextLabel
+ text: catalog.i18nc("@description", "Backup and synchronize your Cura settings.")
+ width: Math.round(parent.width / 2)
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ wrapMode: Label.WordWrap
+ renderType: Text.NativeRendering
+ }
+
+ Cura.PrimaryButton
+ {
+ id: loginButton
+ width: UM.Theme.getSize("account_button").width
+ height: UM.Theme.getSize("account_button").height
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: catalog.i18nc("@button", "Sign in")
+ onClicked: Cura.API.account.login()
+ fixedWidthMode: true
+ }
+}
+
diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto
index 69612210ec..2eabe62366 100644
--- a/plugins/CuraEngineBackend/Cura.proto
+++ b/plugins/CuraEngineBackend/Cura.proto
@@ -29,6 +29,7 @@ message Object
bytes normals = 3; //An array of 3 floats.
bytes indices = 4; //An array of ints.
repeated Setting settings = 5; // Setting override per object, overruling the global settings.
+ string name = 6; //Mesh name
}
message Progress
@@ -57,6 +58,7 @@ message Polygon {
MoveCombingType = 8;
MoveRetractionType = 9;
SupportInterfaceType = 10;
+ PrimeTowerType = 11;
}
Type type = 1; // Type of move
bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
@@ -107,8 +109,9 @@ message PrintTimeMaterialEstimates { // The print time for each feature and mate
float time_travel = 9;
float time_retract = 10;
float time_support_interface = 11;
+ float time_prime_tower = 12;
- repeated MaterialEstimates materialEstimates = 12; // materialEstimates data
+ repeated MaterialEstimates materialEstimates = 13; // materialEstimates data
}
message MaterialEstimates {
diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py
index 9a5c95b04d..ceba5f3006 100755
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -10,6 +10,7 @@ from time import time
from typing import Any, cast, Dict, List, Optional, Set, TYPE_CHECKING
from UM.Backend.Backend import Backend, BackendState
+from UM.Scene.Camera import Camera
from UM.Scene.SceneNode import SceneNode
from UM.Signal import Signal
from UM.Logger import Logger
@@ -86,8 +87,8 @@ class CuraEngineBackend(QObject, Backend):
self._layer_view_active = False #type: bool
self._onActiveViewChanged()
- self._stored_layer_data = [] #type: List[Arcus.PythonMessage]
- self._stored_optimized_layer_data = {} #type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
+ self._stored_layer_data = [] # type: List[Arcus.PythonMessage]
+ self._stored_optimized_layer_data = {} # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._scene = self._application.getController().getScene() #type: Scene
self._scene.sceneChanged.connect(self._onSceneChanged)
@@ -151,7 +152,7 @@ class CuraEngineBackend(QObject, Backend):
if self._multi_build_plate_model:
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged)
- self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
+ self._application.getMachineManager().globalContainerChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
# extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
@@ -179,8 +180,7 @@ class CuraEngineBackend(QObject, Backend):
# This is useful for debugging and used to actually start the engine.
# \return list of commands and args / parameters.
def getEngineCommand(self) -> List[str]:
- json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
- command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
+ command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), ""]
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.")
@@ -204,7 +204,7 @@ class CuraEngineBackend(QObject, Backend):
@pyqtSlot()
def stopSlicing(self) -> None:
- self.backendStateChange.emit(BackendState.NotStarted)
+ self.setState(BackendState.NotStarted)
if self._slicing: # We were already slicing. Stop the old job.
self._terminate()
self._createSocket()
@@ -230,6 +230,7 @@ class CuraEngineBackend(QObject, Backend):
if not self._build_plates_to_be_sliced:
self.processingProgress.emit(1.0)
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
+ self.setState(BackendState.Done)
return
if self._process_layers_job:
@@ -246,7 +247,7 @@ class CuraEngineBackend(QObject, Backend):
num_objects = self._numObjectsPerBuildPlate()
self._stored_layer_data = []
- self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
+
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #Because we created this attribute above.
@@ -254,7 +255,7 @@ class CuraEngineBackend(QObject, Backend):
if self._build_plates_to_be_sliced:
self.slice()
return
-
+ self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
@@ -323,7 +324,7 @@ class CuraEngineBackend(QObject, Backend):
self._start_slice_job = None
if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error:
- self.backendStateChange.emit(BackendState.Error)
+ self.setState(BackendState.Error)
self.backendError.emit(job)
return
@@ -332,10 +333,10 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status",
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
- self.backendStateChange.emit(BackendState.Error)
+ self.setState(BackendState.Error)
self.backendError.emit(job)
else:
- self.backendStateChange.emit(BackendState.NotStarted)
+ self.setState(BackendState.NotStarted)
return
if job.getResult() == StartJobResult.SettingError:
@@ -343,7 +344,7 @@ class CuraEngineBackend(QObject, Backend):
if not self._global_container_stack:
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
return
- extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
+ extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
error_keys = [] #type: List[str]
for extruder in extruders:
error_keys.extend(extruder.getErrorKeys())
@@ -363,10 +364,10 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(", ".join(error_labels)),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
- self.backendStateChange.emit(BackendState.Error)
+ self.setState(BackendState.Error)
self.backendError.emit(job)
else:
- self.backendStateChange.emit(BackendState.NotStarted)
+ self.setState(BackendState.NotStarted)
return
elif job.getResult() == StartJobResult.ObjectSettingError:
@@ -387,7 +388,7 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
- self.backendStateChange.emit(BackendState.Error)
+ self.setState(BackendState.Error)
self.backendError.emit(job)
return
@@ -396,28 +397,28 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
- self.backendStateChange.emit(BackendState.Error)
+ self.setState(BackendState.Error)
self.backendError.emit(job)
else:
- self.backendStateChange.emit(BackendState.NotStarted)
+ self.setState(BackendState.NotStarted)
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
- self.backendStateChange.emit(BackendState.Error)
+ self.setState(BackendState.Error)
self.backendError.emit(job)
return
if job.getResult() == StartJobResult.NothingToSlice:
if self._application.platformActivity:
- self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
+ self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume or are assigned to a disabled extruder. Please scale or rotate models to fit, or enable an extruder."),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
- self.backendStateChange.emit(BackendState.Error)
+ self.setState(BackendState.Error)
self.backendError.emit(job)
else:
- self.backendStateChange.emit(BackendState.NotStarted)
+ self.setState(BackendState.NotStarted)
self._invokeSlice()
return
@@ -425,7 +426,7 @@ class CuraEngineBackend(QObject, Backend):
self._socket.sendMessage(job.getSliceMessage())
# Notify the user that it's now up to the backend to do it's job
- self.backendStateChange.emit(BackendState.Processing)
+ self.setState(BackendState.Processing)
if self._slice_start_time:
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
@@ -443,7 +444,7 @@ class CuraEngineBackend(QObject, Backend):
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
if node.callDecoration("isBlockSlicing"):
enable_timer = False
- self.backendStateChange.emit(BackendState.Disabled)
+ self.setState(BackendState.Disabled)
self._is_disabled = True
gcode_list = node.callDecoration("getGCodeList")
if gcode_list is not None:
@@ -452,7 +453,7 @@ class CuraEngineBackend(QObject, Backend):
if self._use_timer == enable_timer:
return self._use_timer
if enable_timer:
- self.backendStateChange.emit(BackendState.NotStarted)
+ self.setState(BackendState.NotStarted)
self.enableTimer()
return True
else:
@@ -476,7 +477,7 @@ class CuraEngineBackend(QObject, Backend):
#
# \param source The scene node that was changed.
def _onSceneChanged(self, source: SceneNode) -> None:
- if not isinstance(source, SceneNode):
+ if not source.callDecoration("isSliceable"):
return
# This case checks if the source node is a node that contains GCode. In this case the
@@ -519,7 +520,7 @@ class CuraEngineBackend(QObject, Backend):
self._build_plates_to_be_sliced.append(build_plate_number)
self.printDurationMessage.emit(source_build_plate_number, {}, [])
self.processingProgress.emit(0.0)
- self.backendStateChange.emit(BackendState.NotStarted)
+ self.setState(BackendState.NotStarted)
# if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData(build_plate_changed)
@@ -568,7 +569,7 @@ class CuraEngineBackend(QObject, Backend):
self.stopSlicing()
self.markSliceAll()
self.processingProgress.emit(0.0)
- self.backendStateChange.emit(BackendState.NotStarted)
+ self.setState(BackendState.NotStarted)
if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData()
@@ -614,7 +615,7 @@ class CuraEngineBackend(QObject, Backend):
# \param message The protobuf message containing the slicing progress.
def _onProgressMessage(self, message: Arcus.PythonMessage) -> None:
self.processingProgress.emit(message.amount)
- self.backendStateChange.emit(BackendState.Processing)
+ self.setState(BackendState.Processing)
def _invokeSlice(self) -> None:
if self._use_timer:
@@ -633,7 +634,7 @@ class CuraEngineBackend(QObject, Backend):
#
# \param message The protobuf message signalling that slicing is finished.
def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None:
- self.backendStateChange.emit(BackendState.Done)
+ self.setState(BackendState.Done)
self.processingProgress.emit(1.0)
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
@@ -821,7 +822,7 @@ class CuraEngineBackend(QObject, Backend):
extruder.propertyChanged.disconnect(self._onSettingChanged)
extruder.containersChanged.disconnect(self._onChanged)
- self._global_container_stack = self._application.getGlobalContainerStack()
+ self._global_container_stack = self._application.getMachineManager().activeMachine
if self._global_container_stack:
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
@@ -833,7 +834,10 @@ class CuraEngineBackend(QObject, Backend):
self._onChanged()
def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None:
- del self._stored_optimized_layer_data[job.getBuildPlate()]
+ if job.getBuildPlate() in self._stored_optimized_layer_data:
+ del self._stored_optimized_layer_data[job.getBuildPlate()]
+ else:
+ Logger.log("w", "The optimized layer data was already deleted for buildplate %s", job.getBuildPlate())
self._process_layers_job = None
Logger.log("d", "See if there is more to slice(2)...")
self._invokeSlice()
diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
index 3bd6d79198..3cc23130ea 100644
--- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
+++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
@@ -2,6 +2,7 @@
#Cura is released under the terms of the LGPLv3 or higher.
import gc
+import sys
from UM.Job import Job
from UM.Application import Application
@@ -95,23 +96,35 @@ class ProcessSlicedLayersJob(Job):
layer_count = len(self._layers)
# Find the minimum layer number
+ # When disabling the remove empty first layers setting, the minimum layer number will be a positive
+ # value. In that case the first empty layers will be discarded and start processing layers from the
+ # first layer with data.
# When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
- # instead simply offset all other layers so the lowest layer is always 0. It could happens that
- # the first raft layer has value -8 but there are just 4 raft (negative) layers.
- min_layer_number = 0
+ # simply offset all other layers so the lowest layer is always 0. It could happens that the first
+ # raft layer has value -8 but there are just 4 raft (negative) layers.
+ min_layer_number = sys.maxsize
negative_layers = 0
for layer in self._layers:
- if layer.id < min_layer_number:
- min_layer_number = layer.id
- if layer.id < 0:
- negative_layers += 1
+ if layer.repeatedMessageCount("path_segment") > 0:
+ if layer.id < min_layer_number:
+ min_layer_number = layer.id
+ if layer.id < 0:
+ negative_layers += 1
current_layer = 0
for layer in self._layers:
- # Negative layers are offset by the minimum layer number, but the positive layers are just
- # offset by the number of negative layers so there is no layer gap between raft and model
- abs_layer_number = layer.id + abs(min_layer_number) if layer.id < 0 else layer.id + negative_layers
+ # If the layer is below the minimum, it means that there is no data, so that we don't create a layer
+ # data. However, if there are empty layers in between, we compute them.
+ if layer.id < min_layer_number:
+ continue
+
+ # Layers are offset by the minimum layer number. In case the raft (negative layers) is being used,
+ # then the absolute layer number is adjusted by removing the empty layers that can be in between raft
+ # and the model
+ abs_layer_number = layer.id - min_layer_number
+ if layer.id >= 0 and negative_layers != 0:
+ abs_layer_number += (min_layer_number + negative_layers)
layer_data.addLayer(abs_layer_number)
this_layer = layer_data.getLayer(abs_layer_number)
@@ -124,6 +137,7 @@ class ProcessSlicedLayersJob(Job):
extruder = polygon.extruder
line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array
+
line_types = line_types.reshape((-1,1))
points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array
@@ -178,11 +192,11 @@ class ProcessSlicedLayersJob(Job):
# Find out colors per extruder
global_container_stack = Application.getInstance().getGlobalContainerStack()
manager = ExtruderManager.getInstance()
- extruders = list(manager.getMachineExtruders(global_container_stack.getId()))
+ extruders = manager.getActiveExtruderStacks()
if extruders:
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
for extruder in extruders:
- position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position
+ position = int(extruder.getMetaDataEntry("position", default = "0"))
try:
default_color = ExtrudersModel.defaultColors[position]
except IndexError:
diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py
index 0ebcafdbb2..d3882a1209 100644
--- a/plugins/CuraEngineBackend/StartSliceJob.py
+++ b/plugins/CuraEngineBackend/StartSliceJob.py
@@ -41,11 +41,15 @@ class StartJobResult(IntEnum):
## Formatter class that handles token expansion in start/end gcode
class GcodeStartEndFormatter(Formatter):
- def get_value(self, key: str, args: str, kwargs: dict, default_extruder_nr: str = "-1") -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
+ def __init__(self, default_extruder_nr: int = -1) -> None:
+ super().__init__()
+ self._default_extruder_nr = default_extruder_nr
+
+ def get_value(self, key: str, args: str, kwargs: dict) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
# The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
# and a default_extruder_nr to use when no extruder_nr is specified
- extruder_nr = int(default_extruder_nr)
+ extruder_nr = self._default_extruder_nr
key_fragments = [fragment.strip() for fragment in key.split(",")]
if len(key_fragments) == 2:
@@ -62,11 +66,19 @@ class GcodeStartEndFormatter(Formatter):
return "{" + key + "}"
key = key_fragments[0]
- try:
- return kwargs[str(extruder_nr)][key]
- except KeyError:
+
+ default_value_str = "{" + key + "}"
+ value = default_value_str
+ # "-1" is global stack, and if the setting value exists in the global stack, use it as the fallback value.
+ if key in kwargs["-1"]:
+ value = kwargs["-1"]
+ if str(extruder_nr) in kwargs and key in kwargs[str(extruder_nr)]:
+ value = kwargs[str(extruder_nr)][key]
+
+ if value == default_value_str:
Logger.log("w", "Unable to replace '%s' placeholder in start/end g-code", key)
- return "{" + key + "}"
+
+ return value
## Job class that builds up the message of scene data to send to CuraEngine.
@@ -220,8 +232,10 @@ class StartSliceJob(Job):
stack = global_stack
skip_group = False
for node in group:
+ # Only check if the printing extruder is enabled for printing meshes
+ is_non_printing_mesh = node.callDecoration("evaluateIsNonPrintingMesh")
extruder_position = node.callDecoration("getActiveExtruderPosition")
- if not extruders_enabled[extruder_position]:
+ if not is_non_printing_mesh and not extruders_enabled[extruder_position]:
skip_group = True
has_model_with_disabled_extruders = True
associated_disabled_extruders.add(extruder_position)
@@ -245,7 +259,10 @@ class StartSliceJob(Job):
self._buildGlobalInheritsStackMessage(stack)
# Build messages for extruder stacks
- for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
+ # Send the extruder settings in the order of extruder positions. Somehow, if you send e.g. extruder 3 first,
+ # then CuraEngine can slice with the wrong settings. This I think should be fixed in CuraEngine as well.
+ extruder_stack_list = sorted(list(global_stack.extruders.items()), key = lambda item: int(item[0]))
+ for _, extruder_stack in extruder_stack_list:
self._buildExtruderMessage(extruder_stack)
for group in filtered_object_groups:
@@ -268,7 +285,7 @@ class StartSliceJob(Job):
obj = group_message.addRepeatedMessage("objects")
obj.id = id(object)
-
+ obj.name = object.getName()
indices = mesh_data.getIndices()
if indices is not None:
flat_verts = numpy.take(verts, indices.flatten(), axis=0)
@@ -306,7 +323,7 @@ class StartSliceJob(Job):
value = stack.getProperty(key, "value")
result[key] = value
Job.yieldThread()
-
+
result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
result["print_temperature"] = result["material_print_temperature"]
result["time"] = time.strftime("%H:%M:%S") #Some extra settings.
@@ -331,13 +348,13 @@ class StartSliceJob(Job):
"-1": self._buildReplacementTokens(global_stack)
}
- for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
+ for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
try:
# any setting can be used as a token
- fmt = GcodeStartEndFormatter()
+ fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr)
settings = self._all_extruders_settings.copy()
settings["default_extruder_nr"] = default_extruder_nr
return str(fmt.format(value, **settings))
@@ -438,8 +455,7 @@ class StartSliceJob(Job):
Job.yieldThread()
# Ensure that the engine is aware what the build extruder is.
- if stack.getProperty("machine_extruder_count", "value") > 1:
- changed_setting_keys.add("extruder_nr")
+ changed_setting_keys.add("extruder_nr")
# Get values for all changed settings
for key in changed_setting_keys:
diff --git a/plugins/CuraEngineBackend/plugin.json b/plugins/CuraEngineBackend/plugin.json
index e5df06f228..28f0e294e7 100644
--- a/plugins/CuraEngineBackend/plugin.json
+++ b/plugins/CuraEngineBackend/plugin.json
@@ -2,7 +2,7 @@
"name": "CuraEngine Backend",
"author": "Ultimaker B.V.",
"description": "Provides the link to the CuraEngine slicing backend.",
- "api": 4,
- "version": "1.0.0",
+ "api": "6.0",
+ "version": "1.0.1",
"i18n-catalog": "cura"
}
diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py
index 5957b2cecf..11e58dac6d 100644
--- a/plugins/CuraProfileReader/CuraProfileReader.py
+++ b/plugins/CuraProfileReader/CuraProfileReader.py
@@ -50,7 +50,7 @@ class CuraProfileReader(ProfileReader):
# \param profile_id \type{str} The name of the profile.
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
def _upgradeProfile(self, serialized, profile_id):
- parser = configparser.ConfigParser(interpolation=None)
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
if "general" not in parser:
diff --git a/plugins/CuraProfileReader/plugin.json b/plugins/CuraProfileReader/plugin.json
index 004a1ade4d..169fb43360 100644
--- a/plugins/CuraProfileReader/plugin.json
+++ b/plugins/CuraProfileReader/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Cura Profile Reader",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides support for importing Cura profiles.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/CuraProfileWriter/plugin.json b/plugins/CuraProfileWriter/plugin.json
index d9accce770..9627c754d7 100644
--- a/plugins/CuraProfileWriter/plugin.json
+++ b/plugins/CuraProfileWriter/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Cura Profile Writer",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides support for exporting Cura profiles.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog":"cura"
}
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
index f01e8cb276..9c4d498d7e 100644
--- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
@@ -1,18 +1,19 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
+from typing import Set
+
from UM.Extension import Extension
from UM.Application import Application
from UM.Logger import Logger
from UM.i18n import i18nCatalog
from UM.Settings.ContainerRegistry import ContainerRegistry
-from cura.Settings.GlobalStack import GlobalStack
-
from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob
+from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage
i18n_catalog = i18nCatalog("cura")
@@ -21,35 +22,35 @@ i18n_catalog = i18nCatalog("cura")
# The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy
# to change it to work for other applications.
class FirmwareUpdateChecker(Extension):
- JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources"
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- # Initialize the Preference called `latest_checked_firmware` that stores the last version
- # checked for the UM3. In the future if we need to check other printers' firmware
- Application.getInstance().getPreferences().addPreference("info/latest_checked_firmware", "")
-
# Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the
- # 'check for updates' option
+ # "check for updates" option
Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True)
if Application.getInstance().getPreferences().getValue("info/automatic_update_check"):
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
- self._download_url = None
self._check_job = None
+ self._checked_printer_names = set() # type: Set[str]
## Callback for the message that is spawned when there is a new version.
def _onActionTriggered(self, message, action):
- if action == "download":
- if self._download_url is not None:
- QDesktopServices.openUrl(QUrl(self._download_url))
-
- def _onSetDownloadUrl(self, download_url):
- self._download_url = download_url
+ if action == FirmwareUpdateCheckerMessage.STR_ACTION_DOWNLOAD:
+ machine_id = message.getMachineId()
+ download_url = message.getDownloadUrl()
+ if download_url is not None:
+ if QDesktopServices.openUrl(QUrl(download_url)):
+ Logger.log("i", "Redirected browser to {0} to show newly available firmware.".format(download_url))
+ else:
+ Logger.log("e", "Can't reach URL: {0}".format(download_url))
+ else:
+ Logger.log("e", "Can't find URL for {0}".format(machine_id))
def _onContainerAdded(self, container):
# Only take care when a new GlobalStack was added
+ from cura.Settings.GlobalStack import GlobalStack # otherwise circular imports
if isinstance(container, GlobalStack):
self.checkFirmwareVersion(container, True)
@@ -63,13 +64,18 @@ class FirmwareUpdateChecker(Extension):
# \param silent type(boolean) Suppresses messages other than "new version found" messages.
# This is used when checking for a new firmware version at startup.
def checkFirmwareVersion(self, container = None, silent = False):
- # Do not run multiple check jobs in parallel
- if self._check_job is not None:
- Logger.log("i", "A firmware update check is already running, do nothing.")
+ container_name = container.definition.getName()
+ if container_name in self._checked_printer_names:
+ return
+ self._checked_printer_names.add(container_name)
+
+ metadata = container.definition.getMetaData().get("firmware_update_info")
+ if metadata is None:
+ Logger.log("i", "No machine with name {0} in list of firmware to check.".format(container_name))
return
- self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL,
- callback = self._onActionTriggered,
- set_download_url_callback = self._onSetDownloadUrl)
+ self._check_job = FirmwareUpdateCheckerJob(silent = silent,
+ machine_name = container_name, metadata = metadata,
+ callback = self._onActionTriggered)
self._check_job.start()
self._check_job.finished.connect(self._onJobFinished)
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
index eadacf2c02..a1460cca3f 100644
--- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
@@ -1,13 +1,18 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Message import Message
from UM.Logger import Logger
from UM.Job import Job
+from UM.Version import Version
import urllib.request
-import codecs
+from urllib.error import URLError
+from typing import Dict, Optional
+
+from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine
+from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
@@ -15,75 +20,97 @@ i18n_catalog = i18nCatalog("cura")
## This job checks if there is an update available on the provided URL.
class FirmwareUpdateCheckerJob(Job):
- def __init__(self, container = None, silent = False, url = None, callback = None, set_download_url_callback = None):
- super().__init__()
- self._container = container
- self.silent = silent
- self._url = url
- self._callback = callback
- self._set_download_url_callback = set_download_url_callback
+ STRING_ZERO_VERSION = "0.0.0"
+ STRING_EPSILON_VERSION = "0.0.1"
+ ZERO_VERSION = Version(STRING_ZERO_VERSION)
+ EPSILON_VERSION = Version(STRING_EPSILON_VERSION)
- def run(self):
- if not self._url:
- Logger.log("e", "Can not check for a new release. URL not set!")
- return
+ def __init__(self, silent, machine_name, metadata, callback) -> None:
+ super().__init__()
+ self.silent = silent
+ self._callback = callback
+
+ self._machine_name = machine_name
+ self._metadata = metadata
+ self._lookups = FirmwareUpdateCheckerLookup(self._machine_name, self._metadata)
+ self._headers = {} # type:Dict[str, str] # Don't set headers yet.
+
+ def getUrlResponse(self, url: str) -> str:
+ result = self.STRING_ZERO_VERSION
try:
- application_name = Application.getInstance().getApplicationName()
- headers = {"User-Agent": "%s - %s" % (application_name, Application.getInstance().getVersion())}
- request = urllib.request.Request(self._url, headers = headers)
- current_version_file = urllib.request.urlopen(request)
- reader = codecs.getreader("utf-8")
+ request = urllib.request.Request(url, headers = self._headers)
+ response = urllib.request.urlopen(request)
+ result = response.read().decode("utf-8")
+ except URLError:
+ Logger.log("w", "Could not reach '{0}', if this URL is old, consider removal.".format(url))
+ return result
- # get machine name from the definition container
- machine_name = self._container.definition.getName()
- machine_name_parts = machine_name.lower().split(" ")
+ def parseVersionResponse(self, response: str) -> Version:
+ raw_str = response.split("\n", 1)[0].rstrip()
+ return Version(raw_str)
+
+ def getCurrentVersion(self) -> Version:
+ max_version = self.ZERO_VERSION
+ if self._lookups is None:
+ return max_version
+
+ machine_urls = self._lookups.getCheckUrls()
+ if machine_urls is not None:
+ for url in machine_urls:
+ version = self.parseVersionResponse(self.getUrlResponse(url))
+ if version > max_version:
+ max_version = version
+
+ if max_version < self.EPSILON_VERSION:
+ Logger.log("w", "MachineID {0} not handled!".format(self._lookups.getMachineName()))
+
+ return max_version
+
+ def run(self):
+ try:
+ # Initialize a Preference that stores the last version checked for this printer.
+ Application.getInstance().getPreferences().addPreference(
+ getSettingsKeyForMachine(self._lookups.getMachineId()), "")
+
+ # Get headers
+ application_name = Application.getInstance().getApplicationName()
+ application_version = Application.getInstance().getVersion()
+ self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)}
# If it is not None, then we compare between the checked_version and the current_version
- # Now we just do that if the active printer is Ultimaker 3 or Ultimaker 3 Extended or any
- # other Ultimaker 3 that will come in the future
- if len(machine_name_parts) >= 2 and machine_name_parts[:2] == ["ultimaker", "3"]:
- Logger.log("i", "You have a UM3 in printer list. Let's check the firmware!")
+ machine_id = self._lookups.getMachineId()
+ if machine_id is not None:
+ Logger.log("i", "You have a(n) {0} in the printer list. Do firmware-check.".format(self._machine_name))
- # Nothing to parse, just get the string
- # TODO: In the future may be done by parsing a JSON file with diferent version for each printer model
- current_version = reader(current_version_file).readline().rstrip()
+ current_version = self.getCurrentVersion()
- # If it is the first time the version is checked, the checked_version is ''
- checked_version = Application.getInstance().getPreferences().getValue("info/latest_checked_firmware")
+ # This case indicates that was an error checking the version.
+ # It happens for instance when not connected to internet.
+ if current_version == self.ZERO_VERSION:
+ return
- # If the checked_version is '', it's because is the first time we check firmware and in this case
+ # If it is the first time the version is checked, the checked_version is ""
+ setting_key_str = getSettingsKeyForMachine(machine_id)
+ checked_version = Version(Application.getInstance().getPreferences().getValue(setting_key_str))
+
+ # If the checked_version is "", it's because is the first time we check firmware and in this case
# we will not show the notification, but we will store it for the next time
- Application.getInstance().getPreferences().setValue("info/latest_checked_firmware", current_version)
- Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version)
+ Application.getInstance().getPreferences().setValue(setting_key_str, current_version)
+ Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s",
+ self._machine_name, checked_version, current_version)
# The first time we want to store the current version, the notification will not be shown,
# because the new version of Cura will be release before the firmware and we don't want to
# notify the user when no new firmware version is available.
if (checked_version != "") and (checked_version != current_version):
Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE")
-
- message = Message(i18n_catalog.i18nc(
- "@info Don't translate {machine_name}, since it gets replaced by a printer name!",
- "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(
- machine_name=machine_name),
- title=i18n_catalog.i18nc(
- "@info:title The %s gets replaced with the printer name.",
- "New %s firmware available") % machine_name)
-
- message.addAction("download",
- i18n_catalog.i18nc("@action:button", "How to update"),
- "[no_icon]",
- "[no_description]",
- button_style=Message.ActionButtonStyle.LINK,
- button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT)
-
-
- # If we do this in a cool way, the download url should be available in the JSON file
- if self._set_download_url_callback:
- self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware")
+ message = FirmwareUpdateCheckerMessage(machine_id, self._machine_name,
+ self._lookups.getRedirectUserUrl())
message.actionTriggered.connect(self._callback)
message.show()
+ else:
+ Logger.log("i", "No machine with name {0} in list of firmware to check.".format(self._machine_name))
except Exception as e:
Logger.log("w", "Failed to check for new version: %s", e)
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py
new file mode 100644
index 0000000000..c78e6f6025
--- /dev/null
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import List, Optional
+
+from UM.i18n import i18nCatalog
+i18n_catalog = i18nCatalog("cura")
+
+
+def getSettingsKeyForMachine(machine_id: int) -> str:
+ return "info/latest_checked_firmware_for_{0}".format(machine_id)
+
+
+class FirmwareUpdateCheckerLookup:
+
+ def __init__(self, machine_name, machine_json) -> None:
+ # Parse all the needed lookup-tables from the ".json" file(s) in the resources folder.
+ self._machine_id = machine_json.get("id")
+ self._machine_name = machine_name.lower() # Lower in-case upper-case chars are added to the original json.
+ self._check_urls = [] # type:List[str]
+ for check_url in machine_json.get("check_urls", []):
+ self._check_urls.append(check_url)
+ self._redirect_user = machine_json.get("update_url")
+
+ def getMachineId(self) -> Optional[int]:
+ return self._machine_id
+
+ def getMachineName(self) -> Optional[int]:
+ return self._machine_name
+
+ def getCheckUrls(self) -> Optional[List[str]]:
+ return self._check_urls
+
+ def getRedirectUserUrl(self) -> Optional[str]:
+ return self._redirect_user
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py
new file mode 100644
index 0000000000..58c00850cb
--- /dev/null
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.i18n import i18nCatalog
+from UM.Message import Message
+
+i18n_catalog = i18nCatalog("cura")
+
+
+# Make a separate class, since we need an extra field: The machine-id that this message is about.
+class FirmwareUpdateCheckerMessage(Message):
+ STR_ACTION_DOWNLOAD = "download"
+
+ def __init__(self, machine_id: int, machine_name: str, download_url: str) -> None:
+ super().__init__(i18n_catalog.i18nc(
+ "@info Don't translate {machine_name}, since it gets replaced by a printer name!",
+ "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(
+ machine_name = machine_name),
+ title = i18n_catalog.i18nc(
+ "@info:title The %s gets replaced with the printer name.",
+ "New %s firmware available") % machine_name)
+
+ self._machine_id = machine_id
+ self._download_url = download_url
+
+ self.addAction(self.STR_ACTION_DOWNLOAD,
+ i18n_catalog.i18nc("@action:button", "How to update"),
+ "[no_icon]",
+ "[no_description]",
+ button_style = Message.ActionButtonStyle.LINK,
+ button_align = Message.ActionButtonAlignment.ALIGN_LEFT)
+
+ def getMachineId(self) -> int:
+ return self._machine_id
+
+ def getDownloadUrl(self) -> str:
+ return self._download_url
diff --git a/plugins/FirmwareUpdateChecker/__init__.py b/plugins/FirmwareUpdateChecker/__init__.py
index 3fae15e826..892c9c0320 100644
--- a/plugins/FirmwareUpdateChecker/__init__.py
+++ b/plugins/FirmwareUpdateChecker/__init__.py
@@ -1,12 +1,8 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from UM.i18n import i18nCatalog
-
from . import FirmwareUpdateChecker
-i18n_catalog = i18nCatalog("cura")
-
def getMetaData():
return {}
diff --git a/plugins/FirmwareUpdateChecker/plugin.json b/plugins/FirmwareUpdateChecker/plugin.json
index d6a9f9fbd7..6c55d77fd8 100644
--- a/plugins/FirmwareUpdateChecker/plugin.json
+++ b/plugins/FirmwareUpdateChecker/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Firmware Update Checker",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Checks for firmware updates.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/FirmwareUpdateChecker/tests/TestFirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/tests/TestFirmwareUpdateChecker.py
new file mode 100644
index 0000000000..cf61e46d29
--- /dev/null
+++ b/plugins/FirmwareUpdateChecker/tests/TestFirmwareUpdateChecker.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import pytest
+
+from unittest.mock import MagicMock
+
+from UM.Version import Version
+
+import FirmwareUpdateChecker
+
+json_data = \
+ {
+ "ned":
+ {
+ "id": 1,
+ "name": "ned",
+ "check_urls": [""],
+ "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware",
+ "version_parser": "default"
+ },
+ "olivia":
+ {
+ "id": 3,
+ "name": "olivia",
+ "check_urls": [""],
+ "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware",
+ "version_parser": "default"
+ },
+ "emmerson":
+ {
+ "id": 5,
+ "name": "emmerson",
+ "check_urls": [""],
+ "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware",
+ "version_parser": "default"
+ }
+ }
+
+@pytest.mark.parametrize("name, id", [
+ ("ned" , 1),
+ ("olivia" , 3),
+ ("emmerson", 5),
+])
+def test_FirmwareUpdateCheckerLookup(id, name):
+ lookup = FirmwareUpdateChecker.FirmwareUpdateCheckerLookup.FirmwareUpdateCheckerLookup(name, json_data.get(name))
+
+ assert lookup.getMachineName() == name
+ assert lookup.getMachineId() == id
+ assert len(lookup.getCheckUrls()) >= 1
+ assert lookup.getRedirectUserUrl() is not None
+
+@pytest.mark.parametrize("name, version", [
+ ("ned" , Version("5.1.2.3")),
+ ("olivia" , Version("4.3.2.1")),
+ ("emmerson", Version("6.7.8.1")),
+])
+def test_FirmwareUpdateCheckerJob_getCurrentVersion(name, version):
+ machine_data = json_data.get(name)
+ job = FirmwareUpdateChecker.FirmwareUpdateCheckerJob.FirmwareUpdateCheckerJob(False, name, machine_data, MagicMock)
+ job.getUrlResponse = MagicMock(return_value = str(version)) # Pretend like we got a good response from the server
+ assert job.getCurrentVersion() == version
diff --git a/plugins/FirmwareUpdateChecker/tests/__init__.py b/plugins/FirmwareUpdateChecker/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py
new file mode 100644
index 0000000000..e2b0041674
--- /dev/null
+++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from cura.CuraApplication import CuraApplication
+from UM.Settings.DefinitionContainer import DefinitionContainer
+from cura.MachineAction import MachineAction
+from UM.i18n import i18nCatalog
+from UM.Settings.ContainerRegistry import ContainerRegistry
+from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdateState
+
+from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject
+from typing import Optional
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater
+ from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
+ from UM.Settings.ContainerInterface import ContainerInterface
+
+catalog = i18nCatalog("cura")
+
+## Upgrade the firmware of a machine by USB with this action.
+class FirmwareUpdaterMachineAction(MachineAction):
+ def __init__(self) -> None:
+ super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Update Firmware"))
+ self._qml_url = "FirmwareUpdaterMachineAction.qml"
+ ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
+
+ self._active_output_device = None # type: Optional[PrinterOutputDevice]
+ self._active_firmware_updater = None # type: Optional[FirmwareUpdater]
+
+ CuraApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
+
+ def _onEngineCreated(self) -> None:
+ CuraApplication.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
+
+ def _onContainerAdded(self, container: "ContainerInterface") -> None:
+ # Add this action as a supported action to all machine definitions if they support USB connection
+ if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
+ CuraApplication.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
+
+ def _onOutputDevicesChanged(self) -> None:
+ if self._active_output_device and self._active_output_device.activePrinter:
+ self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.disconnect(self._onControllerCanUpdateFirmwareChanged)
+
+ output_devices = CuraApplication.getInstance().getMachineManager().printerOutputDevices
+ self._active_output_device = output_devices[0] if output_devices else None
+
+ if self._active_output_device and self._active_output_device.activePrinter:
+ self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
+
+ self.outputDeviceCanUpdateFirmwareChanged.emit()
+
+ def _onControllerCanUpdateFirmwareChanged(self) -> None:
+ self.outputDeviceCanUpdateFirmwareChanged.emit()
+
+ outputDeviceCanUpdateFirmwareChanged = pyqtSignal()
+ @pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged)
+ def firmwareUpdater(self) -> Optional["FirmwareUpdater"]:
+ if self._active_output_device and self._active_output_device.activePrinter and self._active_output_device.activePrinter.getController() is not None and self._active_output_device.activePrinter.getController().can_update_firmware:
+ self._active_firmware_updater = self._active_output_device.getFirmwareUpdater()
+ return self._active_firmware_updater
+
+ elif self._active_firmware_updater and self._active_firmware_updater.firmwareUpdateState not in [FirmwareUpdateState.idle, FirmwareUpdateState.completed]:
+ # During a firmware update, the PrinterOutputDevice is disconnected but the FirmwareUpdater is still there
+ return self._active_firmware_updater
+
+ self._active_firmware_updater = None
+ return None
diff --git a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml
new file mode 100644
index 0000000000..b5b6c15f50
--- /dev/null
+++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml
@@ -0,0 +1,191 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+import QtQuick.Layouts 1.1
+import QtQuick.Window 2.1
+import QtQuick.Dialogs 1.2 // For filedialog
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+
+Cura.MachineAction
+{
+ anchors.fill: parent;
+ property bool printerConnected: Cura.MachineManager.printerConnected
+ property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
+ property bool canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : false
+
+ Column
+ {
+ id: firmwareUpdaterMachineAction
+ anchors.fill: parent;
+ UM.I18nCatalog { id: catalog; name: "cura"}
+ spacing: UM.Theme.getSize("default_margin").height
+
+ Label
+ {
+ width: parent.width
+ text: catalog.i18nc("@title", "Update Firmware")
+ wrapMode: Text.WordWrap
+ font.pointSize: 18
+ }
+ Label
+ {
+ width: parent.width
+ wrapMode: Text.WordWrap
+ text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.")
+ }
+
+ Label
+ {
+ width: parent.width
+ wrapMode: Text.WordWrap
+ text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
+ }
+
+ Row
+ {
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: childrenRect.width
+ spacing: UM.Theme.getSize("default_margin").width
+ property string firmwareName: Cura.MachineManager.activeMachine.getDefaultFirmwareName()
+ Button
+ {
+ id: autoUpgradeButton
+ text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
+ enabled: parent.firmwareName != "" && canUpdateFirmware
+ onClicked:
+ {
+ updateProgressDialog.visible = true;
+ activeOutputDevice.updateFirmware(parent.firmwareName);
+ }
+ }
+ Button
+ {
+ id: manualUpgradeButton
+ text: catalog.i18nc("@action:button", "Upload custom Firmware");
+ enabled: canUpdateFirmware
+ onClicked:
+ {
+ customFirmwareDialog.open()
+ }
+ }
+ }
+
+ Label
+ {
+ width: parent.width
+ wrapMode: Text.WordWrap
+ visible: !printerConnected && !updateProgressDialog.visible
+ text: catalog.i18nc("@label", "Firmware can not be updated because there is no connection with the printer.");
+ }
+
+ Label
+ {
+ width: parent.width
+ wrapMode: Text.WordWrap
+ visible: printerConnected && !canUpdateFirmware
+ text: catalog.i18nc("@label", "Firmware can not be updated because the connection with the printer does not support upgrading firmware.");
+ }
+ }
+
+ FileDialog
+ {
+ id: customFirmwareDialog
+ title: catalog.i18nc("@title:window", "Select custom firmware")
+ nameFilters: "Firmware image files (*.hex)"
+ selectExisting: true
+ onAccepted:
+ {
+ updateProgressDialog.visible = true;
+ activeOutputDevice.updateFirmware(fileUrl);
+ }
+ }
+
+ UM.Dialog
+ {
+ id: updateProgressDialog
+
+ width: minimumWidth
+ minimumWidth: 500 * screenScaleFactor
+ height: minimumHeight
+ minimumHeight: 100 * screenScaleFactor
+
+ modality: Qt.ApplicationModal
+
+ title: catalog.i18nc("@title:window","Firmware Update")
+
+ Column
+ {
+ anchors.fill: parent
+
+ Label
+ {
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ }
+
+ text: {
+ if(manager.firmwareUpdater == null)
+ {
+ return "";
+ }
+ switch (manager.firmwareUpdater.firmwareUpdateState)
+ {
+ case 0:
+ return ""; //Not doing anything (eg; idling)
+ case 1:
+ return catalog.i18nc("@label","Updating firmware.");
+ case 2:
+ return catalog.i18nc("@label","Firmware update completed.");
+ case 3:
+ return catalog.i18nc("@label","Firmware update failed due to an unknown error.");
+ case 4:
+ return catalog.i18nc("@label","Firmware update failed due to an communication error.");
+ case 5:
+ return catalog.i18nc("@label","Firmware update failed due to an input/output error.");
+ case 6:
+ return catalog.i18nc("@label","Firmware update failed due to missing firmware.");
+ }
+ }
+
+ wrapMode: Text.Wrap
+ }
+
+ ProgressBar
+ {
+ id: prog
+ value: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareProgress : 0
+ minimumValue: 0
+ maximumValue: 100
+ indeterminate:
+ {
+ if(manager.firmwareUpdater == null)
+ {
+ return false;
+ }
+ return manager.firmwareUpdater.firmwareProgress < 1 && manager.firmwareUpdater.firmwareProgress > 0;
+ }
+ anchors
+ {
+ left: parent.left;
+ right: parent.right;
+ }
+ }
+ }
+
+ rightButtons: [
+ Button
+ {
+ text: catalog.i18nc("@action:button","Close");
+ enabled: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareUpdateState != 1 : true;
+ onClicked: updateProgressDialog.visible = false;
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/plugins/FirmwareUpdater/__init__.py b/plugins/FirmwareUpdater/__init__.py
new file mode 100644
index 0000000000..5a008d7d15
--- /dev/null
+++ b/plugins/FirmwareUpdater/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from . import FirmwareUpdaterMachineAction
+
+def getMetaData():
+ return {}
+
+def register(app):
+ return { "machine_action": [
+ FirmwareUpdaterMachineAction.FirmwareUpdaterMachineAction()
+ ]}
diff --git a/plugins/FirmwareUpdater/plugin.json b/plugins/FirmwareUpdater/plugin.json
new file mode 100644
index 0000000000..c1034e5e42
--- /dev/null
+++ b/plugins/FirmwareUpdater/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Firmware Updater",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.1",
+ "description": "Provides a machine actions for updating firmware.",
+ "api": "6.0",
+ "i18n-catalog": "cura"
+}
diff --git a/plugins/GCodeGzReader/GCodeGzReader.py b/plugins/GCodeGzReader/GCodeGzReader.py
index 73a08075d2..d075e4e3b0 100644
--- a/plugins/GCodeGzReader/GCodeGzReader.py
+++ b/plugins/GCodeGzReader/GCodeGzReader.py
@@ -4,15 +4,22 @@
import gzip
from UM.Mesh.MeshReader import MeshReader #The class we're extending/implementing.
+from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType #To add the .gcode.gz files to the MIME type database.
from UM.PluginRegistry import PluginRegistry
-
## A file reader that reads gzipped g-code.
#
# If you're zipping g-code, you might as well use gzip!
class GCodeGzReader(MeshReader):
def __init__(self) -> None:
super().__init__()
+ MimeTypeDatabase.addMimeType(
+ MimeType(
+ name = "application/x-cura-compressed-gcode-file",
+ comment = "Cura Compressed GCode File",
+ suffixes = ["gcode.gz"]
+ )
+ )
self._supported_extensions = [".gcode.gz"]
def _read(self, file_name):
diff --git a/plugins/GCodeGzReader/plugin.json b/plugins/GCodeGzReader/plugin.json
index e9f14724e0..d4f281682f 100644
--- a/plugins/GCodeGzReader/plugin.json
+++ b/plugins/GCodeGzReader/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Compressed G-code Reader",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Reads g-code from a compressed archive.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/GCodeGzWriter/GCodeGzWriter.py b/plugins/GCodeGzWriter/GCodeGzWriter.py
index e191a9c427..cbbfb8f986 100644
--- a/plugins/GCodeGzWriter/GCodeGzWriter.py
+++ b/plugins/GCodeGzWriter/GCodeGzWriter.py
@@ -17,6 +17,10 @@ catalog = i18nCatalog("cura")
#
# If you're zipping g-code, you might as well use gzip!
class GCodeGzWriter(MeshWriter):
+
+ def __init__(self) -> None:
+ super().__init__(add_to_recent_files = False)
+
## Writes the gzipped g-code to a stream.
#
# Note that even though the function accepts a collection of nodes, the
diff --git a/plugins/GCodeGzWriter/__init__.py b/plugins/GCodeGzWriter/__init__.py
index e257bcb011..95949eee74 100644
--- a/plugins/GCodeGzWriter/__init__.py
+++ b/plugins/GCodeGzWriter/__init__.py
@@ -16,7 +16,8 @@ def getMetaData():
"extension": file_extension,
"description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"),
"mime_type": "application/gzip",
- "mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode
+ "mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode,
+ "hide_in_file_dialog": True,
}]
}
}
diff --git a/plugins/GCodeGzWriter/plugin.json b/plugins/GCodeGzWriter/plugin.json
index 9774e9a25c..b0e6f8d605 100644
--- a/plugins/GCodeGzWriter/plugin.json
+++ b/plugins/GCodeGzWriter/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Compressed G-code Writer",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Writes g-code to a compressed archive.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/GCodeProfileReader/plugin.json b/plugins/GCodeProfileReader/plugin.json
index f8f7d4c291..af1c2d1827 100644
--- a/plugins/GCodeProfileReader/plugin.json
+++ b/plugins/GCodeProfileReader/plugin.json
@@ -1,8 +1,8 @@
{
"name": "G-code Profile Reader",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides support for importing profiles from g-code files.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py
index 10f841fc43..f8618712a1 100644
--- a/plugins/GCodeReader/FlavorParser.py
+++ b/plugins/GCodeReader/FlavorParser.py
@@ -44,6 +44,7 @@ class FlavorParser:
self._extruder_offsets = {} # type: Dict[int, List[float]] # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
self._current_layer_thickness = 0.2 # default
self._filament_diameter = 2.85 # default
+ self._previous_extrusion_value = 0.0 # keep track of the filament retractions
CuraApplication.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
@@ -106,6 +107,8 @@ class FlavorParser:
self._layer_data_builder.setLayerHeight(self._layer_number, path[0][2])
self._layer_data_builder.setLayerThickness(self._layer_number, layer_thickness)
this_layer = self._layer_data_builder.getLayer(self._layer_number)
+ if not this_layer:
+ return False
except ValueError:
return False
count = len(path)
@@ -182,6 +185,7 @@ class FlavorParser:
new_extrusion_value = params.e if self._is_absolute_extrusion else e[self._extruder_number] + params.e
if new_extrusion_value > e[self._extruder_number]:
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion
+ self._previous_extrusion_value = new_extrusion_value
else:
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction
e[self._extruder_number] = new_extrusion_value
@@ -191,6 +195,8 @@ class FlavorParser:
if z > self._previous_z and (z - self._previous_z < 1.5):
self._current_layer_thickness = z - self._previous_z # allow a tiny overlap
self._previous_z = z
+ elif self._previous_extrusion_value > e[self._extruder_number]:
+ path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType])
else:
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType])
return self._position(x, y, z, f, e)
@@ -227,6 +233,9 @@ class FlavorParser:
# Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width
self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e
position.e[self._extruder_number] = params.e
+ self._previous_extrusion_value = params.e
+ else:
+ self._previous_extrusion_value = 0.0
return self._position(
params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y,
@@ -275,7 +284,7 @@ class FlavorParser:
## For showing correct x, y offsets for each extruder
def _extruderOffsets(self) -> Dict[int, List[float]]:
result = {}
- for extruder in ExtruderManager.getInstance().getExtruderStacks():
+ for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
result[int(extruder.getMetaData().get("position", "0"))] = [
extruder.getProperty("machine_nozzle_offset_x", "value"),
extruder.getProperty("machine_nozzle_offset_y", "value")]
@@ -286,7 +295,7 @@ class FlavorParser:
self._cancelled = False
# We obtain the filament diameter from the selected extruder to calculate line widths
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
-
+
if not global_stack:
return None
@@ -329,6 +338,7 @@ class FlavorParser:
min_layer_number = 0
negative_layers = 0
previous_layer = 0
+ self._previous_extrusion_value = 0.0
for line in stream.split("\n"):
if self._cancelled:
@@ -356,6 +366,8 @@ class FlavorParser:
self._layer_type = LayerPolygon.SupportType
elif type == "FILL":
self._layer_type = LayerPolygon.InfillType
+ elif type == "SUPPORT-INTERFACE":
+ self._layer_type = LayerPolygon.SupportInterfaceType
else:
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py
index 45ef1e1058..1bc22a3e62 100755
--- a/plugins/GCodeReader/GCodeReader.py
+++ b/plugins/GCodeReader/GCodeReader.py
@@ -1,4 +1,5 @@
# Copyright (c) 2017 Aleph Objects, Inc.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.FileHandler.FileReader import FileReader
@@ -11,13 +12,7 @@ catalog = i18nCatalog("cura")
from . import MarlinFlavorParser, RepRapFlavorParser
-MimeTypeDatabase.addMimeType(
- MimeType(
- name = "application/x-cura-gcode-file",
- comment = "Cura GCode File",
- suffixes = ["gcode", "gcode.gz"]
- )
-)
+
# Class for loading and parsing G-code files
@@ -29,7 +24,15 @@ class GCodeReader(MeshReader):
def __init__(self) -> None:
super().__init__()
+ MimeTypeDatabase.addMimeType(
+ MimeType(
+ name = "application/x-cura-gcode-file",
+ comment = "Cura GCode File",
+ suffixes = ["gcode"]
+ )
+ )
self._supported_extensions = [".gcode", ".g"]
+
self._flavor_reader = None
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
diff --git a/plugins/GCodeReader/RepRapFlavorParser.py b/plugins/GCodeReader/RepRapFlavorParser.py
index ba1e13f23d..2a24d16add 100644
--- a/plugins/GCodeReader/RepRapFlavorParser.py
+++ b/plugins/GCodeReader/RepRapFlavorParser.py
@@ -1,9 +1,9 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import FlavorParser
-# This parser is intented for interpret the RepRap Firmware flavor
+## This parser is intended to interpret the RepRap Firmware g-code flavor.
class RepRapFlavorParser(FlavorParser.FlavorParser):
def __init__(self):
diff --git a/plugins/GCodeReader/plugin.json b/plugins/GCodeReader/plugin.json
index f72a8cc12c..bbc94fa917 100644
--- a/plugins/GCodeReader/plugin.json
+++ b/plugins/GCodeReader/plugin.json
@@ -1,8 +1,8 @@
{
"name": "G-code Reader",
- "author": "Victor Larchenko",
- "version": "1.0.0",
+ "author": "Victor Larchenko, Ultimaker",
+ "version": "1.0.1",
"description": "Allows loading and displaying G-code files.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py
index 59e9a29691..3e5bf59e73 100644
--- a/plugins/GCodeWriter/GCodeWriter.py
+++ b/plugins/GCodeWriter/GCodeWriter.py
@@ -47,7 +47,7 @@ class GCodeWriter(MeshWriter):
_setting_keyword = ";SETTING_"
def __init__(self):
- super().__init__()
+ super().__init__(add_to_recent_files = False)
self._application = Application.getInstance()
@@ -70,7 +70,7 @@ class GCodeWriter(MeshWriter):
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
scene = Application.getInstance().getController().getScene()
if not hasattr(scene, "gcode_dict"):
- self.setInformation(catalog.i18nc("@warning:status", "Please generate G-code before saving."))
+ self.setInformation(catalog.i18nc("@warning:status", "Please prepare G-code before exporting."))
return False
gcode_dict = getattr(scene, "gcode_dict")
gcode_list = gcode_dict.get(active_build_plate, None)
@@ -86,7 +86,7 @@ class GCodeWriter(MeshWriter):
stream.write(settings)
return True
- self.setInformation(catalog.i18nc("@warning:status", "Please generate G-code before saving."))
+ self.setInformation(catalog.i18nc("@warning:status", "Please prepare G-code before exporting."))
return False
## Create a new container with container 2 as base and container 1 written over it.
diff --git a/plugins/GCodeWriter/plugin.json b/plugins/GCodeWriter/plugin.json
index 5fcb1a3bd7..f3a95ddb78 100644
--- a/plugins/GCodeWriter/plugin.json
+++ b/plugins/GCodeWriter/plugin.json
@@ -1,8 +1,8 @@
{
"name": "G-code Writer",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Writes g-code to a file.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml
index d829f46459..b9ff2e4453 100644
--- a/plugins/ImageReader/ConfigUI.qml
+++ b/plugins/ImageReader/ConfigUI.qml
@@ -20,7 +20,7 @@ UM.Dialog
GridLayout
{
- UM.I18nCatalog{id: catalog; name:"cura"}
+ UM.I18nCatalog{id: catalog; name: "cura"}
anchors.fill: parent;
Layout.fillWidth: true
columnSpacing: 16 * screenScaleFactor
@@ -35,7 +35,7 @@ UM.Dialog
width: parent.width
Label {
- text: catalog.i18nc("@action:label","Height (mm)")
+ text: catalog.i18nc("@action:label", "Height (mm)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
@@ -58,7 +58,7 @@ UM.Dialog
width: parent.width
Label {
- text: catalog.i18nc("@action:label","Base (mm)")
+ text: catalog.i18nc("@action:label", "Base (mm)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
@@ -81,7 +81,7 @@ UM.Dialog
width: parent.width
Label {
- text: catalog.i18nc("@action:label","Width (mm)")
+ text: catalog.i18nc("@action:label", "Width (mm)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
@@ -105,7 +105,7 @@ UM.Dialog
width: parent.width
Label {
- text: catalog.i18nc("@action:label","Depth (mm)")
+ text: catalog.i18nc("@action:label", "Depth (mm)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
@@ -151,7 +151,7 @@ UM.Dialog
width: parent.width
Label {
- text: catalog.i18nc("@action:label","Smoothing")
+ text: catalog.i18nc("@action:label", "Smoothing")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
diff --git a/plugins/ImageReader/plugin.json b/plugins/ImageReader/plugin.json
index 2752c6e8f4..d966537d99 100644
--- a/plugins/ImageReader/plugin.json
+++ b/plugins/ImageReader/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Image Reader",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Enables ability to generate printable geometry from 2D image files.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/LegacyProfileReader/DictionaryOfDoom.json b/plugins/LegacyProfileReader/DictionaryOfDoom.json
index 0be413dd2c..f65cc271d1 100644
--- a/plugins/LegacyProfileReader/DictionaryOfDoom.json
+++ b/plugins/LegacyProfileReader/DictionaryOfDoom.json
@@ -1,6 +1,6 @@
{
"source_version": "15.04",
- "target_version": 3,
+ "target_version": "4.5",
"translation": {
"machine_nozzle_size": "nozzle_size",
diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py
index 93c15ca8e0..013bab6f11 100644
--- a/plugins/LegacyProfileReader/LegacyProfileReader.py
+++ b/plugins/LegacyProfileReader/LegacyProfileReader.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser # For reading the legacy profile INI files.
@@ -6,6 +6,7 @@ import io
import json # For reading the Dictionary of Doom.
import math # For mathematical operations included in the Dictionary of Doom.
import os.path # For concatenating the path to the plugin and the relative path to the Dictionary of Doom.
+from typing import Dict
from UM.Application import Application # To get the machine manager to create the new profile in.
from UM.Logger import Logger # Logging errors.
@@ -33,10 +34,11 @@ class LegacyProfileReader(ProfileReader):
# \param json The JSON file to load the default setting values from. This
# should not be a URL but a pre-loaded JSON handle.
# \return A dictionary of the default values of the legacy Cura version.
- def prepareDefaults(self, json):
+ def prepareDefaults(self, json: Dict[str, Dict[str, str]]) -> Dict[str, str]:
defaults = {}
- for key in json["defaults"]: # We have to copy over all defaults from the JSON handle to a normal dict.
- defaults[key] = json["defaults"][key]
+ if "defaults" in json:
+ for key in json["defaults"]: # We have to copy over all defaults from the JSON handle to a normal dict.
+ defaults[key] = json["defaults"][key]
return defaults
## Prepares the local variables that can be used in evaluation of computing
@@ -80,11 +82,10 @@ class LegacyProfileReader(ProfileReader):
Logger.log("i", "Importing legacy profile from file " + file_name + ".")
container_registry = ContainerRegistry.getInstance()
profile_id = container_registry.uniqueName("Imported Legacy Profile")
- profile = InstanceContainer(profile_id) # Create an empty profile.
- parser = configparser.ConfigParser(interpolation = None)
+ input_parser = configparser.ConfigParser(interpolation = None)
try:
- parser.read([file_name]) # Parse the INI file.
+ input_parser.read([file_name]) # Parse the INI file.
except Exception as e:
Logger.log("e", "Unable to open legacy profile %s: %s", file_name, str(e))
return None
@@ -92,7 +93,7 @@ class LegacyProfileReader(ProfileReader):
# Legacy Cura saved the profile under the section "profile_N" where N is the ID of a machine, except when you export in which case it saves it in the section "profile".
# Since importing multiple machine profiles is out of scope, just import the first section we find.
section = ""
- for found_section in parser.sections():
+ for found_section in input_parser.sections():
if found_section.startswith("profile"):
section = found_section
break
@@ -110,15 +111,13 @@ class LegacyProfileReader(ProfileReader):
return None
defaults = self.prepareDefaults(dict_of_doom)
- legacy_settings = self.prepareLocals(parser, section, defaults) #Gets the settings from the legacy profile.
+ legacy_settings = self.prepareLocals(input_parser, section, defaults) #Gets the settings from the legacy profile.
- #Check the target version in the Dictionary of Doom with this application version.
- if "target_version" not in dict_of_doom:
- Logger.log("e", "Dictionary of Doom has no target version. Is it the correct JSON file?")
- return None
- if InstanceContainer.Version != dict_of_doom["target_version"]:
- Logger.log("e", "Dictionary of Doom of legacy profile reader (version %s) is not in sync with the current instance container version (version %s)!", dict_of_doom["target_version"], str(InstanceContainer.Version))
- return None
+ # Serialised format into version 4.5. Do NOT upgrade this, let the version upgrader handle it.
+ output_parser = configparser.ConfigParser(interpolation = None)
+ output_parser.add_section("general")
+ output_parser.add_section("metadata")
+ output_parser.add_section("values")
if "translation" not in dict_of_doom:
Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?")
@@ -127,7 +126,7 @@ class LegacyProfileReader(ProfileReader):
quality_definition = current_printer_definition.getMetaDataEntry("quality_definition")
if not quality_definition:
quality_definition = current_printer_definition.getId()
- profile.setDefinition(quality_definition)
+ output_parser["general"]["definition"] = quality_definition
for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations.
old_setting_expression = dict_of_doom["translation"][new_setting]
compiled = compile(old_setting_expression, new_setting, "eval")
@@ -140,37 +139,34 @@ class LegacyProfileReader(ProfileReader):
definitions = current_printer_definition.findDefinitions(key = new_setting)
if definitions:
if new_value != value_using_defaults and definitions[0].default_value != new_value: # Not equal to the default in the new Cura OR the default in the legacy Cura.
- profile.setProperty(new_setting, "value", new_value) # Store the setting in the profile!
+ output_parser["values"][new_setting] = str(new_value) # Store the setting in the profile!
- if len(profile.getAllKeys()) == 0:
+ if len(output_parser["values"]) == 0:
Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.")
- profile.setMetaDataEntry("type", "profile")
- # don't know what quality_type it is based on, so use "normal" by default
- profile.setMetaDataEntry("quality_type", "normal")
- profile.setName(profile_id)
- profile.setDirty(True)
+ output_parser["general"]["version"] = "4"
+ output_parser["general"]["name"] = profile_id
+ output_parser["metadata"]["type"] = "quality_changes"
+ output_parser["metadata"]["quality_type"] = "normal" # Don't know what quality_type it is based on, so use "normal" by default.
+ output_parser["metadata"]["position"] = "0" # We only support single extrusion.
+ output_parser["metadata"]["setting_version"] = "5" # What the dictionary of doom is made for.
- #Serialise and deserialise in order to perform the version upgrade.
- parser = configparser.ConfigParser(interpolation=None)
- data = profile.serialize()
- parser.read_string(data)
- parser["general"]["version"] = "1"
- if parser.has_section("values"):
- parser["settings"] = parser["values"]
- del parser["values"]
+ # Serialise in order to perform the version upgrade.
stream = io.StringIO()
- parser.write(stream)
+ output_parser.write(stream)
data = stream.getvalue()
- profile.deserialize(data)
- # The definition can get reset to fdmprinter during the deserialization's upgrade. Here we set the definition
- # again.
- profile.setDefinition(quality_definition)
+ profile = InstanceContainer(profile_id)
+ profile.deserialize(data) # Also performs the version upgrade.
+ profile.setDirty(True)
#We need to return one extruder stack and one global stack.
global_container_id = container_registry.uniqueName("Global Imported Legacy Profile")
+ # We duplicate the extruder profile into the global stack.
+ # This may introduce some settings that are global in the extruder stack and some settings that are per-extruder in the global stack.
+ # We don't care about that. The engine will ignore them anyway.
global_profile = profile.duplicate(new_id = global_container_id, new_name = profile_id) #Needs to have the same name as the extruder profile.
+ del global_profile.getMetaData()["position"] # Has no position because it's global.
global_profile.setDirty(True)
profile_definition = "fdmprinter"
diff --git a/plugins/LegacyProfileReader/plugin.json b/plugins/LegacyProfileReader/plugin.json
index 2dc71511a9..2f5264ad37 100644
--- a/plugins/LegacyProfileReader/plugin.json
+++ b/plugins/LegacyProfileReader/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Legacy Cura Profile Reader",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides support for importing profiles from legacy Cura versions.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py
new file mode 100644
index 0000000000..480a61f301
--- /dev/null
+++ b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py
@@ -0,0 +1,190 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import configparser # An input for some functions we're testing.
+import os.path # To find the integration test .ini files.
+import pytest # To register tests with.
+import unittest.mock # To mock the application, plug-in and container registry out.
+
+import UM.Application # To mock the application out.
+import UM.PluginRegistry # To mock the plug-in registry out.
+import UM.Settings.ContainerRegistry # To mock the container registry out.
+import UM.Settings.InstanceContainer # To intercept the serialised data from the read() function.
+
+import LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module.
+from LegacyProfileReader import LegacyProfileReader # The module we're testing.
+
+@pytest.fixture
+def legacy_profile_reader():
+ return LegacyProfileReader()
+
+test_prepareDefaultsData = [
+ {
+ "defaults":
+ {
+ "foo": "bar"
+ },
+ "cheese": "delicious"
+ },
+ {
+ "cat": "fluffy",
+ "dog": "floofy"
+ }
+]
+
+@pytest.mark.parametrize("input", test_prepareDefaultsData)
+def test_prepareDefaults(legacy_profile_reader, input):
+ output = legacy_profile_reader.prepareDefaults(input)
+ if "defaults" in input:
+ assert input["defaults"] == output
+ else:
+ assert output == {}
+
+test_prepareLocalsData = [
+ ( # Ordinary case.
+ { # Parser data.
+ "profile":
+ {
+ "layer_height": "0.2",
+ "infill_density": "30"
+ }
+ },
+ { # Defaults.
+ "layer_height": "0.1",
+ "infill_density": "20",
+ "line_width": "0.4"
+ }
+ ),
+ ( # Empty data.
+ { # Parser data.
+ "profile":
+ {
+ }
+ },
+ { # Defaults.
+ }
+ ),
+ ( # All defaults.
+ { # Parser data.
+ "profile":
+ {
+ }
+ },
+ { # Defaults.
+ "foo": "bar",
+ "boo": "far"
+ }
+ ),
+ ( # Multiple config sections.
+ { # Parser data.
+ "some_other_name":
+ {
+ "foo": "bar"
+ },
+ "profile":
+ {
+ "foo": "baz" #Not the same as in some_other_name
+ }
+ },
+ { # Defaults.
+ "foo": "bla"
+ }
+ )
+]
+
+@pytest.mark.parametrize("parser_data, defaults", test_prepareLocalsData)
+def test_prepareLocals(legacy_profile_reader, parser_data, defaults):
+ parser = configparser.ConfigParser()
+ parser.read_dict(parser_data)
+
+ output = legacy_profile_reader.prepareLocals(parser, "profile", defaults)
+
+ assert set(defaults.keys()) <= set(output.keys()) # All defaults must be in there.
+ assert set(parser_data["profile"]) <= set(output.keys()) # All overwritten values must be in there.
+ for key in output:
+ if key in parser_data["profile"]:
+ assert output[key] == parser_data["profile"][key] # If overwritten, must be the overwritten value.
+ else:
+ assert output[key] == defaults[key] # Otherwise must be equal to the default.
+
+test_prepareLocalsNoSectionErrorData = [
+ ( # Section does not exist.
+ { # Parser data.
+ "some_other_name":
+ {
+ "foo": "bar"
+ },
+ },
+ { # Defaults.
+ "foo": "baz"
+ }
+ )
+]
+
+## Test cases where a key error is expected.
+@pytest.mark.parametrize("parser_data, defaults", test_prepareLocalsNoSectionErrorData)
+def test_prepareLocalsNoSectionError(legacy_profile_reader, parser_data, defaults):
+ parser = configparser.ConfigParser()
+ parser.read_dict(parser_data)
+
+ with pytest.raises(configparser.NoSectionError):
+ legacy_profile_reader.prepareLocals(parser, "profile", defaults)
+
+intercepted_data = ""
+
+@pytest.mark.parametrize("file_name", ["normal_case.ini"])
+def test_read(legacy_profile_reader, file_name):
+ # Mock out all dependencies. Quite a lot!
+ global_stack = unittest.mock.MagicMock()
+ global_stack.getProperty = unittest.mock.MagicMock(return_value = 1) # For machine_extruder_count setting.
+ def getMetaDataEntry(key, default_value = ""):
+ if key == "quality_definition":
+ return "mocked_quality_definition"
+ if key == "has_machine_quality":
+ return "True"
+ global_stack.definition.getMetaDataEntry = getMetaDataEntry
+ global_stack.definition.getId = unittest.mock.MagicMock(return_value = "mocked_global_definition")
+ application = unittest.mock.MagicMock()
+ application.getGlobalContainerStack = unittest.mock.MagicMock(return_value = global_stack)
+ application_getInstance = unittest.mock.MagicMock(return_value = application)
+ container_registry = unittest.mock.MagicMock()
+ container_registry_getInstance = unittest.mock.MagicMock(return_value = container_registry)
+ container_registry.uniqueName = unittest.mock.MagicMock(return_value = "Imported Legacy Profile")
+ container_registry.findDefinitionContainers = unittest.mock.MagicMock(return_value = [global_stack.definition])
+ UM.Settings.InstanceContainer.setContainerRegistry(container_registry)
+ plugin_registry = unittest.mock.MagicMock()
+ plugin_registry_getInstance = unittest.mock.MagicMock(return_value = plugin_registry)
+ plugin_registry.getPluginPath = unittest.mock.MagicMock(return_value = os.path.dirname(LegacyProfileReaderModule.__file__))
+
+ # Mock out the resulting InstanceContainer so that we can intercept the data before it's passed through the version upgrader.
+ def deserialize(self, data): # Intercepts the serialised data that we'd perform the version upgrade from when deserializing.
+ global intercepted_data
+ intercepted_data = data
+
+ parser = configparser.ConfigParser()
+ parser.read_string(data)
+ self._metadata["position"] = parser["metadata"]["position"]
+ def duplicate(self, new_id, new_name):
+ self._metadata["id"] = new_id
+ self._metadata["name"] = new_name
+ return self
+
+ with unittest.mock.patch.object(UM.Application.Application, "getInstance", application_getInstance):
+ with unittest.mock.patch.object(UM.Settings.ContainerRegistry.ContainerRegistry, "getInstance", container_registry_getInstance):
+ with unittest.mock.patch.object(UM.PluginRegistry.PluginRegistry, "getInstance", plugin_registry_getInstance):
+ with unittest.mock.patch.object(UM.Settings.InstanceContainer.InstanceContainer, "deserialize", deserialize):
+ with unittest.mock.patch.object(UM.Settings.InstanceContainer.InstanceContainer, "duplicate", duplicate):
+ result = legacy_profile_reader.read(os.path.join(os.path.dirname(__file__), file_name))
+
+ assert len(result) == 1
+
+ # Let's see what's inside the actual output file that we generated.
+ parser = configparser.ConfigParser()
+ parser.read_string(intercepted_data)
+ assert parser["general"]["definition"] == "mocked_quality_definition"
+ assert parser["general"]["version"] == "4" # Yes, before we upgraded.
+ assert parser["general"]["name"] == "Imported Legacy Profile" # Because we overwrote uniqueName.
+ assert parser["metadata"]["type"] == "quality_changes"
+ assert parser["metadata"]["quality_type"] == "normal"
+ assert parser["metadata"]["position"] == "0"
+ assert parser["metadata"]["setting_version"] == "5" # Yes, before we upgraded.
\ No newline at end of file
diff --git a/plugins/LegacyProfileReader/tests/normal_case.ini b/plugins/LegacyProfileReader/tests/normal_case.ini
new file mode 100644
index 0000000000..213444d2d3
--- /dev/null
+++ b/plugins/LegacyProfileReader/tests/normal_case.ini
@@ -0,0 +1,7 @@
+[profile]
+foo = bar
+boo = far
+fill_overlap = 3
+
+[alterations]
+some = values
diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.qml b/plugins/MachineSettingsAction/MachineSettingsAction.qml
index b12f8f8696..ef8fda224a 100644
--- a/plugins/MachineSettingsAction/MachineSettingsAction.qml
+++ b/plugins/MachineSettingsAction/MachineSettingsAction.qml
@@ -1,4 +1,4 @@
-// Copyright (c) 2016 Ultimaker B.V.
+// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
@@ -13,15 +13,18 @@ import Cura 1.0 as Cura
Cura.MachineAction
{
id: base
- property var extrudersModel: Cura.ExtrudersModel{}
+ property var extrudersModel: Cura.ExtrudersModel{} // Do not retrieve the Model from a backend. Otherwise the tabs
+ // in tabView will not removed/updated. Probably QML bug
property int extruderTabsCount: 0
+ property var activeMachineId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.id : ""
+
Connections
{
target: base.extrudersModel
onModelChanged:
{
- var extruderCount = base.extrudersModel.rowCount();
+ var extruderCount = base.extrudersModel.count;
base.extruderTabsCount = extruderCount;
}
}
@@ -403,7 +406,15 @@ Cura.MachineAction
{
if (settingsTabs.currentIndex > 0)
{
- manager.updateMaterialForDiameter(settingsTabs.currentIndex - 1);
+ manager.updateMaterialForDiameter(settingsTabs.currentIndex - 1)
+ }
+ }
+ function setValueFunction(value)
+ {
+ if (settingsTabs.currentIndex > 0)
+ {
+ const extruderIndex = index.toString()
+ Cura.MachineManager.activeMachine.extruders[extruderIndex].compatibleMaterialDiameter = value
}
}
property bool isExtruderSetting: true
@@ -433,6 +444,18 @@ Cura.MachineAction
property bool allowNegative: true
}
+ Loader
+ {
+ id: extruderCoolingFanNumberField
+ sourceComponent: numericTextFieldWithUnit
+ property string settingKey: "machine_extruder_cooling_fan_number"
+ property string label: catalog.i18nc("@label", "Cooling Fan Number")
+ property string unit: catalog.i18nc("@label", "")
+ property bool isExtruderSetting: true
+ property bool forceUpdateOnChange: true
+ property bool allowNegative: false
+ }
+
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
Row
@@ -511,7 +534,7 @@ Cura.MachineAction
}
return "";
}
- return Cura.MachineManager.activeMachineId;
+ return base.activeMachineId
}
key: settingKey
watchedProperties: [ "value", "description" ]
@@ -550,6 +573,7 @@ Cura.MachineAction
property bool _forceUpdateOnChange: (typeof(forceUpdateOnChange) === 'undefined') ? false : forceUpdateOnChange
property string _label: (typeof(label) === 'undefined') ? "" : label
property string _tooltip: (typeof(tooltip) === 'undefined') ? propertyProvider.properties.description : tooltip
+ property var _setValueFunction: (typeof(setValueFunction) === 'undefined') ? undefined : setValueFunction
UM.SettingPropertyProvider
{
@@ -564,7 +588,7 @@ Cura.MachineAction
}
return "";
}
- return Cura.MachineManager.activeMachineId;
+ return base.activeMachineId
}
key: settingKey
watchedProperties: [ "value", "description" ]
@@ -602,14 +626,32 @@ Cura.MachineAction
{
if (propertyProvider && text != propertyProvider.properties.value)
{
- propertyProvider.setPropertyValue("value", text);
+ // For some properties like the extruder-compatible material diameter, they need to
+ // trigger many updates, such as the available materials, the current material may
+ // need to be switched, etc. Although setting the diameter can be done directly via
+ // the provider, all the updates that need to be triggered then need to depend on
+ // the metadata update, a signal that can be fired way too often. The update functions
+ // can have if-checks to filter out the irrelevant updates, but still it incurs unnecessary
+ // overhead.
+ // The ExtruderStack class has a dedicated function for this call "setCompatibleMaterialDiameter()",
+ // and it triggers the diameter update signals only when it is needed. Here it is optionally
+ // choose to use setCompatibleMaterialDiameter() or other more specific functions that
+ // are available.
+ if (_setValueFunction !== undefined)
+ {
+ _setValueFunction(text)
+ }
+ else
+ {
+ propertyProvider.setPropertyValue("value", text)
+ }
if(_forceUpdateOnChange)
{
- manager.forceUpdate();
+ manager.forceUpdate()
}
if(_afterOnEditingFinished)
{
- _afterOnEditingFinished();
+ _afterOnEditingFinished()
}
}
}
@@ -655,7 +697,7 @@ Cura.MachineAction
}
return "";
}
- return Cura.MachineManager.activeMachineId;
+ return base.activeMachineId
}
key: settingKey
watchedProperties: [ "value", "options", "description" ]
@@ -754,7 +796,7 @@ Cura.MachineAction
}
return "";
}
- return Cura.MachineManager.activeMachineId;
+ return base.activeMachineId
}
key: settingKey
watchedProperties: [ "value", "description" ]
@@ -879,7 +921,7 @@ Cura.MachineAction
{
id: machineExtruderCountProvider
- containerStackId: Cura.MachineManager.activeMachineId
+ containerStackId: base.activeMachineId
key: "machine_extruder_count"
watchedProperties: [ "value", "description" ]
storeIndex: manager.containerIndex
@@ -889,7 +931,7 @@ Cura.MachineAction
{
id: machineHeadPolygonProvider
- containerStackId: Cura.MachineManager.activeMachineId
+ containerStackId: base.activeMachineId
key: "machine_head_with_fans_polygon"
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex
diff --git a/plugins/MachineSettingsAction/__init__.py b/plugins/MachineSettingsAction/__init__.py
index b1c4a75fec..ff80a12551 100644
--- a/plugins/MachineSettingsAction/__init__.py
+++ b/plugins/MachineSettingsAction/__init__.py
@@ -3,8 +3,6 @@
from . import MachineSettingsAction
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
def getMetaData():
return {}
diff --git a/plugins/MachineSettingsAction/plugin.json b/plugins/MachineSettingsAction/plugin.json
index 703a145deb..d734c1adf5 100644
--- a/plugins/MachineSettingsAction/plugin.json
+++ b/plugins/MachineSettingsAction/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Machine Settings action",
"author": "fieldOfView",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py
index d2c2eefac2..0619c95d67 100644
--- a/plugins/ModelChecker/ModelChecker.py
+++ b/plugins/ModelChecker/ModelChecker.py
@@ -3,12 +3,13 @@
import os
-from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty
+from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty, QTimer
from UM.Application import Application
from UM.Extension import Extension
from UM.Logger import Logger
from UM.Message import Message
+from UM.Scene.Camera import Camera
from UM.i18n import i18nCatalog
from UM.PluginRegistry import PluginRegistry
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@@ -29,13 +30,22 @@ class ModelChecker(QObject, Extension):
lifetime = 0,
title = catalog.i18nc("@info:title", "3D Model Assistant"))
+ self._change_timer = QTimer()
+ self._change_timer.setInterval(200)
+ self._change_timer.setSingleShot(True)
+ self._change_timer.timeout.connect(self.onChanged)
+
Application.getInstance().initializationFinished.connect(self._pluginsInitialized)
Application.getInstance().getController().getScene().sceneChanged.connect(self._onChanged)
Application.getInstance().globalContainerStackChanged.connect(self._onChanged)
- ## Pass-through to allow UM.Signal to connect with a pyqtSignal.
def _onChanged(self, *args, **kwargs):
- self.onChanged.emit()
+ # Ignore camera updates.
+ if len(args) == 0:
+ self._change_timer.start()
+ return
+ if not isinstance(args[0], Camera):
+ self._change_timer.start()
## Called when plug-ins are initialized.
#
diff --git a/plugins/ModelChecker/ModelChecker.qml b/plugins/ModelChecker/ModelChecker.qml
index 98db233bf8..ddeed063b1 100644
--- a/plugins/ModelChecker/ModelChecker.qml
+++ b/plugins/ModelChecker/ModelChecker.qml
@@ -4,19 +4,19 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
-import QtQuick.Layouts 1.1
-import QtQuick.Dialogs 1.1
-import QtQuick.Window 2.2
import UM 1.2 as UM
-import Cura 1.0 as Cura
Button
{
id: modelCheckerButton
- UM.I18nCatalog{id: catalog; name:"cura"}
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
visible: manager.hasWarnings
tooltip: catalog.i18nc("@info:tooltip", "Some things could be problematic in this print. Click to see tips for adjustment.")
@@ -25,6 +25,8 @@ Button
width: UM.Theme.getSize("save_button_specs_icons").width
height: UM.Theme.getSize("save_button_specs_icons").height
+ anchors.verticalCenter: parent ? parent.verticalCenter : undefined
+
style: ButtonStyle
{
background: Item
@@ -33,7 +35,6 @@ Button
{
width: UM.Theme.getSize("save_button_specs_icons").width;
height: UM.Theme.getSize("save_button_specs_icons").height;
- sourceSize.width: width;
sourceSize.height: width;
color: control.hovered ? UM.Theme.getColor("text_scene_hover") : UM.Theme.getColor("text_scene");
source: "model_checker.svg"
diff --git a/plugins/ModelChecker/__init__.py b/plugins/ModelChecker/__init__.py
index 5f4d443729..dffee21723 100644
--- a/plugins/ModelChecker/__init__.py
+++ b/plugins/ModelChecker/__init__.py
@@ -1,11 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V.
-# This example is released under the terms of the AGPLv3 or higher.
+# Cura is released under the terms of the LGPLv3 or higher.
from . import ModelChecker
-from UM.i18n import i18nCatalog
-i18n_catalog = i18nCatalog("cura")
-
def getMetaData():
return {}
diff --git a/plugins/ModelChecker/plugin.json b/plugins/ModelChecker/plugin.json
index a9190adcaa..59be5bbf0a 100644
--- a/plugins/ModelChecker/plugin.json
+++ b/plugins/ModelChecker/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Model Checker",
"author": "Ultimaker B.V.",
- "version": "0.1",
- "api": 4,
+ "version": "1.0.1",
+ "api": "6.0",
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
"i18n-catalog": "cura"
}
diff --git a/plugins/MonitorStage/MonitorMain.qml b/plugins/MonitorStage/MonitorMain.qml
new file mode 100644
index 0000000000..88193737bb
--- /dev/null
+++ b/plugins/MonitorStage/MonitorMain.qml
@@ -0,0 +1,184 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.10
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+// We show a nice overlay on the 3D viewer when the current output device has no monitor view
+Rectangle
+{
+ id: viewportOverlay
+
+ property bool isConnected: Cura.MachineManager.activeMachineHasNetworkConnection || Cura.MachineManager.activeMachineHasCloudConnection
+ property bool isNetworkConfigurable: ["Ultimaker 3", "Ultimaker 3 Extended", "Ultimaker S5"].indexOf(Cura.MachineManager.activeMachineDefinitionName) > -1
+ property bool isNetworkConfigured:
+ {
+ // Readability:
+ var connectedTypes = [2, 3];
+ var types = Cura.MachineManager.activeMachineConfiguredConnectionTypes
+
+ // Check if configured connection types includes either 2 or 3 (LAN or cloud)
+ for (var i = 0; i < types.length; i++)
+ {
+ if (connectedTypes.indexOf(types[i]) >= 0)
+ {
+ return true
+ }
+ }
+ return false
+ }
+
+ color: UM.Theme.getColor("viewport_overlay")
+ anchors.fill: parent
+
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
+
+ // This mouse area is to prevent mouse clicks to be passed onto the scene.
+ MouseArea
+ {
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+ onWheel: wheel.accepted = true
+ }
+
+ // Disable dropping files into Cura when the monitor page is active
+ DropArea
+ {
+ anchors.fill: parent
+ }
+
+ // CASE 1: CAN MONITOR & CONNECTED
+ Loader
+ {
+ id: monitorViewComponent
+
+ anchors.fill: parent
+
+ height: parent.height
+
+ property real maximumWidth: parent.width
+ property real maximumHeight: parent.height
+
+ sourceComponent: Cura.MachineManager.printerOutputDevices.length > 0 ? Cura.MachineManager.printerOutputDevices[0].monitorItem : null
+ }
+
+ // CASE 2 & 3: Empty states
+ Column
+ {
+ anchors
+ {
+ top: parent.top
+ topMargin: UM.Theme.getSize("monitor_empty_state_offset").height
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: UM.Theme.getSize("monitor_empty_state_size").width
+ spacing: UM.Theme.getSize("default_margin").height
+ visible: monitorViewComponent.sourceComponent == null
+
+ // CASE 2: CAN MONITOR & NOT CONNECTED
+ Label
+ {
+ anchors
+ {
+ horizontalCenter: parent.horizontalCenter
+ }
+ visible: isNetworkConfigured && !isConnected
+ text: catalog.i18nc("@info", "Please make sure your printer has a connection:\n- Check if the printer is turned on.\n- Check if the printer is connected to the network.")
+ font: UM.Theme.getFont("medium")
+ color: UM.Theme.getColor("monitor_text_primary")
+ wrapMode: Text.WordWrap
+ lineHeight: UM.Theme.getSize("monitor_text_line_large").height
+ lineHeightMode: Text.FixedHeight
+ width: contentWidth
+ }
+
+ // CASE 3: CAN NOT MONITOR
+ Label
+ {
+ id: noNetworkLabel
+ anchors
+ {
+ horizontalCenter: parent.horizontalCenter
+ }
+ visible: !isNetworkConfigured
+ text: catalog.i18nc("@info", "Please select a network connected printer to monitor.")
+ font: UM.Theme.getFont("medium")
+ color: UM.Theme.getColor("monitor_text_primary")
+ wrapMode: Text.WordWrap
+ width: contentWidth
+ lineHeight: UM.Theme.getSize("monitor_text_line_large").height
+ lineHeightMode: Text.FixedHeight
+ }
+ Label
+ {
+ id: noNetworkUltimakerLabel
+ anchors
+ {
+ horizontalCenter: parent.horizontalCenter
+ }
+ visible: !isNetworkConfigured && isNetworkConfigurable
+ text: catalog.i18nc("@info", "Please connect your Ultimaker printer to your local network.")
+ font: UM.Theme.getFont("medium")
+ color: UM.Theme.getColor("monitor_text_primary")
+ wrapMode: Text.WordWrap
+ width: contentWidth
+ lineHeight: UM.Theme.getSize("monitor_text_line_large").height
+ lineHeightMode: Text.FixedHeight
+ }
+ Item
+ {
+ anchors
+ {
+ left: noNetworkUltimakerLabel.left
+ }
+ visible: !isNetworkConfigured && isNetworkConfigurable
+ height: UM.Theme.getSize("monitor_text_line").height
+ width: childrenRect.width
+
+ UM.RecolorImage
+ {
+ id: externalLinkIcon
+ anchors.verticalCenter: parent.verticalCenter
+ color: UM.Theme.getColor("monitor_text_link")
+ source: UM.Theme.getIcon("external_link")
+ width: UM.Theme.getSize("monitor_external_link_icon").width
+ height: UM.Theme.getSize("monitor_external_link_icon").height
+ }
+ Label
+ {
+ id: manageQueueText
+ anchors
+ {
+ left: externalLinkIcon.right
+ leftMargin: UM.Theme.getSize("narrow_margin").width
+ verticalCenter: externalLinkIcon.verticalCenter
+ }
+ color: UM.Theme.getColor("monitor_text_link")
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ linkColor: UM.Theme.getColor("monitor_text_link")
+ text: catalog.i18nc("@label link to technical assistance", "View user manuals online")
+ renderType: Text.NativeRendering
+ }
+ MouseArea
+ {
+ anchors.fill: parent
+ hoverEnabled: true
+ onClicked: Qt.openUrlExternally("https://ultimaker.com/en/resources/manuals/ultimaker-3d-printers")
+ onEntered:
+ {
+ manageQueueText.font.underline = true
+ }
+ onExited:
+ {
+ manageQueueText.font.underline = false
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/MonitorStage/MonitorMainView.qml b/plugins/MonitorStage/MonitorMainView.qml
deleted file mode 100644
index c48f6d0aab..0000000000
--- a/plugins/MonitorStage/MonitorMainView.qml
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) 2017 Ultimaker B.V.
-
-import QtQuick 2.2
-import QtQuick.Controls 1.1
-
-import UM 1.3 as UM
-import Cura 1.0 as Cura
-
-Item
-{
- // parent could be undefined as this component is not visible at all times
- width: parent ? parent.width : 0
- height: parent ? parent.height : 0
-
- // We show a nice overlay on the 3D viewer when the current output device has no monitor view
- Rectangle
- {
- id: viewportOverlay
-
- color: UM.Theme.getColor("viewport_overlay")
- width: parent.width
- height: parent.height
-
- MouseArea
- {
- anchors.fill: parent
- acceptedButtons: Qt.AllButtons
- onWheel: wheel.accepted = true
- }
- }
-
- Loader
- {
- id: monitorViewComponent
-
- width: parent.width
- height: parent.height
-
- property real maximumWidth: parent.width
- property real maximumHeight: parent.height
-
- sourceComponent: Cura.MachineManager.printerOutputDevices.length > 0 ? Cura.MachineManager.printerOutputDevices[0].monitorItem: null
- visible: sourceComponent != null
- }
-}
diff --git a/plugins/MonitorStage/MonitorMenu.qml b/plugins/MonitorStage/MonitorMenu.qml
new file mode 100644
index 0000000000..bc95c276e8
--- /dev/null
+++ b/plugins/MonitorStage/MonitorMenu.qml
@@ -0,0 +1,23 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.3
+
+import UM 1.3 as UM
+import Cura 1.1 as Cura
+
+Item
+{
+ signal showTooltip(Item item, point location, string text)
+ signal hideTooltip()
+
+ Cura.MachineSelector
+ {
+ id: machineSelection
+ headerCornerSide: Cura.RoundedRectangle.Direction.All
+ width: UM.Theme.getSize("machine_selector_widget").width
+ height: parent.height
+ anchors.centerIn: parent
+ }
+}
\ No newline at end of file
diff --git a/plugins/MonitorStage/MonitorStage.py b/plugins/MonitorStage/MonitorStage.py
index ace201e994..69b7f20f4e 100644
--- a/plugins/MonitorStage/MonitorStage.py
+++ b/plugins/MonitorStage/MonitorStage.py
@@ -65,15 +65,10 @@ class MonitorStage(CuraStage):
# We can only connect now, as we need to be sure that everything is loaded (plugins get created quite early)
Application.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
self._onOutputDevicesChanged()
- self._updateMainOverlay()
- self._updateSidebar()
- def _updateMainOverlay(self):
- main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("MonitorStage"),
- "MonitorMainView.qml")
- self.addDisplayComponent("main", main_component_path)
-
- def _updateSidebar(self):
- sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles),
- "MonitorSidebar.qml")
- self.addDisplayComponent("sidebar", sidebar_component_path)
+ plugin_path = Application.getInstance().getPluginRegistry().getPluginPath(self.getPluginId())
+ if plugin_path is not None:
+ menu_component_path = os.path.join(plugin_path, "MonitorMenu.qml")
+ main_component_path = os.path.join(plugin_path, "MonitorMain.qml")
+ self.addDisplayComponent("menu", menu_component_path)
+ self.addDisplayComponent("main", main_component_path)
diff --git a/plugins/MonitorStage/__init__.py b/plugins/MonitorStage/__init__.py
index 884d43a8af..0468e6319b 100644
--- a/plugins/MonitorStage/__init__.py
+++ b/plugins/MonitorStage/__init__.py
@@ -3,17 +3,20 @@
from . import MonitorStage
+
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
+
def getMetaData():
return {
"stage": {
"name": i18n_catalog.i18nc("@item:inmenu", "Monitor"),
- "weight": 1
+ "weight": 2
}
}
+
def register(app):
return {
"stage": MonitorStage.MonitorStage()
diff --git a/plugins/MonitorStage/plugin.json b/plugins/MonitorStage/plugin.json
index cb3f55a80d..95e4b86f36 100644
--- a/plugins/MonitorStage/plugin.json
+++ b/plugins/MonitorStage/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Monitor Stage",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides a monitor stage in Cura.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
\ No newline at end of file
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
index a2790dcf08..0e2bd88619 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
@@ -17,7 +17,6 @@ Item {
width: childrenRect.width;
height: childrenRect.height;
-
property var all_categories_except_support: [ "machine_settings", "resolution", "shell", "infill", "material", "speed",
"travel", "cooling", "platform_adhesion", "dual", "meshfix", "blackmagic", "experimental"]
@@ -45,7 +44,7 @@ Item {
UM.SettingPropertyProvider
{
id: meshTypePropertyProvider
- containerStackId: Cura.MachineManager.activeMachineId
+ containerStack: Cura.MachineManager.activeMachine
watchedProperties: [ "enabled" ]
}
@@ -186,6 +185,12 @@ Item {
{
selectedObjectId: UM.ActiveTool.properties.getValue("SelectedObjectId")
}
+
+ // For some reason the model object is updated after removing him from the memory and
+ // it happens only on Windows. For this reason, set the destroyed value manually.
+ Component.onDestruction: {
+ setDestroyed(true);
+ }
}
delegate: Row
@@ -260,7 +265,6 @@ Item {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: width
- sourceSize.width: width
sourceSize.height: width
color: control.hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
source: UM.Theme.getIcon("minus")
@@ -402,14 +406,9 @@ Item {
function updateFilter()
{
var new_filter = {};
- if (printSequencePropertyProvider.properties.value == "one_at_a_time")
- {
- new_filter["settable_per_meshgroup"] = true;
- }
- else
- {
- new_filter["settable_per_mesh"] = true;
- }
+ new_filter["settable_per_mesh"] = true;
+ // Don't filter on "settable_per_meshgroup" any more when `printSequencePropertyProvider.properties.value`
+ // is set to "one_at_a_time", because the current backend architecture isn't ready for that.
if(filterInput.text != "")
{
@@ -518,7 +517,7 @@ Item {
{
id: machineExtruderCount
- containerStackId: Cura.MachineManager.activeMachineId
+ containerStack: Cura.MachineManager.activeMachine
key: "machine_extruder_count"
watchedProperties: [ "value" ]
storeIndex: 0
@@ -528,7 +527,7 @@ Item {
{
id: printSequencePropertyProvider
- containerStackId: Cura.MachineManager.activeMachineId
+ containerStack: Cura.MachineManager.activeMachine
key: "print_sequence"
watchedProperties: [ "value" ]
storeIndex: 0
diff --git a/plugins/PerObjectSettingsTool/plugin.json b/plugins/PerObjectSettingsTool/plugin.json
index 3254662d33..f272abf06a 100644
--- a/plugins/PerObjectSettingsTool/plugin.json
+++ b/plugins/PerObjectSettingsTool/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Per Model Settings Tool",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides the Per Model Settings.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
index da971a8e61..78f9cc0516 100644
--- a/plugins/PostProcessingPlugin/PostProcessingPlugin.py
+++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
@@ -1,6 +1,8 @@
-# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+# Copyright (c) 2018 Jaime van Kessel, Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
+from typing import Dict, Type, TYPE_CHECKING, List, Optional, cast
from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources
@@ -8,55 +10,63 @@ from UM.Application import Application
from UM.Extension import Extension
from UM.Logger import Logger
-import configparser #The script lists are stored in metadata as serialised config files.
-import io #To allow configparser to write to a string.
+import configparser # The script lists are stored in metadata as serialised config files.
+import io # To allow configparser to write to a string.
import os.path
import pkgutil
import sys
import importlib.util
from UM.i18n import i18nCatalog
+from cura.CuraApplication import CuraApplication
+
i18n_catalog = i18nCatalog("cura")
+if TYPE_CHECKING:
+ from .Script import Script
+
## The post processing plugin is an Extension type plugin that enables pre-written scripts to post process generated
# g-code files.
class PostProcessingPlugin(QObject, Extension):
- def __init__(self, parent = None):
- super().__init__(parent)
- self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup)
+ def __init__(self, parent = None) -> None:
+ QObject.__init__(self, parent)
+ Extension.__init__(self)
+ self.setMenuName(i18n_catalog.i18nc("@item:inmenu", "Post Processing"))
+ self.addMenuItem(i18n_catalog.i18nc("@item:inmenu", "Modify G-Code"), self.showPopup)
self._view = None
# Loaded scripts are all scripts that can be used
- self._loaded_scripts = {}
- self._script_labels = {}
+ self._loaded_scripts = {} # type: Dict[str, Type[Script]]
+ self._script_labels = {} # type: Dict[str, str]
# Script list contains instances of scripts in loaded_scripts.
# There can be duplicates, which will be executed in sequence.
- self._script_list = []
+ self._script_list = [] # type: List[Script]
self._selected_script_index = -1
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self.execute)
- Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) #When the current printer changes, update the list of scripts.
- Application.getInstance().mainWindowChanged.connect(self._createView) #When the main window is created, create the view so that we can display the post-processing icon if necessary.
+ Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) # When the current printer changes, update the list of scripts.
+ CuraApplication.getInstance().mainWindowChanged.connect(self._createView) # When the main window is created, create the view so that we can display the post-processing icon if necessary.
selectedIndexChanged = pyqtSignal()
- @pyqtProperty("QVariant", notify = selectedIndexChanged)
- def selectedScriptDefinitionId(self):
+
+ @pyqtProperty(str, notify = selectedIndexChanged)
+ def selectedScriptDefinitionId(self) -> Optional[str]:
try:
return self._script_list[self._selected_script_index].getDefinitionId()
- except:
+ except IndexError:
return ""
- @pyqtProperty("QVariant", notify=selectedIndexChanged)
- def selectedScriptStackId(self):
+ @pyqtProperty(str, notify=selectedIndexChanged)
+ def selectedScriptStackId(self) -> Optional[str]:
try:
return self._script_list[self._selected_script_index].getStackId()
- except:
+ except IndexError:
return ""
## Execute all post-processing scripts on the gcode.
- def execute(self, output_device):
+ def execute(self, output_device) -> None:
scene = Application.getInstance().getController().getScene()
# If the scene does not have a gcode, do nothing
if not hasattr(scene, "gcode_dict"):
@@ -66,7 +76,7 @@ class PostProcessingPlugin(QObject, Extension):
return
# get gcode list for the active build plate
- active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
gcode_list = gcode_dict[active_build_plate_id]
if not gcode_list:
return
@@ -85,16 +95,17 @@ class PostProcessingPlugin(QObject, Extension):
Logger.log("e", "Already post processed")
@pyqtSlot(int)
- def setSelectedScriptIndex(self, index):
- self._selected_script_index = index
- self.selectedIndexChanged.emit()
+ def setSelectedScriptIndex(self, index: int) -> None:
+ if self._selected_script_index != index:
+ self._selected_script_index = index
+ self.selectedIndexChanged.emit()
@pyqtProperty(int, notify = selectedIndexChanged)
- def selectedScriptIndex(self):
+ def selectedScriptIndex(self) -> int:
return self._selected_script_index
@pyqtSlot(int, int)
- def moveScript(self, index, new_index):
+ def moveScript(self, index: int, new_index: int) -> None:
if new_index < 0 or new_index > len(self._script_list) - 1:
return # nothing needs to be done
else:
@@ -106,7 +117,7 @@ class PostProcessingPlugin(QObject, Extension):
## Remove a script from the active script list by index.
@pyqtSlot(int)
- def removeScriptByIndex(self, index):
+ def removeScriptByIndex(self, index: int) -> None:
self._script_list.pop(index)
if len(self._script_list) - 1 < self._selected_script_index:
self._selected_script_index = len(self._script_list) - 1
@@ -117,14 +128,16 @@ class PostProcessingPlugin(QObject, Extension):
## Load all scripts from all paths where scripts can be found.
#
# This should probably only be done on init.
- def loadAllScripts(self):
- if self._loaded_scripts: #Already loaded.
+ def loadAllScripts(self) -> None:
+ if self._loaded_scripts: # Already loaded.
return
- #The PostProcessingPlugin path is for built-in scripts.
- #The Resources path is where the user should store custom scripts.
- #The Preferences path is legacy, where the user may previously have stored scripts.
+ # The PostProcessingPlugin path is for built-in scripts.
+ # The Resources path is where the user should store custom scripts.
+ # The Preferences path is legacy, where the user may previously have stored scripts.
for root in [PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), Resources.getStoragePath(Resources.Resources), Resources.getStoragePath(Resources.Preferences)]:
+ if root is None:
+ continue
path = os.path.join(root, "scripts")
if not os.path.isdir(path):
try:
@@ -138,7 +151,7 @@ class PostProcessingPlugin(QObject, Extension):
## Load all scripts from provided path.
# This should probably only be done on init.
# \param path Path to check for scripts.
- def loadScripts(self, path):
+ def loadScripts(self, path: str) -> None:
## Load all scripts in the scripts folders
scripts = pkgutil.iter_modules(path = [path])
for loader, script_name, ispkg in scripts:
@@ -147,6 +160,8 @@ class PostProcessingPlugin(QObject, Extension):
try:
spec = importlib.util.spec_from_file_location(__name__ + "." + script_name, os.path.join(path, script_name + ".py"))
loaded_script = importlib.util.module_from_spec(spec)
+ if spec.loader is None:
+ continue
spec.loader.exec_module(loaded_script)
sys.modules[script_name] = loaded_script #TODO: This could be a security risk. Overwrite any module with a user-provided name?
@@ -171,23 +186,24 @@ class PostProcessingPlugin(QObject, Extension):
loadedScriptListChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = loadedScriptListChanged)
- def loadedScriptList(self):
+ def loadedScriptList(self) -> List[str]:
return sorted(list(self._loaded_scripts.keys()))
@pyqtSlot(str, result = str)
- def getScriptLabelByKey(self, key):
- return self._script_labels[key]
+ def getScriptLabelByKey(self, key: str) -> Optional[str]:
+ return self._script_labels.get(key)
scriptListChanged = pyqtSignal()
- @pyqtProperty("QVariantList", notify = scriptListChanged)
- def scriptList(self):
+ @pyqtProperty("QStringList", notify = scriptListChanged)
+ def scriptList(self) -> List[str]:
script_list = [script.getSettingData()["key"] for script in self._script_list]
return script_list
@pyqtSlot(str)
- def addScriptToList(self, key):
+ def addScriptToList(self, key: str) -> None:
Logger.log("d", "Adding script %s to list.", key)
new_script = self._loaded_scripts[key]()
+ new_script.initialize()
self._script_list.append(new_script)
self.setSelectedScriptIndex(len(self._script_list) - 1)
self.scriptListChanged.emit()
@@ -195,88 +211,103 @@ class PostProcessingPlugin(QObject, Extension):
## When the global container stack is changed, swap out the list of active
# scripts.
- def _onGlobalContainerStackChanged(self):
+ def _onGlobalContainerStackChanged(self) -> None:
self.loadAllScripts()
new_stack = Application.getInstance().getGlobalContainerStack()
+ if new_stack is None:
+ return
self._script_list.clear()
- if not new_stack.getMetaDataEntry("post_processing_scripts"): #Missing or empty.
- self.scriptListChanged.emit() #Even emit this if it didn't change. We want it to write the empty list to the stack's metadata.
+ if not new_stack.getMetaDataEntry("post_processing_scripts"): # Missing or empty.
+ self.scriptListChanged.emit() # Even emit this if it didn't change. We want it to write the empty list to the stack's metadata.
return
self._script_list.clear()
scripts_list_strs = new_stack.getMetaDataEntry("post_processing_scripts")
- for script_str in scripts_list_strs.split("\n"): #Encoded config files should never contain three newlines in a row. At most 2, just before section headers.
- if not script_str: #There were no scripts in this one (or a corrupt file caused more than 3 consecutive newlines here).
+ for script_str in scripts_list_strs.split("\n"): # Encoded config files should never contain three newlines in a row. At most 2, just before section headers.
+ if not script_str: # There were no scripts in this one (or a corrupt file caused more than 3 consecutive newlines here).
continue
- script_str = script_str.replace(r"\\\n", "\n").replace(r"\\\\", "\\\\") #Unescape escape sequences.
+ script_str = script_str.replace(r"\\\n", "\n").replace(r"\\\\", "\\\\") # Unescape escape sequences.
script_parser = configparser.ConfigParser(interpolation = None)
- script_parser.optionxform = str #Don't transform the setting keys as they are case-sensitive.
+ script_parser.optionxform = str # type: ignore # Don't transform the setting keys as they are case-sensitive.
script_parser.read_string(script_str)
- for script_name, settings in script_parser.items(): #There should only be one, really! Otherwise we can't guarantee the order or allow multiple uses of the same script.
- if script_name == "DEFAULT": #ConfigParser always has a DEFAULT section, but we don't fill it. Ignore this one.
+ for script_name, settings in script_parser.items(): # There should only be one, really! Otherwise we can't guarantee the order or allow multiple uses of the same script.
+ if script_name == "DEFAULT": # ConfigParser always has a DEFAULT section, but we don't fill it. Ignore this one.
continue
- if script_name not in self._loaded_scripts: #Don't know this post-processing plug-in.
+ if script_name not in self._loaded_scripts: # Don't know this post-processing plug-in.
Logger.log("e", "Unknown post-processing script {script_name} was encountered in this global stack.".format(script_name = script_name))
continue
new_script = self._loaded_scripts[script_name]()
- for setting_key, setting_value in settings.items(): #Put all setting values into the script.
- new_script._instance.setProperty(setting_key, "value", setting_value)
+ new_script.initialize()
+ for setting_key, setting_value in settings.items(): # Put all setting values into the script.
+ if new_script._instance is not None:
+ new_script._instance.setProperty(setting_key, "value", setting_value)
self._script_list.append(new_script)
self.setSelectedScriptIndex(0)
self.scriptListChanged.emit()
@pyqtSlot()
- def writeScriptsToStack(self):
- script_list_strs = []
+ def writeScriptsToStack(self) -> None:
+ script_list_strs = [] # type: List[str]
for script in self._script_list:
- parser = configparser.ConfigParser(interpolation = None) #We'll encode the script as a config with one section. The section header is the key and its values are the settings.
- parser.optionxform = str #Don't transform the setting keys as they are case-sensitive.
+ parser = configparser.ConfigParser(interpolation = None) # We'll encode the script as a config with one section. The section header is the key and its values are the settings.
+ parser.optionxform = str # type: ignore # Don't transform the setting keys as they are case-sensitive.
script_name = script.getSettingData()["key"]
parser.add_section(script_name)
for key in script.getSettingData()["settings"]:
value = script.getSettingValueByKey(key)
parser[script_name][key] = str(value)
- serialized = io.StringIO() #ConfigParser can only write to streams. Fine.
+ serialized = io.StringIO() # ConfigParser can only write to streams. Fine.
parser.write(serialized)
serialized.seek(0)
script_str = serialized.read()
- script_str = script_str.replace("\\\\", r"\\\\").replace("\n", r"\\\n") #Escape newlines because configparser sees those as section delimiters.
+ script_str = script_str.replace("\\\\", r"\\\\").replace("\n", r"\\\n") # Escape newlines because configparser sees those as section delimiters.
script_list_strs.append(script_str)
- script_list_strs = "\n".join(script_list_strs) #ConfigParser should never output three newlines in a row when serialised, so it's a safe delimiter.
+ script_list_string = "\n".join(script_list_strs) # ConfigParser should never output three newlines in a row when serialised, so it's a safe delimiter.
global_stack = Application.getInstance().getGlobalContainerStack()
+ if global_stack is None:
+ return
+
if "post_processing_scripts" not in global_stack.getMetaData():
global_stack.setMetaDataEntry("post_processing_scripts", "")
- Application.getInstance().getGlobalContainerStack().setMetaDataEntry("post_processing_scripts", script_list_strs)
+
+ global_stack.setMetaDataEntry("post_processing_scripts", script_list_string)
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
- def _createView(self):
+ def _createView(self) -> None:
Logger.log("d", "Creating post processing plugin view.")
self.loadAllScripts()
# Create the plugin dialog component
- path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
- self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
+ path = os.path.join(cast(str, PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin")), "PostProcessingPlugin.qml")
+ self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
+ if self._view is None:
+ Logger.log("e", "Not creating PostProcessing button near save button because the QML component failed to be created.")
+ return
Logger.log("d", "Post processing view created.")
# Create the save button component
- Application.getInstance().addAdditionalComponent("saveButton", self._view.findChild(QObject, "postProcessingSaveAreaButton"))
+ CuraApplication.getInstance().addAdditionalComponent("saveButton", self._view.findChild(QObject, "postProcessingSaveAreaButton"))
## Show the (GUI) popup of the post processing plugin.
- def showPopup(self):
+ def showPopup(self) -> None:
if self._view is None:
self._createView()
+ if self._view is None:
+ Logger.log("e", "Not creating PostProcessing window since the QML component failed to be created.")
+ return
self._view.show()
## Property changed: trigger re-slice
# To do this we use the global container stack propertyChanged.
# Re-slicing is necessary for setting changes in this plugin, because the changes
# are applied only once per "fresh" gcode
- def _propertyChanged(self):
+ def _propertyChanged(self) -> None:
global_container_stack = Application.getInstance().getGlobalContainerStack()
- global_container_stack.propertyChanged.emit("post_processing_plugin", "value")
+ if global_container_stack is not None:
+ global_container_stack.propertyChanged.emit("post_processing_plugin", "value")
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml
index b8d7258ef2..cd8303d1d3 100644
--- a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml
+++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml
@@ -25,13 +25,13 @@ UM.Dialog
{
if(!visible) //Whenever the window is closed (either via the "Close" button or the X on the window frame), we want to update it in the stack.
{
- manager.writeScriptsToStack();
+ manager.writeScriptsToStack()
}
}
Item
{
- UM.I18nCatalog{id: catalog; name:"cura"}
+ UM.I18nCatalog{id: catalog; name: "cura"}
id: base
property int columnWidth: Math.round((base.width / 2) - UM.Theme.getSize("default_margin").width)
property int textMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
@@ -61,17 +61,23 @@ UM.Dialog
anchors.leftMargin: base.textMargin
anchors.right: parent.right
anchors.rightMargin: base.textMargin
- font: UM.Theme.getFont("large")
+ font: UM.Theme.getFont("large_bold")
+ elide: Text.ElideRight
}
ListView
{
id: activeScriptsList
- anchors.top: activeScriptsHeader.bottom
- anchors.topMargin: base.textMargin
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin: base.textMargin
+
+ anchors
+ {
+ top: activeScriptsHeader.bottom
+ left: parent.left
+ right: parent.right
+ rightMargin: base.textMargin
+ topMargin: base.textMargin
+ leftMargin: UM.Theme.getSize("default_margin").width
+ }
+
height: childrenRect.height
model: manager.scriptList
delegate: Item
@@ -83,8 +89,12 @@ UM.Dialog
id: activeScriptButton
text: manager.getScriptLabelByKey(modelData.toString())
exclusiveGroup: selectedScriptGroup
+ width: parent.width
+ height: UM.Theme.getSize("setting").height
checkable: true
- checked: {
+
+ checked:
+ {
if (manager.selectedScriptIndex == index)
{
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
@@ -101,8 +111,7 @@ UM.Dialog
manager.setSelectedScriptIndex(index)
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
}
- width: parent.width
- height: UM.Theme.getSize("setting").height
+
style: ButtonStyle
{
background: Rectangle
@@ -115,10 +124,12 @@ UM.Dialog
{
wrapMode: Text.Wrap
text: control.text
+ elide: Text.ElideRight
color: activeScriptButton.checked ? palette.highlightedText : palette.text
}
}
}
+
Button
{
id: removeButton
@@ -139,7 +150,6 @@ UM.Dialog
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(control.width / 2.7)
height: Math.round(control.height / 2.7)
- sourceSize.width: width
sourceSize.height: width
color: palette.text
source: UM.Theme.getIcon("cross1")
@@ -174,7 +184,6 @@ UM.Dialog
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(control.width / 2.5)
height: Math.round(control.height / 2.5)
- sourceSize.width: width
sourceSize.height: width
color: control.enabled ? palette.text : disabledPalette.text
source: UM.Theme.getIcon("arrow_bottom")
@@ -209,7 +218,6 @@ UM.Dialog
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(control.width / 2.5)
height: Math.round(control.height / 2.5)
- sourceSize.width: width
sourceSize.height: width
color: control.enabled ? palette.text : disabledPalette.text
source: UM.Theme.getIcon("arrow_top")
@@ -250,15 +258,15 @@ UM.Dialog
onTriggered: manager.addScriptToList(modelData.toString())
}
- onObjectAdded: scriptsMenu.insertItem(index, object);
- onObjectRemoved: scriptsMenu.removeItem(object);
+ onObjectAdded: scriptsMenu.insertItem(index, object)
+ onObjectRemoved: scriptsMenu.removeItem(object)
}
}
}
Rectangle
{
- color: UM.Theme.getColor("sidebar")
+ color: UM.Theme.getColor("main_background")
anchors.left: activeScripts.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
@@ -269,25 +277,35 @@ UM.Dialog
{
id: scriptSpecsHeader
text: manager.selectedScriptIndex == -1 ? catalog.i18nc("@label", "Settings") : base.activeScriptName
- anchors.top: parent.top
- anchors.topMargin: base.textMargin
- anchors.left: parent.left
- anchors.leftMargin: base.textMargin
- anchors.right: parent.right
- anchors.rightMargin: base.textMargin
+ anchors
+ {
+ top: parent.top
+ topMargin: base.textMargin
+ left: parent.left
+ leftMargin: base.textMargin
+ right: parent.right
+ rightMargin: base.textMargin
+ }
+
+ elide: Text.ElideRight
height: 20 * screenScaleFactor
- font: UM.Theme.getFont("large")
+ font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text")
}
ScrollView
{
id: scrollView
- anchors.top: scriptSpecsHeader.bottom
- anchors.topMargin: settingsPanel.textMargin
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
+ anchors
+ {
+ top: scriptSpecsHeader.bottom
+ topMargin: settingsPanel.textMargin
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ right: parent.right
+ bottom: parent.bottom
+ }
+
visible: manager.selectedScriptDefinitionId != ""
style: UM.Theme.styles.scrollview;
@@ -297,11 +315,12 @@ UM.Dialog
spacing: UM.Theme.getSize("default_lining").height
model: UM.SettingDefinitionsModel
{
- id: definitionsModel;
+ id: definitionsModel
containerId: manager.selectedScriptDefinitionId
showAll: true
}
- delegate:Loader
+
+ delegate: Loader
{
id: settingLoader
@@ -312,23 +331,24 @@ UM.Dialog
{
if(model.type != undefined)
{
- return UM.Theme.getSize("section").height;
+ return UM.Theme.getSize("section").height
}
else
{
- return 0;
+ return 0
}
}
else
{
- return 0;
+ return 0
}
-
}
Behavior on height { NumberAnimation { duration: 100 } }
opacity: provider.properties.enabled == "True" ? 1 : 0
+
Behavior on opacity { NumberAnimation { duration: 100 } }
enabled: opacity > 0
+
property var definition: model
property var settingDefinitionsModel: definitionsModel
property var propertyProvider: provider
@@ -339,11 +359,12 @@ UM.Dialog
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
asynchronous: model.type != "enum" && model.type != "extruder"
- onLoaded: {
+ onLoaded:
+ {
settingLoader.item.showRevertButton = false
settingLoader.item.showInheritButton = false
settingLoader.item.showLinkedSettingIcon = false
- settingLoader.item.doDepthIndentation = true
+ settingLoader.item.doDepthIndentation = false
settingLoader.item.doQualityUserSettingEmphasis = false
}
@@ -384,7 +405,7 @@ UM.Dialog
UM.SettingPropertyProvider
{
id: inheritStackProvider
- containerStackId: Cura.MachineManager.activeMachineId
+ containerStack: Cura.MachineManager.activeMachine
key: model.key ? model.key : "None"
watchedProperties: [ "limit_to_extruder" ]
}
@@ -395,24 +416,20 @@ UM.Dialog
onShowTooltip:
{
- tooltip.text = text;
- var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0);
- tooltip.show(position);
+ tooltip.text = text
+ var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0)
+ tooltip.show(position)
tooltip.target.x = position.x + 1
}
- onHideTooltip:
- {
- tooltip.hide();
- }
+ onHideTooltip: tooltip.hide()
}
-
}
}
}
}
- Cura.SidebarTooltip
+ Cura.PrintSetupTooltip
{
id: tooltip
}
@@ -459,6 +476,7 @@ UM.Dialog
Cura.SettingUnknown { }
}
}
+
rightButtons: Button
{
text: catalog.i18nc("@action:button", "Close")
@@ -466,44 +484,15 @@ UM.Dialog
onClicked: dialog.accept()
}
- Button {
+ Cura.SecondaryButton
+ {
objectName: "postProcessingSaveAreaButton"
visible: activeScriptsList.count > 0
- height: UM.Theme.getSize("save_button_save_to_button").height
+ height: UM.Theme.getSize("action_button").height
width: height
tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
onClicked: dialog.show()
-
- style: ButtonStyle {
- background: Rectangle {
- id: deviceSelectionIcon
- border.width: UM.Theme.getSize("default_lining").width
- border.color: !control.enabled ? UM.Theme.getColor("action_button_disabled_border") :
- control.pressed ? UM.Theme.getColor("action_button_active_border") :
- control.hovered ? UM.Theme.getColor("action_button_hovered_border") : UM.Theme.getColor("action_button_border")
- color: !control.enabled ? UM.Theme.getColor("action_button_disabled") :
- control.pressed ? UM.Theme.getColor("action_button_active") :
- control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
- Behavior on color { ColorAnimation { duration: 50; } }
- anchors.left: parent.left
- anchors.leftMargin: Math.round(UM.Theme.getSize("save_button_text_margin").width / 2);
- width: parent.height
- height: parent.height
-
- UM.RecolorImage {
- anchors.verticalCenter: parent.verticalCenter
- anchors.horizontalCenter: parent.horizontalCenter
- width: Math.round(parent.width / 2)
- height: Math.round(parent.height / 2)
- sourceSize.width: width
- sourceSize.height: height
- color: !control.enabled ? UM.Theme.getColor("action_button_disabled_text") :
- control.pressed ? UM.Theme.getColor("action_button_active_text") :
- control.hovered ? UM.Theme.getColor("action_button_hovered_text") : UM.Theme.getColor("action_button_text");
- source: "postprocessing.svg"
- }
- }
- label: Label{ }
- }
+ iconSource: "postprocessing.svg"
+ fixedWidthMode: true
}
}
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/Script.py b/plugins/PostProcessingPlugin/Script.py
index 7e430a5c78..e502f107f9 100644
--- a/plugins/PostProcessingPlugin/Script.py
+++ b/plugins/PostProcessingPlugin/Script.py
@@ -1,6 +1,8 @@
# Copyright (c) 2015 Jaime van Kessel
# Copyright (c) 2018 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+from typing import Optional, Any, Dict, TYPE_CHECKING, List
+
from UM.Signal import Signal, signalemitter
from UM.i18n import i18nCatalog
@@ -17,23 +19,27 @@ import json
import collections
i18n_catalog = i18nCatalog("cura")
+if TYPE_CHECKING:
+ from UM.Settings.Interfaces import DefinitionContainerInterface
+
## Base class for scripts. All scripts should inherit the script class.
@signalemitter
class Script:
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
- self._settings = None
- self._stack = None
+ self._stack = None # type: Optional[ContainerStack]
+ self._definition = None # type: Optional[DefinitionContainerInterface]
+ self._instance = None # type: Optional[InstanceContainer]
+ def initialize(self) -> None:
setting_data = self.getSettingData()
- self._stack = ContainerStack(stack_id = str(id(self)))
+ self._stack = ContainerStack(stack_id=str(id(self)))
self._stack.setDirty(False) # This stack does not need to be saved.
-
## Check if the definition of this script already exists. If not, add it to the registry.
if "key" in setting_data:
- definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"])
+ definitions = ContainerRegistry.getInstance().findDefinitionContainers(id=setting_data["key"])
if definitions:
# Definition was found
self._definition = definitions[0]
@@ -45,10 +51,13 @@ class Script:
except ContainerFormatError:
self._definition = None
return
+ if self._definition is None:
+ return
self._stack.addContainer(self._definition)
self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
self._instance.setDefinition(self._definition.getId())
- self._instance.setMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0))
+ self._instance.setMetaDataEntry("setting_version",
+ self._definition.getMetaDataEntry("setting_version", default=0))
self._stack.addContainer(self._instance)
self._stack.propertyChanged.connect(self._onPropertyChanged)
@@ -57,16 +66,17 @@ class Script:
settingsLoaded = Signal()
valueChanged = Signal() # Signal emitted whenever a value of a setting is changed
- def _onPropertyChanged(self, key, property_name):
+ def _onPropertyChanged(self, key: str, property_name: str) -> None:
if property_name == "value":
self.valueChanged.emit()
# Property changed: trigger reslice
# To do this we use the global container stack propertyChanged.
- # Reslicing is necessary for setting changes in this plugin, because the changes
+ # Re-slicing is necessary for setting changes in this plugin, because the changes
# are applied only once per "fresh" gcode
global_container_stack = Application.getInstance().getGlobalContainerStack()
- global_container_stack.propertyChanged.emit(key, property_name)
+ if global_container_stack is not None:
+ global_container_stack.propertyChanged.emit(key, property_name)
## Needs to return a dict that can be used to construct a settingcategory file.
# See the example script for an example.
@@ -74,30 +84,35 @@ class Script:
# Scripts can either override getSettingData directly, or use getSettingDataString
# to return a string that will be parsed as json. The latter has the benefit over
# returning a dict in that the order of settings is maintained.
- def getSettingData(self):
- setting_data = self.getSettingDataString()
- if type(setting_data) == str:
- setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict)
+ def getSettingData(self) -> Dict[str, Any]:
+ setting_data_as_string = self.getSettingDataString()
+ setting_data = json.loads(setting_data_as_string, object_pairs_hook = collections.OrderedDict)
return setting_data
- def getSettingDataString(self):
+ def getSettingDataString(self) -> str:
raise NotImplementedError()
- def getDefinitionId(self):
+ def getDefinitionId(self) -> Optional[str]:
if self._stack:
- return self._stack.getBottom().getId()
+ bottom = self._stack.getBottom()
+ if bottom is not None:
+ return bottom.getId()
+ return None
- def getStackId(self):
+ def getStackId(self) -> Optional[str]:
if self._stack:
return self._stack.getId()
+ return None
## Convenience function that retrieves value of a setting from the stack.
- def getSettingValueByKey(self, key):
- return self._stack.getProperty(key, "value")
+ def getSettingValueByKey(self, key: str) -> Any:
+ if self._stack is not None:
+ return self._stack.getProperty(key, "value")
+ return None
## Convenience function that finds the value in a line of g-code.
# When requesting key = x from line "G1 X100" the value 100 is returned.
- def getValue(self, line, key, default = None):
+ def getValue(self, line: str, key: str, default = None) -> Any:
if not key in line or (';' in line and line.find(key) > line.find(';')):
return default
sub_part = line[line.find(key) + 1:]
@@ -125,7 +140,7 @@ class Script:
# \param line The original g-code line that must be modified. If not
# provided, an entirely new g-code line will be produced.
# \return A line of g-code with the desired parameters filled in.
- def putValue(self, line = "", **kwargs):
+ def putValue(self, line: str = "", **kwargs) -> str:
#Strip the comment.
comment = ""
if ";" in line:
@@ -166,5 +181,5 @@ class Script:
## This is called when the script is executed.
# It gets a list of g-code strings and needs to return a (modified) list.
- def execute(self, data):
+ def execute(self, data: List[str]) -> List[str]:
raise NotImplementedError()
diff --git a/plugins/PostProcessingPlugin/__init__.py b/plugins/PostProcessingPlugin/__init__.py
index 85f1126136..8064d1132a 100644
--- a/plugins/PostProcessingPlugin/__init__.py
+++ b/plugins/PostProcessingPlugin/__init__.py
@@ -2,10 +2,10 @@
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
from . import PostProcessingPlugin
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
+
+
def getMetaData():
return {}
-
+
def register(app):
return {"extension": PostProcessingPlugin.PostProcessingPlugin()}
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/plugin.json b/plugins/PostProcessingPlugin/plugin.json
index ebfef8145a..1e73133c53 100644
--- a/plugins/PostProcessingPlugin/plugin.json
+++ b/plugins/PostProcessingPlugin/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Post Processing",
"author": "Ultimaker",
- "version": "2.2",
- "api": 4,
+ "version": "2.2.1",
+ "api": "6.0",
"description": "Extension that allows for user created scripts for post processing",
"catalog": "cura"
}
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py b/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py
index 54d6fdb155..be9f93c0f6 100644
--- a/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py
+++ b/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py
@@ -112,7 +112,7 @@ class ChangeAtZ(Script):
"e1_Change_speed":
{
"label": "Change Speed",
- "description": "Select if total speed (print and travel) has to be cahnged",
+ "description": "Select if total speed (print and travel) has to be changed",
"type": "bool",
"default_value": false
},
@@ -407,13 +407,13 @@ class ChangeAtZ(Script):
if "M106" in line and state < 3: #looking for fan speed
old["fanSpeed"] = self.getValue(line, "S", old["fanSpeed"])
if "M221" in line and state < 3: #looking for flow rate
- tmp_extruder = self.getValue(line,"T",None)
+ tmp_extruder = self.getValue(line, "T", None)
if tmp_extruder == None: #check if extruder is specified
old["flowrate"] = self.getValue(line, "S", old["flowrate"])
elif tmp_extruder == 0: #first extruder
old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
elif tmp_extruder == 1: #second extruder
- old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
+ old["flowrateTwo"] = self.getValue(line, "S", old["flowrateTwo"])
if ("M84" in line or "M25" in line):
if state>0 and ChangeProp["speed"]: #"finish" commands for UM Original and UM2
modified_gcode += "M220 S100 ; speed reset to 100% at the end of print\n"
diff --git a/plugins/PostProcessingPlugin/scripts/DisplayFilenameAndLayerOnLCD.py b/plugins/PostProcessingPlugin/scripts/DisplayFilenameAndLayerOnLCD.py
new file mode 100644
index 0000000000..9fd9e08d7d
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/DisplayFilenameAndLayerOnLCD.py
@@ -0,0 +1,53 @@
+# Cura PostProcessingPlugin
+# Author: Amanda de Castilho
+# Date: August 28, 2018
+
+# Description: This plugin inserts a line at the start of each layer,
+# M117 - displays the filename and layer height to the LCD
+# Alternatively, user can override the filename to display alt text + layer height
+
+from ..Script import Script
+from UM.Application import Application
+
+class DisplayFilenameAndLayerOnLCD(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name": "Display filename and layer on LCD",
+ "key": "DisplayFilenameAndLayerOnLCD",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "name":
+ {
+ "label": "text to display:",
+ "description": "By default the current filename will be displayed on the LCD. Enter text here to override the filename and display something else.",
+ "type": "str",
+ "default_value": ""
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ if self.getSettingValueByKey("name") != "":
+ name = self.getSettingValueByKey("name")
+ else:
+ name = Application.getInstance().getPrintInformation().jobName
+ lcd_text = "M117 " + name + " layer: "
+ i = 0
+ for layer in data:
+ display_text = lcd_text + str(i)
+ layer_index = data.index(layer)
+ lines = layer.split("\n")
+ for line in lines:
+ if line.startswith(";LAYER:"):
+ line_index = lines.index(line)
+ lines.insert(line_index + 1, display_text)
+ i += 1
+ final_lines = "\n".join(lines)
+ data[layer_index] = final_lines
+
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/ExampleScript.md b/plugins/PostProcessingPlugin/scripts/ExampleScript.md
new file mode 100644
index 0000000000..08652132aa
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/ExampleScript.md
@@ -0,0 +1,3 @@
+A good example script is SearchAndReplace.py.
+If you have any questions please ask them at:
+https://github.com/Ultimaker/Cura/issues
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/ExampleScript.py b/plugins/PostProcessingPlugin/scripts/ExampleScript.py
deleted file mode 100644
index 416a5f5404..0000000000
--- a/plugins/PostProcessingPlugin/scripts/ExampleScript.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
-# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
-from ..Script import Script
-
-class ExampleScript(Script):
- def __init__(self):
- super().__init__()
-
- def getSettingDataString(self):
- return """{
- "name":"Example script",
- "key": "ExampleScript",
- "metadata": {},
- "version": 2,
- "settings":
- {
- "test":
- {
- "label": "Test",
- "description": "None",
- "unit": "mm",
- "type": "float",
- "default_value": 0.5,
- "minimum_value": "0",
- "minimum_value_warning": "0.1",
- "maximum_value_warning": "1"
- },
- "derp":
- {
- "label": "zomg",
- "description": "afgasgfgasfgasf",
- "unit": "mm",
- "type": "float",
- "default_value": 0.5,
- "minimum_value": "0",
- "minimum_value_warning": "0.1",
- "maximum_value_warning": "1"
- }
- }
- }"""
-
- def execute(self, data):
- return data
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/FilamentChange.py b/plugins/PostProcessingPlugin/scripts/FilamentChange.py
index 0fa52de4f1..febb93be4c 100644
--- a/plugins/PostProcessingPlugin/scripts/FilamentChange.py
+++ b/plugins/PostProcessingPlugin/scripts/FilamentChange.py
@@ -1,5 +1,6 @@
-# This PostProcessing Plugin script is released
-# under the terms of the AGPLv3 or higher
+# Copyright (c) 2019 Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
from typing import Optional, Tuple
from UM.Logger import Logger
@@ -44,6 +45,22 @@ class FilamentChange(Script):
"unit": "mm",
"type": "float",
"default_value": 300.0
+ },
+ "x_position":
+ {
+ "label": "X Position",
+ "description": "Extruder X position. The print head will move here for filament change.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0
+ },
+ "y_position":
+ {
+ "label": "Y Position",
+ "description": "Extruder Y position. The print head will move here for filament change.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0
}
}
}"""
@@ -54,17 +71,25 @@ class FilamentChange(Script):
layer_nums = self.getSettingValueByKey("layer_number")
initial_retract = self.getSettingValueByKey("initial_retract")
later_retract = self.getSettingValueByKey("later_retract")
-
+ x_pos = self.getSettingValueByKey("x_position")
+ y_pos = self.getSettingValueByKey("y_position")
+
color_change = "M600"
-
+
if initial_retract is not None and initial_retract > 0.:
- color_change = color_change + (" E-%.2f" % initial_retract)
-
+ color_change = color_change + (" E%.2f" % initial_retract)
+
if later_retract is not None and later_retract > 0.:
- color_change = color_change + (" L-%.2f" % later_retract)
-
+ color_change = color_change + (" L%.2f" % later_retract)
+
+ if x_pos is not None:
+ color_change = color_change + (" X%.2f" % x_pos)
+
+ if y_pos is not None:
+ color_change = color_change + (" Y%.2f" % y_pos)
+
color_change = color_change + " ; Generated by FilamentChange plugin"
-
+
layer_targets = layer_nums.split(",")
if len(layer_targets) > 0:
for layer_num in layer_targets:
diff --git a/plugins/PostProcessingPlugin/scripts/PauseAtHeightRepRapFirmwareDuet.py b/plugins/PostProcessingPlugin/scripts/PauseAtHeightRepRapFirmwareDuet.py
new file mode 100644
index 0000000000..79e5d8c62d
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/PauseAtHeightRepRapFirmwareDuet.py
@@ -0,0 +1,51 @@
+from ..Script import Script
+
+class PauseAtHeightRepRapFirmwareDuet(Script):
+
+ def getSettingDataString(self):
+ return """{
+ "name": "Pause at height for RepRapFirmware DuetWifi / Duet Ethernet / Duet Maestro",
+ "key": "PauseAtHeightRepRapFirmwareDuet",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "pause_height":
+ {
+ "label": "Pause height",
+ "description": "At what height should the pause occur",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ current_z = 0.
+ pause_z = self.getSettingValueByKey("pause_height")
+
+ layers_started = False
+ for layer_number, layer in enumerate(data):
+ lines = layer.split("\n")
+ for line in lines:
+ if ";LAYER:0" in line:
+ layers_started = True
+ continue
+
+ if not layers_started:
+ continue
+
+ if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
+ current_z = self.getValue(line, 'Z')
+ if current_z != None:
+ if current_z >= pause_z:
+ prepend_gcode = ";TYPE:CUSTOM\n"
+ prepend_gcode += "; -- Pause at height (%.2f mm) --\n" % pause_z
+ prepend_gcode += self.putValue(M = 226) + "\n"
+ layer = prepend_gcode + layer
+
+ data[layer_number] = layer # Override the data of this layer with the modified data
+ return data
+ break
+ return data
diff --git a/plugins/PrepareStage/PrepareMain.qml b/plugins/PrepareStage/PrepareMain.qml
new file mode 100644
index 0000000000..bfeb62f0e8
--- /dev/null
+++ b/plugins/PrepareStage/PrepareMain.qml
@@ -0,0 +1,24 @@
+//Copyright (c) 2019 Ultimaker B.V.
+//Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.4
+import QtQuick.Controls 1.2
+import QtQuick.Layouts 1.1
+import QtQuick.Controls.Styles 1.1
+
+import UM 1.0 as UM
+import Cura 1.0 as Cura
+
+Item
+{
+ id: prepareMain
+
+ Cura.ActionPanelWidget
+ {
+ id: actionPanelWidget
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: UM.Theme.getSize("thick_margin").width
+ anchors.bottomMargin: UM.Theme.getSize("thick_margin").height
+ }
+}
\ No newline at end of file
diff --git a/plugins/PrepareStage/PrepareMenu.qml b/plugins/PrepareStage/PrepareMenu.qml
new file mode 100644
index 0000000000..b62d65254d
--- /dev/null
+++ b/plugins/PrepareStage/PrepareMenu.qml
@@ -0,0 +1,134 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.3
+
+import UM 1.3 as UM
+import Cura 1.1 as Cura
+
+import QtGraphicalEffects 1.0 // For the dropshadow
+
+Item
+{
+ id: prepareMenu
+
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
+
+ // Item to ensure that all of the buttons are nicely centered.
+ Item
+ {
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: openFileButton.width + itemRow.width + UM.Theme.getSize("default_margin").width
+ height: parent.height
+
+ RowLayout
+ {
+ id: itemRow
+
+ anchors.left: openFileButton.right
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+
+ width: Math.round(0.9 * prepareMenu.width)
+ height: parent.height
+ spacing: 0
+
+ Cura.MachineSelector
+ {
+ id: machineSelection
+ headerCornerSide: Cura.RoundedRectangle.Direction.Left
+ Layout.minimumWidth: UM.Theme.getSize("machine_selector_widget").width
+ Layout.maximumWidth: UM.Theme.getSize("machine_selector_widget").width
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+
+ // Separator line
+ Rectangle
+ {
+ height: parent.height
+ width: UM.Theme.getSize("default_lining").width
+ color: UM.Theme.getColor("lining")
+ }
+
+ Cura.ConfigurationMenu
+ {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.preferredWidth: itemRow.width - machineSelection.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width
+ }
+
+ // Separator line
+ Rectangle
+ {
+ height: parent.height
+ width: UM.Theme.getSize("default_lining").width
+ color: UM.Theme.getColor("lining")
+ }
+
+ Item
+ {
+ id: printSetupSelectorItem
+ // This is a work around to prevent the printSetupSelector from having to be re-loaded every time
+ // a stage switch is done.
+ children: [printSetupSelector]
+ height: childrenRect.height
+ width: childrenRect.width
+ }
+ }
+
+ Button
+ {
+ id: openFileButton
+ height: UM.Theme.getSize("stage_menu").height
+ width: UM.Theme.getSize("stage_menu").height
+ onClicked: Cura.Actions.open.trigger()
+ hoverEnabled: true
+
+ contentItem: Item
+ {
+ anchors.fill: parent
+ UM.RecolorImage
+ {
+ id: buttonIcon
+ anchors.centerIn: parent
+ source: UM.Theme.getIcon("load")
+ width: UM.Theme.getSize("button_icon").width
+ height: UM.Theme.getSize("button_icon").height
+ color: UM.Theme.getColor("icon")
+
+ sourceSize.height: height
+ }
+ }
+
+ background: Rectangle
+ {
+ id: background
+ height: UM.Theme.getSize("stage_menu").height
+ width: UM.Theme.getSize("stage_menu").height
+
+ radius: UM.Theme.getSize("default_radius").width
+ color: openFileButton.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
+ }
+
+ DropShadow
+ {
+ id: shadow
+ // Don't blur the shadow
+ radius: 0
+ anchors.fill: background
+ source: background
+ verticalOffset: 2
+ visible: true
+ color: UM.Theme.getColor("action_button_shadow")
+ // Should always be drawn behind the background.
+ z: background.z - 1
+ }
+ }
+ }
+}
diff --git a/plugins/PrepareStage/PrepareStage.py b/plugins/PrepareStage/PrepareStage.py
index c3c9f0a1f8..c2dee9693b 100644
--- a/plugins/PrepareStage/PrepareStage.py
+++ b/plugins/PrepareStage/PrepareStage.py
@@ -1,19 +1,19 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+
import os.path
from UM.Application import Application
-from UM.Resources import Resources
+from UM.PluginRegistry import PluginRegistry
from cura.Stages.CuraStage import CuraStage
-
## Stage for preparing model (slicing).
class PrepareStage(CuraStage):
-
def __init__(self, parent = None):
super().__init__(parent)
Application.getInstance().engineCreatedSignal.connect(self._engineCreated)
def _engineCreated(self):
- sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles),
- "PrepareSidebar.qml")
- self.addDisplayComponent("sidebar", sidebar_component_path)
+ menu_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("PrepareStage"), "PrepareMenu.qml")
+ main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("PrepareStage"), "PrepareMain.qml")
+ self.addDisplayComponent("menu", menu_component_path)
+ self.addDisplayComponent("main", main_component_path)
\ No newline at end of file
diff --git a/plugins/PrepareStage/plugin.json b/plugins/PrepareStage/plugin.json
index 4fd55e955e..dc5c68ce16 100644
--- a/plugins/PrepareStage/plugin.json
+++ b/plugins/PrepareStage/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Prepare Stage",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides a prepare stage in Cura.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
\ No newline at end of file
diff --git a/plugins/PreviewStage/PreviewMain.qml b/plugins/PreviewStage/PreviewMain.qml
new file mode 100644
index 0000000000..6b5ce2436b
--- /dev/null
+++ b/plugins/PreviewStage/PreviewMain.qml
@@ -0,0 +1,31 @@
+// Copyright (c) 2019 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.4
+import QtQuick.Controls 1.2
+import QtQuick.Layouts 1.1
+import QtQuick.Controls.Styles 1.1
+
+import UM 1.0 as UM
+import Cura 1.0 as Cura
+
+Item
+{
+ Loader
+ {
+ id: previewMain
+ anchors.fill: parent
+
+ source: UM.Controller.activeView != null && UM.Controller.activeView.mainComponent != null ? UM.Controller.activeView.mainComponent : ""
+ }
+
+ Cura.ActionPanelWidget
+ {
+ id: actionPanelWidget
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: UM.Theme.getSize("thick_margin").width
+ anchors.bottomMargin: UM.Theme.getSize("thick_margin").height
+ hasPreviewButton: false
+ }
+}
\ No newline at end of file
diff --git a/plugins/PreviewStage/PreviewMenu.qml b/plugins/PreviewStage/PreviewMenu.qml
new file mode 100644
index 0000000000..62f814aac9
--- /dev/null
+++ b/plugins/PreviewStage/PreviewMenu.qml
@@ -0,0 +1,79 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.3
+
+import UM 1.3 as UM
+import Cura 1.1 as Cura
+
+Item
+{
+ id: previewMenu
+
+ property real itemHeight: height - 2 * UM.Theme.getSize("default_lining").width
+
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
+
+ Row
+ {
+ id: stageMenuRow
+ anchors.centerIn: parent
+ height: parent.height
+ width: childrenRect.width
+
+ // We want this row to have a preferred with equals to the 85% of the parent
+ property int preferredWidth: Math.round(0.85 * previewMenu.width)
+
+ Cura.ViewsSelector
+ {
+ id: viewsSelector
+ height: parent.height
+ width: UM.Theme.getSize("views_selector").width
+ headerCornerSide: Cura.RoundedRectangle.Direction.Left
+ }
+
+ // Separator line
+ Rectangle
+ {
+ height: parent.height
+ // If there is no viewPanel, we only need a single spacer, so hide this one.
+ visible: viewPanel.source != ""
+ width: visible ? UM.Theme.getSize("default_lining").width : 0
+
+ color: UM.Theme.getColor("lining")
+ }
+
+ // This component will grow freely up to complete the preferredWidth of the row.
+ Loader
+ {
+ id: viewPanel
+ height: parent.height
+ width: source != "" ? (stageMenuRow.preferredWidth - viewsSelector.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width) : 0
+ source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : ""
+ }
+
+ // Separator line
+ Rectangle
+ {
+ height: parent.height
+ width: UM.Theme.getSize("default_lining").width
+ color: UM.Theme.getColor("lining")
+ }
+
+ Item
+ {
+ id: printSetupSelectorItem
+ // This is a work around to prevent the printSetupSelector from having to be re-loaded every time
+ // a stage switch is done.
+ children: [printSetupSelector]
+ height: childrenRect.height
+ width: childrenRect.width
+ }
+ }
+}
diff --git a/plugins/PreviewStage/PreviewStage.py b/plugins/PreviewStage/PreviewStage.py
new file mode 100644
index 0000000000..1c487c8340
--- /dev/null
+++ b/plugins/PreviewStage/PreviewStage.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import os.path
+
+from UM.Qt.QtApplication import QtApplication
+from cura.Stages.CuraStage import CuraStage
+
+from typing import TYPE_CHECKING, Optional
+
+if TYPE_CHECKING:
+ from UM.View.View import View
+
+
+## Displays a preview of what you're about to print.
+#
+# The Python component of this stage just loads PreviewMain.qml for display
+# when the stage is selected, and makes sure that it reverts to the previous
+# view when the previous stage is activated.
+class PreviewStage(CuraStage):
+ def __init__(self, application: QtApplication, parent = None) -> None:
+ super().__init__(parent)
+ self._application = application
+ self._application.engineCreatedSignal.connect(self._engineCreated)
+ self._previously_active_view = None # type: Optional[View]
+
+ ## When selecting the stage, remember which was the previous view so that
+ # we can revert to that view when we go out of the stage later.
+ def onStageSelected(self) -> None:
+ self._previously_active_view = self._application.getController().getActiveView()
+
+ ## Called when going to a different stage (away from the Preview Stage).
+ #
+ # When going to a different stage, the view should be reverted to what it
+ # was before. Normally, that just reverts it to solid view.
+ def onStageDeselected(self) -> None:
+ if self._previously_active_view is not None:
+ self._application.getController().setActiveView(self._previously_active_view.getPluginId())
+ self._previously_active_view = None
+
+ ## Delayed load of the QML files.
+ #
+ # We need to make sure that the QML engine is running before we can load
+ # these.
+ def _engineCreated(self) -> None:
+ plugin_path = self._application.getPluginRegistry().getPluginPath(self.getPluginId())
+ if plugin_path is not None:
+ menu_component_path = os.path.join(plugin_path, "PreviewMenu.qml")
+ main_component_path = os.path.join(plugin_path, "PreviewMain.qml")
+ self.addDisplayComponent("menu", menu_component_path)
+ self.addDisplayComponent("main", main_component_path)
diff --git a/plugins/PreviewStage/__init__.py b/plugins/PreviewStage/__init__.py
new file mode 100644
index 0000000000..424f573e4a
--- /dev/null
+++ b/plugins/PreviewStage/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from . import PreviewStage
+
+from UM.i18n import i18nCatalog
+i18n_catalog = i18nCatalog("cura")
+
+
+def getMetaData():
+ return {
+ "stage": {
+ "name": i18n_catalog.i18nc("@item:inmenu", "Preview"),
+ "weight": 1
+ }
+ }
+
+
+def register(app):
+ return {
+ "stage": PreviewStage.PreviewStage(app)
+ }
diff --git a/plugins/PreviewStage/plugin.json b/plugins/PreviewStage/plugin.json
new file mode 100644
index 0000000000..e1e4288bae
--- /dev/null
+++ b/plugins/PreviewStage/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Preview Stage",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.1",
+ "description": "Provides a preview stage in Cura.",
+ "api": "6.0",
+ "i18n-catalog": "cura"
+}
\ No newline at end of file
diff --git a/plugins/RemovableDriveOutputDevice/__init__.py b/plugins/RemovableDriveOutputDevice/__init__.py
index dc547b7bcc..1758801f8a 100644
--- a/plugins/RemovableDriveOutputDevice/__init__.py
+++ b/plugins/RemovableDriveOutputDevice/__init__.py
@@ -3,12 +3,10 @@
from UM.Platform import Platform
from UM.Logger import Logger
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
+
def getMetaData():
- return {
- }
+ return {}
def register(app):
if Platform.isWindows():
diff --git a/plugins/RemovableDriveOutputDevice/plugin.json b/plugins/RemovableDriveOutputDevice/plugin.json
index df11644256..5523d6b1c1 100644
--- a/plugins/RemovableDriveOutputDevice/plugin.json
+++ b/plugins/RemovableDriveOutputDevice/plugin.json
@@ -2,7 +2,7 @@
"name": "Removable Drive Output Device Plugin",
"author": "Ultimaker B.V.",
"description": "Provides removable drive hotplugging and writing support.",
- "version": "1.0.0",
- "api": 4,
+ "version": "1.0.1",
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/SimulationView/LayerSlider.qml b/plugins/SimulationView/LayerSlider.qml
index 6dcaa3f475..88f298d1f5 100644
--- a/plugins/SimulationView/LayerSlider.qml
+++ b/plugins/SimulationView/LayerSlider.qml
@@ -9,26 +9,24 @@ import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
-Item {
+Item
+{
id: sliderRoot
- // handle properties
- property real handleSize: 10
+ // Handle properties
+ property real handleSize: UM.Theme.getSize("slider_handle").width
property real handleRadius: handleSize / 2
property real minimumRangeHandleSize: handleSize / 2
- property color upperHandleColor: "black"
- property color lowerHandleColor: "black"
- property color rangeHandleColor: "black"
- property color handleActiveColor: "white"
- property real handleLabelWidth: width
+ property color upperHandleColor: UM.Theme.getColor("slider_handle")
+ property color lowerHandleColor: UM.Theme.getColor("slider_handle")
+ property color rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
+ property color handleActiveColor: UM.Theme.getColor("slider_handle_active")
property var activeHandle: upperHandle
- // track properties
- property real trackThickness: 4 // width of the slider track
- property real trackRadius: trackThickness / 2
- property color trackColor: "white"
- property real trackBorderWidth: 1 // width of the slider track border
- property color trackBorderColor: "black"
+ // Track properties
+ property real trackThickness: UM.Theme.getSize("slider_groove").width // width of the slider track
+ property real trackRadius: UM.Theme.getSize("slider_groove_radius").width
+ property color trackColor: UM.Theme.getColor("slider_groove")
// value properties
property real maximumValue: 100
@@ -39,40 +37,49 @@ Item {
property real lowerValue: minimumValue
property bool layersVisible: true
+ property bool manuallyChanged: true // Indicates whether the value was changed manually or during simulation
- function getUpperValueFromSliderHandle() {
+ function getUpperValueFromSliderHandle()
+ {
return upperHandle.getValue()
}
- function setUpperValue(value) {
+ function setUpperValue(value)
+ {
upperHandle.setValue(value)
updateRangeHandle()
}
- function getLowerValueFromSliderHandle() {
+ function getLowerValueFromSliderHandle()
+ {
return lowerHandle.getValue()
}
- function setLowerValue(value) {
+ function setLowerValue(value)
+ {
lowerHandle.setValue(value)
updateRangeHandle()
}
- function updateRangeHandle() {
+ function updateRangeHandle()
+ {
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
}
// set the active handle to show only one label at a time
- function setActiveHandle(handle) {
+ function setActiveHandle(handle)
+ {
activeHandle = handle
}
- function normalizeValue(value) {
+ function normalizeValue(value)
+ {
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
}
- // slider track
- Rectangle {
+ // Slider track
+ Rectangle
+ {
id: track
width: sliderRoot.trackThickness
@@ -80,13 +87,12 @@ Item {
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
- border.width: sliderRoot.trackBorderWidth
- border.color: sliderRoot.trackBorderColor
visible: sliderRoot.layersVisible
}
// Range handle
- Item {
+ Item
+ {
id: rangeHandle
y: upperHandle.y + upperHandle.height
@@ -95,8 +101,10 @@ Item {
anchors.horizontalCenter: sliderRoot.horizontalCenter
visible: sliderRoot.layersVisible
- // set the new value when dragging
- function onHandleDragged () {
+ // Set the new value when dragging
+ function onHandleDragged()
+ {
+ sliderRoot.manuallyChanged = true
upperHandle.y = y - upperHandle.height
lowerHandle.y = y + height
@@ -109,7 +117,14 @@ Item {
UM.SimulationView.setMinimumLayer(lowerValue)
}
- function setValue (value) {
+ function setValueManually(value)
+ {
+ sliderRoot.manuallyChanged = true
+ upperHandle.setValue(value)
+ }
+
+ function setValue(value)
+ {
var range = sliderRoot.upperValue - sliderRoot.lowerValue
value = Math.min(value, sliderRoot.maximumValue)
value = Math.max(value, sliderRoot.minimumValue + range)
@@ -118,17 +133,21 @@ Item {
UM.SimulationView.setMinimumLayer(value - range)
}
- Rectangle {
- width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
+ Rectangle
+ {
+ width: sliderRoot.trackThickness
height: parent.height + sliderRoot.handleSize
anchors.centerIn: parent
+ radius: sliderRoot.trackRadius
color: sliderRoot.rangeHandleColor
}
- MouseArea {
+ MouseArea
+ {
anchors.fill: parent
- drag {
+ drag
+ {
target: parent
axis: Drag.YAxis
minimumY: upperHandle.height
@@ -139,7 +158,8 @@ Item {
onPressed: sliderRoot.setActiveHandle(rangeHandle)
}
- SimulationSliderLabel {
+ SimulationSliderLabel
+ {
id: rangleHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@@ -152,12 +172,13 @@ Item {
maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue
busy: UM.SimulationView.busy
- setValue: rangeHandle.setValue // connect callback functions
+ setValue: rangeHandle.setValueManually // connect callback functions
}
}
// Upper handle
- Rectangle {
+ Rectangle
+ {
id: upperHandle
y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
@@ -168,10 +189,13 @@ Item {
color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
visible: sliderRoot.layersVisible
- function onHandleDragged () {
+ function onHandleDragged()
+ {
+ sliderRoot.manuallyChanged = true
// don't allow the lower handle to be heigher than the upper handle
- if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) {
+ if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize)
+ {
lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
}
@@ -183,21 +207,34 @@ Item {
}
// get the upper value based on the slider position
- function getValue () {
+ function getValue()
+ {
var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
+ function setValueManually(value)
+ {
+ sliderRoot.manuallyChanged = true
+ upperHandle.setValue(value)
+ }
+
// set the slider position based on the upper value
- function setValue (value) {
+ function setValue(value)
+ {
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
UM.SimulationView.setCurrentLayer(value)
var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
+ // In case there is only one layer, the diff value results in a NaN, so this is for catching this specific case
+ if (isNaN(diff))
+ {
+ diff = 0
+ }
var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
y = newUpperYPosition
@@ -209,10 +246,12 @@ Item {
Keys.onDownPressed: upperHandleLabel.setValue(upperHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging
- MouseArea {
+ MouseArea
+ {
anchors.fill: parent
- drag {
+ drag
+ {
target: parent
axis: Drag.YAxis
minimumY: 0
@@ -220,17 +259,19 @@ Item {
}
onPositionChanged: parent.onHandleDragged()
- onPressed: {
+ onPressed:
+ {
sliderRoot.setActiveHandle(upperHandle)
upperHandleLabel.forceActiveFocus()
}
}
- SimulationSliderLabel {
+ SimulationSliderLabel
+ {
id: upperHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
- x: parent.x - width - UM.Theme.getSize("default_margin").width
+ x: parent.x - parent.width - width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent
@@ -239,12 +280,13 @@ Item {
maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue
busy: UM.SimulationView.busy
- setValue: upperHandle.setValue // connect callback functions
+ setValue: upperHandle.setValueManually // connect callback functions
}
}
// Lower handle
- Rectangle {
+ Rectangle
+ {
id: lowerHandle
y: sliderRoot.height - sliderRoot.handleSize
@@ -256,10 +298,13 @@ Item {
visible: sliderRoot.layersVisible
- function onHandleDragged () {
+ function onHandleDragged()
+ {
+ sliderRoot.manuallyChanged = true
// don't allow the upper handle to be lower than the lower handle
- if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) {
+ if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize)
+ {
upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
}
@@ -271,21 +316,35 @@ Item {
}
// get the lower value from the current slider position
- function getValue () {
+ function getValue()
+ {
var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
+ function setValueManually(value)
+ {
+ sliderRoot.manuallyChanged = true
+ lowerHandle.setValue(value)
+ }
+
// set the slider position based on the lower value
- function setValue (value) {
+ function setValue(value)
+ {
+
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
UM.SimulationView.setMinimumLayer(value)
var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
+ // In case there is only one layer, the diff value results in a NaN, so this is for catching this specific case
+ if (isNaN(diff))
+ {
+ diff = 0
+ }
var newLowerYPosition = Math.round((sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) + diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
y = newLowerYPosition
@@ -297,10 +356,12 @@ Item {
Keys.onDownPressed: lowerHandleLabel.setValue(lowerHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging
- MouseArea {
+ MouseArea
+ {
anchors.fill: parent
- drag {
+ drag
+ {
target: parent
axis: Drag.YAxis
minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
@@ -308,26 +369,28 @@ Item {
}
onPositionChanged: parent.onHandleDragged()
- onPressed: {
+ onPressed:
+ {
sliderRoot.setActiveHandle(lowerHandle)
lowerHandleLabel.forceActiveFocus()
}
}
- SimulationSliderLabel {
+ SimulationSliderLabel
+ {
id: lowerHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
- x: parent.x - width - UM.Theme.getSize("default_margin").width
+ x: parent.x - parent.width - width
anchors.verticalCenter: parent.verticalCenter
- target: Qt.point(sliderRoot.width, y + height / 2)
+ target: Qt.point(sliderRoot.width + width, y + height / 2)
visible: sliderRoot.activeHandle == parent
// custom properties
maximumValue: sliderRoot.maximumValue
value: sliderRoot.lowerValue
busy: UM.SimulationView.busy
- setValue: lowerHandle.setValue // connect callback functions
+ setValue: lowerHandle.setValueManually // connect callback functions
}
}
-}
+}
\ No newline at end of file
diff --git a/plugins/SimulationView/PathSlider.qml b/plugins/SimulationView/PathSlider.qml
index 999912e3ba..c7a43c6407 100644
--- a/plugins/SimulationView/PathSlider.qml
+++ b/plugins/SimulationView/PathSlider.qml
@@ -9,23 +9,22 @@ import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
-Item {
+Item
+{
id: sliderRoot
// handle properties
- property real handleSize: 10
+ property real handleSize: UM.Theme.getSize("slider_handle").width
property real handleRadius: handleSize / 2
- property color handleColor: "black"
- property color handleActiveColor: "white"
- property color rangeColor: "black"
+ property color handleColor: UM.Theme.getColor("slider_handle")
+ property color handleActiveColor: UM.Theme.getColor("slider_handle_active")
+ property color rangeColor: UM.Theme.getColor("slider_groove_fill")
property real handleLabelWidth: width
// track properties
- property real trackThickness: 4 // width of the slider track
- property real trackRadius: trackThickness / 2
- property color trackColor: "white"
- property real trackBorderWidth: 1 // width of the slider track border
- property color trackBorderColor: "black"
+ property real trackThickness: UM.Theme.getSize("slider_groove").width
+ property real trackRadius: UM.Theme.getSize("slider_groove_radius").width
+ property color trackColor: UM.Theme.getColor("slider_groove")
// value properties
property real maximumValue: 100
@@ -34,26 +33,32 @@ Item {
property real handleValue: maximumValue
property bool pathsVisible: true
+ property bool manuallyChanged: true // Indicates whether the value was changed manually or during simulation
- function getHandleValueFromSliderHandle () {
+ function getHandleValueFromSliderHandle()
+ {
return handle.getValue()
}
- function setHandleValue (value) {
+ function setHandleValue(value)
+ {
handle.setValue(value)
updateRangeHandle()
}
- function updateRangeHandle () {
+ function updateRangeHandle()
+ {
rangeHandle.width = handle.x - sliderRoot.handleSize
}
- function normalizeValue(value) {
+ function normalizeValue(value)
+ {
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
}
// slider track
- Rectangle {
+ Rectangle
+ {
id: track
width: sliderRoot.width - sliderRoot.handleSize
@@ -61,13 +66,12 @@ Item {
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
- border.width: sliderRoot.trackBorderWidth
- border.color: sliderRoot.trackBorderColor
visible: sliderRoot.pathsVisible
}
// Progress indicator
- Item {
+ Item
+ {
id: rangeHandle
x: handle.width
@@ -76,16 +80,19 @@ Item {
anchors.verticalCenter: sliderRoot.verticalCenter
visible: sliderRoot.pathsVisible
- Rectangle {
- height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
+ Rectangle
+ {
+ height: sliderRoot.trackThickness
width: parent.width + sliderRoot.handleSize
anchors.centerIn: parent
+ radius: sliderRoot.trackRadius
color: sliderRoot.rangeColor
}
}
// Handle
- Rectangle {
+ Rectangle
+ {
id: handle
x: sliderRoot.handleSize
@@ -96,7 +103,9 @@ Item {
color: handleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.handleColor
visible: sliderRoot.pathsVisible
- function onHandleDragged () {
+ function onHandleDragged()
+ {
+ sliderRoot.manuallyChanged = true
// update the range handle
sliderRoot.updateRangeHandle()
@@ -106,15 +115,23 @@ Item {
}
// get the value based on the slider position
- function getValue () {
+ function getValue()
+ {
var result = x / (sliderRoot.width - sliderRoot.handleSize)
result = result * sliderRoot.maximumValue
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
+ function setValueManually(value)
+ {
+ sliderRoot.manuallyChanged = true
+ handle.setValue(value)
+ }
+
// set the slider position based on the value
- function setValue (value) {
+ function setValue(value)
+ {
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
@@ -132,23 +149,23 @@ Item {
Keys.onLeftPressed: handleLabel.setValue(handleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging
- MouseArea {
+ MouseArea
+ {
anchors.fill: parent
- drag {
+ drag
+ {
target: parent
axis: Drag.XAxis
minimumX: 0
maximumX: sliderRoot.width - sliderRoot.handleSize
}
- onPressed: {
- handleLabel.forceActiveFocus()
- }
-
+ onPressed: handleLabel.forceActiveFocus()
onPositionChanged: parent.onHandleDragged()
}
- SimulationSliderLabel {
+ SimulationSliderLabel
+ {
id: handleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@@ -162,7 +179,7 @@ Item {
maximumValue: sliderRoot.maximumValue
value: sliderRoot.handleValue
busy: UM.SimulationView.busy
- setValue: handle.setValue // connect callback functions
+ setValue: handle.setValueManually // connect callback functions
}
}
}
diff --git a/plugins/SimulationView/SimulationSliderLabel.qml b/plugins/SimulationView/SimulationSliderLabel.qml
index 94167d001e..06c6a51b44 100644
--- a/plugins/SimulationView/SimulationSliderLabel.qml
+++ b/plugins/SimulationView/SimulationSliderLabel.qml
@@ -44,12 +44,11 @@ UM.PointingRectangle {
id: valueLabel
anchors {
- left: parent.left
- leftMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter
+ horizontalCenter: parent.horizontalCenter
}
- width: maximumValue.toString().length * 12 * screenScaleFactor
+ width: ((maximumValue + 1).toString().length + 1) * 10 * screenScaleFactor
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
horizontalAlignment: TextInput.AlignRight
diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py
index 44643dbf1c..3b2db2efac 100644
--- a/plugins/SimulationView/SimulationView.py
+++ b/plugins/SimulationView/SimulationView.py
@@ -16,43 +16,55 @@ from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Message import Message
from UM.Platform import Platform
from UM.PluginRegistry import PluginRegistry
+from UM.Qt.QtApplication import QtApplication
from UM.Resources import Resources
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
+
from UM.Scene.Selection import Selection
from UM.Signal import Signal
+from UM.View.CompositePass import CompositePass
from UM.View.GL.OpenGL import OpenGL
from UM.View.GL.OpenGLContext import OpenGLContext
-from UM.View.View import View
+from UM.View.GL.ShaderProgram import ShaderProgram
+
from UM.i18n import i18nCatalog
+from cura.CuraView import CuraView
from cura.Scene.ConvexHullNode import ConvexHullNode
from cura.CuraApplication import CuraApplication
from .NozzleNode import NozzleNode
from .SimulationPass import SimulationPass
from .SimulationViewProxy import SimulationViewProxy
-
-catalog = i18nCatalog("cura")
-
import numpy
import os.path
+from typing import Optional, TYPE_CHECKING, List, cast
+
+if TYPE_CHECKING:
+ from UM.Scene.SceneNode import SceneNode
+ from UM.Scene.Scene import Scene
+ from UM.Settings.ContainerStack import ContainerStack
+
+catalog = i18nCatalog("cura")
+
+
## View used to display g-code paths.
-class SimulationView(View):
- # Must match SimulationView.qml
+class SimulationView(CuraView):
+ # Must match SimulationViewMenuComponent.qml
LAYER_VIEW_TYPE_MATERIAL_TYPE = 0
LAYER_VIEW_TYPE_LINE_TYPE = 1
LAYER_VIEW_TYPE_FEEDRATE = 2
LAYER_VIEW_TYPE_THICKNESS = 3
- def __init__(self):
- super().__init__()
+ def __init__(self, parent = None) -> None:
+ super().__init__(parent)
self._max_layers = 0
self._current_layer_num = 0
self._minimum_layer_num = 0
self._current_layer_mesh = None
self._current_layer_jumps = None
- self._top_layers_job = None
+ self._top_layers_job = None # type: Optional["_CreateTopLayersJob"]
self._activity = False
self._old_max_layers = 0
@@ -64,21 +76,21 @@ class SimulationView(View):
self._busy = False
self._simulation_running = False
- self._ghost_shader = None
- self._layer_pass = None
- self._composite_pass = None
- self._old_layer_bindings = None
- self._simulationview_composite_shader = None
- self._old_composite_shader = None
+ self._ghost_shader = None # type: Optional["ShaderProgram"]
+ self._layer_pass = None # type: Optional[SimulationPass]
+ self._composite_pass = None # type: Optional[CompositePass]
+ self._old_layer_bindings = None # type: Optional[List[str]]
+ self._simulationview_composite_shader = None # type: Optional["ShaderProgram"]
+ self._old_composite_shader = None # type: Optional["ShaderProgram"]
- self._global_container_stack = None
+ self._global_container_stack = None # type: Optional[ContainerStack]
self._proxy = SimulationViewProxy()
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._resetSettings()
self._legend_items = None
self._show_travel_moves = False
- self._nozzle_node = None
+ self._nozzle_node = None # type: Optional[NozzleNode]
Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5)
Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False)
@@ -102,29 +114,39 @@ class SimulationView(View):
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
title = catalog.i18nc("@info:title", "Simulation View"))
- def _evaluateCompatibilityMode(self):
+ QtApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
+
+ def _onEngineCreated(self) -> None:
+ plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
+ if plugin_path:
+ self.addDisplayComponent("main", os.path.join(plugin_path, "SimulationViewMainComponent.qml"))
+ self.addDisplayComponent("menu", os.path.join(plugin_path, "SimulationViewMenuComponent.qml"))
+ else:
+ Logger.log("e", "Unable to find the path for %s", self.getPluginId())
+
+ def _evaluateCompatibilityMode(self) -> bool:
return OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode"))
- def _resetSettings(self):
- self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
+ def _resetSettings(self) -> None:
+ self._layer_view_type = 0 # type: int # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
self._extruder_count = 0
self._extruder_opacity = [1.0, 1.0, 1.0, 1.0]
- self._show_travel_moves = 0
- self._show_helpers = 1
- self._show_skin = 1
- self._show_infill = 1
+ self._show_travel_moves = False
+ self._show_helpers = True
+ self._show_skin = True
+ self._show_infill = True
self.resetLayerData()
- def getActivity(self):
+ def getActivity(self) -> bool:
return self._activity
- def setActivity(self, activity):
+ def setActivity(self, activity: bool) -> None:
if self._activity == activity:
return
self._activity = activity
self.activityChanged.emit()
- def getSimulationPass(self):
+ def getSimulationPass(self) -> SimulationPass:
if not self._layer_pass:
# Currently the RenderPass constructor requires a size > 0
# This should be fixed in RenderPass's constructor.
@@ -133,30 +155,30 @@ class SimulationView(View):
self._layer_pass.setSimulationView(self)
return self._layer_pass
- def getCurrentLayer(self):
+ def getCurrentLayer(self) -> int:
return self._current_layer_num
- def getMinimumLayer(self):
+ def getMinimumLayer(self) -> int:
return self._minimum_layer_num
- def getMaxLayers(self):
+ def getMaxLayers(self) -> int:
return self._max_layers
- def getCurrentPath(self):
+ def getCurrentPath(self) -> int:
return self._current_path_num
- def getMinimumPath(self):
+ def getMinimumPath(self) -> int:
return self._minimum_path_num
- def getMaxPaths(self):
+ def getMaxPaths(self) -> int:
return self._max_paths
- def getNozzleNode(self):
+ def getNozzleNode(self) -> NozzleNode:
if not self._nozzle_node:
self._nozzle_node = NozzleNode()
return self._nozzle_node
- def _onSceneChanged(self, node):
+ def _onSceneChanged(self, node: "SceneNode") -> None:
if node.getMeshData() is None:
self.resetLayerData()
@@ -164,21 +186,21 @@ class SimulationView(View):
self.calculateMaxLayers()
self.calculateMaxPathsOnLayer(self._current_layer_num)
- def isBusy(self):
+ def isBusy(self) -> bool:
return self._busy
- def setBusy(self, busy):
+ def setBusy(self, busy: bool) -> None:
if busy != self._busy:
self._busy = busy
self.busyChanged.emit()
- def isSimulationRunning(self):
+ def isSimulationRunning(self) -> bool:
return self._simulation_running
- def setSimulationRunning(self, running):
+ def setSimulationRunning(self, running: bool) -> None:
self._simulation_running = running
- def resetLayerData(self):
+ def resetLayerData(self) -> None:
self._current_layer_mesh = None
self._current_layer_jumps = None
self._max_feedrate = sys.float_info.min
@@ -186,15 +208,17 @@ class SimulationView(View):
self._max_thickness = sys.float_info.min
self._min_thickness = sys.float_info.max
- def beginRendering(self):
+ def beginRendering(self) -> None:
scene = self.getController().getScene()
renderer = self.getRenderer()
if not self._ghost_shader:
self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
- self._ghost_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_ghost").getRgb()))
+ theme = CuraApplication.getInstance().getTheme()
+ if theme is not None:
+ self._ghost_shader.setUniformValue("u_color", Color(*theme.getColor("layerview_ghost").getRgb()))
- for node in DepthFirstIterator(scene.getRoot()):
+ for node in DepthFirstIterator(scene.getRoot()): # type: ignore
# We do not want to render ConvexHullNode as it conflicts with the bottom layers.
# However, it is somewhat relevant when the node is selected, so do render it then.
if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
@@ -204,7 +228,7 @@ class SimulationView(View):
if (node.getMeshData()) and node.isVisible():
renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
- def setLayer(self, value):
+ def setLayer(self, value: int) -> None:
if self._current_layer_num != value:
self._current_layer_num = value
if self._current_layer_num < 0:
@@ -218,7 +242,7 @@ class SimulationView(View):
self.currentLayerNumChanged.emit()
- def setMinimumLayer(self, value):
+ def setMinimumLayer(self, value: int) -> None:
if self._minimum_layer_num != value:
self._minimum_layer_num = value
if self._minimum_layer_num < 0:
@@ -232,7 +256,7 @@ class SimulationView(View):
self.currentLayerNumChanged.emit()
- def setPath(self, value):
+ def setPath(self, value: int) -> None:
if self._current_path_num != value:
self._current_path_num = value
if self._current_path_num < 0:
@@ -246,7 +270,7 @@ class SimulationView(View):
self.currentPathNumChanged.emit()
- def setMinimumPath(self, value):
+ def setMinimumPath(self, value: int) -> None:
if self._minimum_path_num != value:
self._minimum_path_num = value
if self._minimum_path_num < 0:
@@ -263,24 +287,24 @@ class SimulationView(View):
## Set the layer view type
#
# \param layer_view_type integer as in SimulationView.qml and this class
- def setSimulationViewType(self, layer_view_type):
+ def setSimulationViewType(self, layer_view_type: int) -> None:
self._layer_view_type = layer_view_type
self.currentLayerNumChanged.emit()
## Return the layer view type, integer as in SimulationView.qml and this class
- def getSimulationViewType(self):
+ def getSimulationViewType(self) -> int:
return self._layer_view_type
## Set the extruder opacity
#
# \param extruder_nr 0..3
# \param opacity 0.0 .. 1.0
- def setExtruderOpacity(self, extruder_nr, opacity):
+ def setExtruderOpacity(self, extruder_nr: int, opacity: float) -> None:
if 0 <= extruder_nr <= 3:
self._extruder_opacity[extruder_nr] = opacity
self.currentLayerNumChanged.emit()
- def getExtruderOpacities(self):
+ def getExtruderOpacities(self)-> List[float]:
return self._extruder_opacity
def setShowTravelMoves(self, show):
@@ -290,52 +314,56 @@ class SimulationView(View):
def getShowTravelMoves(self):
return self._show_travel_moves
- def setShowHelpers(self, show):
+ def setShowHelpers(self, show: bool) -> None:
self._show_helpers = show
self.currentLayerNumChanged.emit()
- def getShowHelpers(self):
+ def getShowHelpers(self) -> bool:
return self._show_helpers
- def setShowSkin(self, show):
+ def setShowSkin(self, show: bool) -> None:
self._show_skin = show
self.currentLayerNumChanged.emit()
- def getShowSkin(self):
+ def getShowSkin(self) -> bool:
return self._show_skin
- def setShowInfill(self, show):
+ def setShowInfill(self, show: bool) -> None:
self._show_infill = show
self.currentLayerNumChanged.emit()
- def getShowInfill(self):
+ def getShowInfill(self) -> bool:
return self._show_infill
- def getCompatibilityMode(self):
+ def getCompatibilityMode(self) -> bool:
return self._compatibility_mode
- def getExtruderCount(self):
+ def getExtruderCount(self) -> int:
return self._extruder_count
- def getMinFeedrate(self):
+ def getMinFeedrate(self) -> float:
+ if abs(self._min_feedrate - sys.float_info.max) < 10: # Some lenience due to floating point rounding.
+ return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
return self._min_feedrate
- def getMaxFeedrate(self):
+ def getMaxFeedrate(self) -> float:
return self._max_feedrate
- def getMinThickness(self):
+ def getMinThickness(self) -> float:
+ if abs(self._min_thickness - sys.float_info.max) < 10: # Some lenience due to floating point rounding.
+ return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
return self._min_thickness
- def getMaxThickness(self):
+ def getMaxThickness(self) -> float:
return self._max_thickness
- def calculateMaxLayers(self):
+ def calculateMaxLayers(self) -> None:
scene = self.getController().getScene()
self._old_max_layers = self._max_layers
## Recalculate num max layers
- new_max_layers = 0
- for node in DepthFirstIterator(scene.getRoot()):
+ new_max_layers = -1
+ for node in DepthFirstIterator(scene.getRoot()): # type: ignore
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
@@ -369,7 +397,7 @@ class SimulationView(View):
if new_max_layers < layer_count:
new_max_layers = layer_count
- if new_max_layers > 0 and new_max_layers != self._old_max_layers:
+ if new_max_layers >= 0 and new_max_layers != self._old_max_layers:
self._max_layers = new_max_layers
# The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
@@ -383,10 +411,10 @@ class SimulationView(View):
self.maxLayersChanged.emit()
self._startUpdateTopLayers()
- def calculateMaxPathsOnLayer(self, layer_num):
+ def calculateMaxPathsOnLayer(self, layer_num: int) -> None:
# Update the currentPath
scene = self.getController().getScene()
- for node in DepthFirstIterator(scene.getRoot()):
+ for node in DepthFirstIterator(scene.getRoot()): # type: ignore
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
@@ -415,10 +443,10 @@ class SimulationView(View):
def getProxy(self, engine, script_engine):
return self._proxy
- def endRendering(self):
+ def endRendering(self) -> None:
pass
- def event(self, event):
+ def event(self, event) -> bool:
modifiers = QApplication.keyboardModifiers()
ctrl_is_active = modifiers & Qt.ControlModifier
shift_is_active = modifiers & Qt.ShiftModifier
@@ -447,7 +475,7 @@ class SimulationView(View):
if QOpenGLContext.currentContext() is None:
Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later")
CuraApplication.getInstance().callLater(lambda e=event: self.event(e))
- return
+ return False
# Make sure the SimulationPass is created
layer_pass = self.getSimulationPass()
@@ -462,15 +490,17 @@ class SimulationView(View):
self._onGlobalStackChanged()
if not self._simulationview_composite_shader:
- self._simulationview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), "simulationview_composite.shader"))
- theme = Application.getInstance().getTheme()
- self._simulationview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb()))
- self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))
+ plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath("SimulationView"))
+ self._simulationview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(plugin_path, "simulationview_composite.shader"))
+ theme = CuraApplication.getInstance().getTheme()
+ if theme is not None:
+ self._simulationview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb()))
+ self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))
if not self._composite_pass:
- self._composite_pass = self.getRenderer().getRenderPass("composite")
+ self._composite_pass = cast(CompositePass, self.getRenderer().getRenderPass("composite"))
- self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later
+ self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later
self._composite_pass.getLayerBindings().append("simulationview")
self._old_composite_shader = self._composite_pass.getCompositeShader()
self._composite_pass.setCompositeShader(self._simulationview_composite_shader)
@@ -480,11 +510,14 @@ class SimulationView(View):
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
-
- self._nozzle_node.setParent(None)
+ if self._nozzle_node:
+ self._nozzle_node.setParent(None)
self.getRenderer().removeRenderPass(self._layer_pass)
- self._composite_pass.setLayerBindings(self._old_layer_bindings)
- self._composite_pass.setCompositeShader(self._old_composite_shader)
+ if self._composite_pass:
+ self._composite_pass.setLayerBindings(cast(List[str], self._old_layer_bindings))
+ self._composite_pass.setCompositeShader(cast(ShaderProgram, self._old_composite_shader))
+
+ return False
def getCurrentLayerMesh(self):
return self._current_layer_mesh
@@ -492,7 +525,7 @@ class SimulationView(View):
def getCurrentLayerJumps(self):
return self._current_layer_jumps
- def _onGlobalStackChanged(self):
+ def _onGlobalStackChanged(self) -> None:
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
@@ -504,17 +537,17 @@ class SimulationView(View):
else:
self._wireprint_warning_message.hide()
- def _onPropertyChanged(self, key, property_name):
+ def _onPropertyChanged(self, key: str, property_name: str) -> None:
if key == "wireframe_enabled" and property_name == "value":
- if self._global_container_stack.getProperty("wireframe_enabled", "value"):
+ if self._global_container_stack and self._global_container_stack.getProperty("wireframe_enabled", "value"):
self._wireprint_warning_message.show()
else:
self._wireprint_warning_message.hide()
- def _onCurrentLayerNumChanged(self):
+ def _onCurrentLayerNumChanged(self) -> None:
self.calculateMaxPathsOnLayer(self._current_layer_num)
- def _startUpdateTopLayers(self):
+ def _startUpdateTopLayers(self) -> None:
if not self._compatibility_mode:
return
@@ -525,10 +558,10 @@ class SimulationView(View):
self.setBusy(True)
self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
- self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
- self._top_layers_job.start()
+ self._top_layers_job.finished.connect(self._updateCurrentLayerMesh) # type: ignore # mypy doesn't understand the whole private class thing that's going on here.
+ self._top_layers_job.start() # type: ignore
- def _updateCurrentLayerMesh(self, job):
+ def _updateCurrentLayerMesh(self, job: "_CreateTopLayersJob") -> None:
self.setBusy(False)
if not job.getResult():
@@ -539,9 +572,9 @@ class SimulationView(View):
self._current_layer_jumps = job.getResult().get("jumps")
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
- self._top_layers_job = None
+ self._top_layers_job = None # type: Optional["_CreateTopLayersJob"]
- def _updateWithPreferences(self):
+ def _updateWithPreferences(self) -> None:
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
self._compatibility_mode = self._evaluateCompatibilityMode()
@@ -563,7 +596,7 @@ class SimulationView(View):
self._startUpdateTopLayers()
self.preferencesChanged.emit()
- def _onPreferencesChanged(self, preference):
+ def _onPreferencesChanged(self, preference: str) -> None:
if preference not in {
"view/top_layer_count",
"view/only_show_top_layers",
@@ -581,7 +614,7 @@ class SimulationView(View):
class _CreateTopLayersJob(Job):
- def __init__(self, scene, layer_number, solid_layers):
+ def __init__(self, scene: "Scene", layer_number: int, solid_layers: int) -> None:
super().__init__()
self._scene = scene
@@ -589,9 +622,9 @@ class _CreateTopLayersJob(Job):
self._solid_layers = solid_layers
self._cancel = False
- def run(self):
+ def run(self) -> None:
layer_data = None
- for node in DepthFirstIterator(self._scene.getRoot()):
+ for node in DepthFirstIterator(self._scene.getRoot()): # type: ignore
layer_data = node.callDecoration("getLayerData")
if layer_data:
break
@@ -638,6 +671,6 @@ class _CreateTopLayersJob(Job):
self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
- def cancel(self):
+ def cancel(self) -> None:
self._cancel = True
super().cancel()
diff --git a/plugins/SimulationView/SimulationView.qml b/plugins/SimulationView/SimulationView.qml
deleted file mode 100644
index a3a8ced4cf..0000000000
--- a/plugins/SimulationView/SimulationView.qml
+++ /dev/null
@@ -1,704 +0,0 @@
-// Copyright (c) 2017 Ultimaker B.V.
-// Cura is released under the terms of the LGPLv3 or higher.
-
-import QtQuick 2.4
-import QtQuick.Controls 1.2
-import QtQuick.Layouts 1.1
-import QtQuick.Controls.Styles 1.1
-
-import UM 1.0 as UM
-import Cura 1.0 as Cura
-
-Item
-{
- id: base
- width: {
- if (UM.SimulationView.compatibilityMode) {
- return UM.Theme.getSize("layerview_menu_size_compatibility").width;
- } else {
- return UM.Theme.getSize("layerview_menu_size").width;
- }
- }
- height: {
- if (viewSettings.collapsed) {
- if (UM.SimulationView.compatibilityMode) {
- return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
- }
- return UM.Theme.getSize("layerview_menu_size_collapsed").height;
- } else if (UM.SimulationView.compatibilityMode) {
- return UM.Theme.getSize("layerview_menu_size_compatibility").height;
- } else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
- return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
- } else {
- return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
- }
- }
- Behavior on height { NumberAnimation { duration: 100 } }
-
- property var buttonTarget: {
- if(parent != null)
- {
- var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
- return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y)
- }
- return Qt.point(0,0)
- }
-
- Rectangle {
- id: layerViewMenu
- anchors.right: parent.right
- anchors.top: parent.top
- width: parent.width
- height: parent.height
- clip: true
- z: layerSlider.z - 1
- color: UM.Theme.getColor("tool_panel_background")
- border.width: UM.Theme.getSize("default_lining").width
- border.color: UM.Theme.getColor("lining")
-
- Button {
- id: collapseButton
- anchors.top: parent.top
- anchors.topMargin: Math.round(UM.Theme.getSize("default_margin").height + (UM.Theme.getSize("layerview_row").height - UM.Theme.getSize("default_margin").height) / 2)
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
-
- width: UM.Theme.getSize("standard_arrow").width
- height: UM.Theme.getSize("standard_arrow").height
-
- onClicked: viewSettings.collapsed = !viewSettings.collapsed
-
- style: ButtonStyle
- {
- background: UM.RecolorImage
- {
- width: control.width
- height: control.height
- sourceSize.width: width
- sourceSize.height: width
- color: UM.Theme.getColor("setting_control_text")
- source: viewSettings.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
- }
- label: Label{ }
- }
- }
-
- ColumnLayout {
- id: viewSettings
-
- property bool collapsed: false
- property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
- property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
- property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
- property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
- property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
- // if we are in compatibility mode, we only show the "line type"
- property bool show_legend: UM.SimulationView.compatibilityMode ? true : UM.Preferences.getValue("layerview/layer_view_type") == 1
- property bool show_gradient: UM.SimulationView.compatibilityMode ? false : UM.Preferences.getValue("layerview/layer_view_type") == 2 || UM.Preferences.getValue("layerview/layer_view_type") == 3
- property bool show_feedrate_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 2
- property bool show_thickness_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 3
- property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
- property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
-
- anchors.top: parent.top
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- spacing: UM.Theme.getSize("layerview_row_spacing").height
-
- Label
- {
- id: layerViewTypesLabel
- anchors.left: parent.left
- text: catalog.i18nc("@label","Color scheme")
- font: UM.Theme.getFont("default");
- visible: !UM.SimulationView.compatibilityMode
- Layout.fillWidth: true
- color: UM.Theme.getColor("setting_control_text")
- }
-
- ListModel // matches SimulationView.py
- {
- id: layerViewTypes
- }
-
- Component.onCompleted:
- {
- layerViewTypes.append({
- text: catalog.i18nc("@label:listbox", "Material Color"),
- type_id: 0
- })
- layerViewTypes.append({
- text: catalog.i18nc("@label:listbox", "Line Type"),
- type_id: 1
- })
- layerViewTypes.append({
- text: catalog.i18nc("@label:listbox", "Feedrate"),
- type_id: 2
- })
- layerViewTypes.append({
- text: catalog.i18nc("@label:listbox", "Layer thickness"),
- type_id: 3 // these ids match the switching in the shader
- })
- }
-
- ComboBox
- {
- id: layerTypeCombobox
- anchors.left: parent.left
- Layout.fillWidth: true
- Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
- model: layerViewTypes
- visible: !UM.SimulationView.compatibilityMode
- style: UM.Theme.styles.combobox
- anchors.right: parent.right
-
- onActivated:
- {
- UM.Preferences.setValue("layerview/layer_view_type", index);
- }
-
- Component.onCompleted:
- {
- currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
- updateLegends(currentIndex);
- }
-
- function updateLegends(type_id)
- {
- // update visibility of legends
- viewSettings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1);
- viewSettings.show_gradient = !UM.SimulationView.compatibilityMode && (type_id == 2 || type_id == 3);
- viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
- viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
- }
- }
-
- Label
- {
- id: compatibilityModeLabel
- anchors.left: parent.left
- text: catalog.i18nc("@label","Compatibility Mode")
- font: UM.Theme.getFont("default")
- color: UM.Theme.getColor("text")
- visible: UM.SimulationView.compatibilityMode
- Layout.fillWidth: true
- Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
- Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
- }
-
- Item
- {
- height: Math.round(UM.Theme.getSize("default_margin").width / 2)
- width: width
- }
-
- Connections {
- target: UM.Preferences
- onPreferenceChanged:
- {
- layerTypeCombobox.currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
- layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
- playButton.pauseSimulation();
- viewSettings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
- viewSettings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
- viewSettings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
- viewSettings.show_skin = UM.Preferences.getValue("layerview/show_skin");
- viewSettings.show_infill = UM.Preferences.getValue("layerview/show_infill");
- viewSettings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers");
- viewSettings.top_layer_count = UM.Preferences.getValue("view/top_layer_count");
- }
- }
-
- Repeater {
- model: Cura.ExtrudersModel{}
- CheckBox {
- id: extrudersModelCheckBox
- checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
- onClicked: {
- viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
- UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
- }
- visible: !UM.SimulationView.compatibilityMode
- enabled: index + 1 <= 4
- Rectangle {
- anchors.verticalCenter: parent.verticalCenter
- anchors.right: extrudersModelCheckBox.right
- width: UM.Theme.getSize("layerview_legend_size").width
- height: UM.Theme.getSize("layerview_legend_size").height
- color: model.color
- radius: Math.round(width / 2)
- border.width: UM.Theme.getSize("default_lining").width
- border.color: UM.Theme.getColor("lining")
- visible: !viewSettings.show_legend & !viewSettings.show_gradient
- }
- Layout.fillWidth: true
- Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
- Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
- style: UM.Theme.styles.checkbox
- Label
- {
- text: model.name
- elide: Text.ElideRight
- color: UM.Theme.getColor("setting_control_text")
- font: UM.Theme.getFont("default")
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: extrudersModelCheckBox.left;
- anchors.right: extrudersModelCheckBox.right;
- anchors.leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width/2)
- anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
- }
- }
- }
-
- Repeater {
- model: ListModel {
- id: typesLegendModel
- Component.onCompleted:
- {
- typesLegendModel.append({
- label: catalog.i18nc("@label", "Show Travels"),
- initialValue: viewSettings.show_travel_moves,
- preference: "layerview/show_travel_moves",
- colorId: "layerview_move_combing"
- });
- typesLegendModel.append({
- label: catalog.i18nc("@label", "Show Helpers"),
- initialValue: viewSettings.show_helpers,
- preference: "layerview/show_helpers",
- colorId: "layerview_support"
- });
- typesLegendModel.append({
- label: catalog.i18nc("@label", "Show Shell"),
- initialValue: viewSettings.show_skin,
- preference: "layerview/show_skin",
- colorId: "layerview_inset_0"
- });
- typesLegendModel.append({
- label: catalog.i18nc("@label", "Show Infill"),
- initialValue: viewSettings.show_infill,
- preference: "layerview/show_infill",
- colorId: "layerview_infill"
- });
- }
- }
-
- CheckBox {
- id: legendModelCheckBox
- checked: model.initialValue
- onClicked: {
- UM.Preferences.setValue(model.preference, checked);
- }
- Rectangle {
- anchors.verticalCenter: parent.verticalCenter
- anchors.right: legendModelCheckBox.right
- width: UM.Theme.getSize("layerview_legend_size").width
- height: UM.Theme.getSize("layerview_legend_size").height
- color: UM.Theme.getColor(model.colorId)
- border.width: UM.Theme.getSize("default_lining").width
- border.color: UM.Theme.getColor("lining")
- visible: viewSettings.show_legend
- }
- Layout.fillWidth: true
- Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
- Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
- style: UM.Theme.styles.checkbox
- Label
- {
- text: label
- font: UM.Theme.getFont("default")
- elide: Text.ElideRight
- color: UM.Theme.getColor("setting_control_text")
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: legendModelCheckBox.left;
- anchors.right: legendModelCheckBox.right;
- anchors.leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width/2)
- anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
- }
- }
- }
-
- CheckBox {
- checked: viewSettings.only_show_top_layers
- onClicked: {
- UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
- }
- text: catalog.i18nc("@label", "Only Show Top Layers")
- visible: UM.SimulationView.compatibilityMode
- style: UM.Theme.styles.checkbox
- }
- CheckBox {
- checked: viewSettings.top_layer_count == 5
- onClicked: {
- UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
- }
- text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
- visible: UM.SimulationView.compatibilityMode
- style: UM.Theme.styles.checkbox
- }
-
- Repeater {
- model: ListModel {
- id: typesLegendModelNoCheck
- Component.onCompleted:
- {
- typesLegendModelNoCheck.append({
- label: catalog.i18nc("@label", "Top / Bottom"),
- colorId: "layerview_skin",
- });
- typesLegendModelNoCheck.append({
- label: catalog.i18nc("@label", "Inner Wall"),
- colorId: "layerview_inset_x",
- });
- }
- }
-
- Label {
- text: label
- visible: viewSettings.show_legend
- id: typesLegendModelLabel
- Rectangle {
- anchors.verticalCenter: parent.verticalCenter
- anchors.right: typesLegendModelLabel.right
- width: UM.Theme.getSize("layerview_legend_size").width
- height: UM.Theme.getSize("layerview_legend_size").height
- color: UM.Theme.getColor(model.colorId)
- border.width: UM.Theme.getSize("default_lining").width
- border.color: UM.Theme.getColor("lining")
- visible: viewSettings.show_legend
- }
- Layout.fillWidth: true
- Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
- Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
- color: UM.Theme.getColor("setting_control_text")
- font: UM.Theme.getFont("default")
- }
- }
-
- // Text for the minimum, maximum and units for the feedrates and layer thickness
- Item {
- id: gradientLegend
- visible: viewSettings.show_gradient
- width: parent.width
- height: UM.Theme.getSize("layerview_row").height
- anchors {
- topMargin: UM.Theme.getSize("slider_layerview_margin").height
- horizontalCenter: parent.horizontalCenter
- }
-
- Label {
- text: minText()
- anchors.left: parent.left
- color: UM.Theme.getColor("setting_control_text")
- font: UM.Theme.getFont("default")
-
- function minText() {
- if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
- // Feedrate selected
- if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
- return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
- }
- // Layer thickness selected
- if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
- return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
- }
- }
- return catalog.i18nc("@label","min")
- }
- }
-
- Label {
- text: unitsText()
- anchors.horizontalCenter: parent.horizontalCenter
- color: UM.Theme.getColor("setting_control_text")
- font: UM.Theme.getFont("default")
-
- function unitsText() {
- if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
- // Feedrate selected
- if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
- return "mm/s"
- }
- // Layer thickness selected
- if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
- return "mm"
- }
- }
- return ""
- }
- }
-
- Label {
- text: maxText()
- anchors.right: parent.right
- color: UM.Theme.getColor("setting_control_text")
- font: UM.Theme.getFont("default")
-
- function maxText() {
- if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
- // Feedrate selected
- if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
- return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
- }
- // Layer thickness selected
- if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
- return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
- }
- }
- return catalog.i18nc("@label","max")
- }
- }
- }
-
- // Gradient colors for feedrate
- Rectangle { // In QML 5.9 can be changed by LinearGradient
- // Invert values because then the bar is rotated 90 degrees
- id: feedrateGradient
- visible: viewSettings.show_feedrate_gradient
- anchors.left: parent.right
- height: parent.width
- width: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
- border.width: UM.Theme.getSize("default_lining").width
- border.color: UM.Theme.getColor("lining")
- transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
- gradient: Gradient {
- GradientStop {
- position: 0.000
- color: Qt.rgba(1, 0.5, 0, 1)
- }
- GradientStop {
- position: 0.625
- color: Qt.rgba(0.375, 0.5, 0, 1)
- }
- GradientStop {
- position: 0.75
- color: Qt.rgba(0.25, 1, 0, 1)
- }
- GradientStop {
- position: 1.0
- color: Qt.rgba(0, 0, 1, 1)
- }
- }
- }
-
- // Gradient colors for layer thickness (similar to parula colormap)
- Rectangle { // In QML 5.9 can be changed by LinearGradient
- // Invert values because then the bar is rotated 90 degrees
- id: thicknessGradient
- visible: viewSettings.show_thickness_gradient
- anchors.left: parent.right
- height: parent.width
- width: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
- border.width: UM.Theme.getSize("default_lining").width
- border.color: UM.Theme.getColor("lining")
- transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
- gradient: Gradient {
- GradientStop {
- position: 0.000
- color: Qt.rgba(1, 1, 0, 1)
- }
- GradientStop {
- position: 0.25
- color: Qt.rgba(1, 0.75, 0.25, 1)
- }
- GradientStop {
- position: 0.5
- color: Qt.rgba(0, 0.75, 0.5, 1)
- }
- GradientStop {
- position: 0.75
- color: Qt.rgba(0, 0.375, 0.75, 1)
- }
- GradientStop {
- position: 1.0
- color: Qt.rgba(0, 0, 0.5, 1)
- }
- }
- }
- }
- }
-
- Item {
- id: slidersBox
-
- width: parent.width
- visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
-
- anchors {
- top: parent.bottom
- topMargin: UM.Theme.getSize("slider_layerview_margin").height
- left: parent.left
- }
-
- PathSlider {
- id: pathSlider
-
- height: UM.Theme.getSize("slider_handle").width
- anchors.left: playButton.right
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- visible: !UM.SimulationView.compatibilityMode
-
- // custom properties
- handleValue: UM.SimulationView.currentPath
- maximumValue: UM.SimulationView.numPaths
- handleSize: UM.Theme.getSize("slider_handle").width
- trackThickness: UM.Theme.getSize("slider_groove").width
- trackColor: UM.Theme.getColor("slider_groove")
- trackBorderColor: UM.Theme.getColor("slider_groove_border")
- handleColor: UM.Theme.getColor("slider_handle")
- handleActiveColor: UM.Theme.getColor("slider_handle_active")
- rangeColor: UM.Theme.getColor("slider_groove_fill")
-
- // update values when layer data changes
- Connections {
- target: UM.SimulationView
- onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
- onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
- }
-
- // make sure the slider handlers show the correct value after switching views
- Component.onCompleted: {
- pathSlider.setHandleValue(UM.SimulationView.currentPath)
- }
- }
-
- LayerSlider {
- id: layerSlider
-
- width: UM.Theme.getSize("slider_handle").width
- height: UM.Theme.getSize("layerview_menu_size").height
-
- anchors {
- top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
- topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
- right: parent.right
- rightMargin: UM.Theme.getSize("slider_layerview_margin").width
- }
-
- // custom properties
- upperValue: UM.SimulationView.currentLayer
- lowerValue: UM.SimulationView.minimumLayer
- maximumValue: UM.SimulationView.numLayers
- handleSize: UM.Theme.getSize("slider_handle").width
- trackThickness: UM.Theme.getSize("slider_groove").width
- trackColor: UM.Theme.getColor("slider_groove")
- trackBorderColor: UM.Theme.getColor("slider_groove_border")
- upperHandleColor: UM.Theme.getColor("slider_handle")
- lowerHandleColor: UM.Theme.getColor("slider_handle")
- rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
- handleActiveColor: UM.Theme.getColor("slider_handle_active")
- handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
-
- // update values when layer data changes
- Connections {
- target: UM.SimulationView
- onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
- onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
- onCurrentLayerChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
- }
-
- // make sure the slider handlers show the correct value after switching views
- Component.onCompleted: {
- layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
- layerSlider.setUpperValue(UM.SimulationView.currentLayer)
- }
- }
-
- // Play simulation button
- Button {
- id: playButton
- iconSource: "./resources/simulation_resume.svg"
- style: UM.Theme.styles.small_tool_button
- visible: !UM.SimulationView.compatibilityMode
- anchors {
- verticalCenter: pathSlider.verticalCenter
- }
-
- property var status: 0 // indicates if it's stopped (0) or playing (1)
-
- onClicked: {
- switch(status) {
- case 0: {
- resumeSimulation()
- break
- }
- case 1: {
- pauseSimulation()
- break
- }
- }
- }
-
- function pauseSimulation() {
- UM.SimulationView.setSimulationRunning(false)
- iconSource = "./resources/simulation_resume.svg"
- simulationTimer.stop()
- status = 0
- }
-
- function resumeSimulation() {
- UM.SimulationView.setSimulationRunning(true)
- iconSource = "./resources/simulation_pause.svg"
- simulationTimer.start()
- }
- }
-
- Timer
- {
- id: simulationTimer
- interval: 100
- running: false
- repeat: true
- onTriggered: {
- var currentPath = UM.SimulationView.currentPath
- var numPaths = UM.SimulationView.numPaths
- var currentLayer = UM.SimulationView.currentLayer
- var numLayers = UM.SimulationView.numLayers
- // When the user plays the simulation, if the path slider is at the end of this layer, we start
- // the simulation at the beginning of the current layer.
- if (playButton.status == 0)
- {
- if (currentPath >= numPaths)
- {
- UM.SimulationView.setCurrentPath(0)
- }
- else
- {
- UM.SimulationView.setCurrentPath(currentPath+1)
- }
- }
- // If the simulation is already playing and we reach the end of a layer, then it automatically
- // starts at the beginning of the next layer.
- else
- {
- if (currentPath >= numPaths)
- {
- // At the end of the model, the simulation stops
- if (currentLayer >= numLayers)
- {
- playButton.pauseSimulation()
- }
- else
- {
- UM.SimulationView.setCurrentLayer(currentLayer+1)
- UM.SimulationView.setCurrentPath(0)
- }
- }
- else
- {
- UM.SimulationView.setCurrentPath(currentPath+1)
- }
- }
- playButton.status = 1
- }
- }
- }
-
- FontMetrics {
- id: fontMetrics
- font: UM.Theme.getFont("default")
- }
-}
diff --git a/plugins/SimulationView/SimulationViewMainComponent.qml b/plugins/SimulationView/SimulationViewMainComponent.qml
new file mode 100644
index 0000000000..58901652d3
--- /dev/null
+++ b/plugins/SimulationView/SimulationViewMainComponent.qml
@@ -0,0 +1,216 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.4
+import QtQuick.Controls 1.2
+import QtQuick.Layouts 1.1
+import QtQuick.Controls.Styles 1.1
+
+import UM 1.4 as UM
+import Cura 1.0 as Cura
+
+Item
+{
+ property bool is_simulation_playing: false
+ visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
+
+ PathSlider
+ {
+ id: pathSlider
+ height: UM.Theme.getSize("slider_handle").width
+ width: UM.Theme.getSize("slider_layerview_size").height
+
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: UM.Theme.getSize("default_margin").height
+
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ visible: !UM.SimulationView.compatibilityMode
+
+ // Custom properties
+ handleValue: UM.SimulationView.currentPath
+ maximumValue: UM.SimulationView.numPaths
+
+ // Update values when layer data changes.
+ Connections
+ {
+ target: UM.SimulationView
+ onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
+ onCurrentPathChanged:
+ {
+ // Only pause the simulation when the layer was changed manually, not when the simulation is running
+ if (pathSlider.manuallyChanged)
+ {
+ playButton.pauseSimulation()
+ }
+ pathSlider.setHandleValue(UM.SimulationView.currentPath)
+ }
+ }
+
+ // Ensure that the slider handlers show the correct value after switching views.
+ Component.onCompleted:
+ {
+ pathSlider.setHandleValue(UM.SimulationView.currentPath)
+ }
+
+ }
+
+ UM.SimpleButton
+ {
+ id: playButton
+ iconSource: !is_simulation_playing ? "./resources/simulation_resume.svg": "./resources/simulation_pause.svg"
+ width: UM.Theme.getSize("small_button").width
+ height: UM.Theme.getSize("small_button").height
+ hoverColor: UM.Theme.getColor("slider_handle_active")
+ color: UM.Theme.getColor("slider_handle")
+ iconMargin: UM.Theme.getSize("thick_lining").width
+ visible: !UM.SimulationView.compatibilityMode
+
+ Connections
+ {
+ target: UM.Preferences
+ onPreferenceChanged:
+ {
+ if (preference !== "view/only_show_top_layers" && preference !== "view/top_layer_count" && ! preference.match("layerview/"))
+ {
+ return;
+ }
+
+ playButton.pauseSimulation()
+ }
+ }
+
+ anchors
+ {
+ right: pathSlider.left
+ verticalCenter: pathSlider.verticalCenter
+ }
+
+ onClicked:
+ {
+ if(is_simulation_playing)
+ {
+ pauseSimulation()
+ }
+ else
+ {
+ resumeSimulation()
+ }
+ }
+
+ function pauseSimulation()
+ {
+ UM.SimulationView.setSimulationRunning(false)
+ simulationTimer.stop()
+ is_simulation_playing = false
+ layerSlider.manuallyChanged = true
+ pathSlider.manuallyChanged = true
+ }
+
+ function resumeSimulation()
+ {
+ UM.SimulationView.setSimulationRunning(true)
+ simulationTimer.start()
+ layerSlider.manuallyChanged = false
+ pathSlider.manuallyChanged = false
+ }
+ }
+
+ Timer
+ {
+ id: simulationTimer
+ interval: 100
+ running: false
+ repeat: true
+ onTriggered:
+ {
+ var currentPath = UM.SimulationView.currentPath
+ var numPaths = UM.SimulationView.numPaths
+ var currentLayer = UM.SimulationView.currentLayer
+ var numLayers = UM.SimulationView.numLayers
+
+ // When the user plays the simulation, if the path slider is at the end of this layer, we start
+ // the simulation at the beginning of the current layer.
+ if (!is_simulation_playing)
+ {
+ if (currentPath >= numPaths)
+ {
+ UM.SimulationView.setCurrentPath(0)
+ }
+ else
+ {
+ UM.SimulationView.setCurrentPath(currentPath + 1)
+ }
+ }
+ // If the simulation is already playing and we reach the end of a layer, then it automatically
+ // starts at the beginning of the next layer.
+ else
+ {
+ if (currentPath >= numPaths)
+ {
+ // At the end of the model, the simulation stops
+ if (currentLayer >= numLayers)
+ {
+ playButton.pauseSimulation()
+ }
+ else
+ {
+ UM.SimulationView.setCurrentLayer(currentLayer + 1)
+ UM.SimulationView.setCurrentPath(0)
+ }
+ }
+ else
+ {
+ UM.SimulationView.setCurrentPath(currentPath + 1)
+ }
+ }
+ // The status must be set here instead of in the resumeSimulation function otherwise it won't work
+ // correctly, because part of the logic is in this trigger function.
+ is_simulation_playing = true
+ }
+ }
+
+ LayerSlider
+ {
+ id: layerSlider
+
+ width: UM.Theme.getSize("slider_handle").width
+ height: UM.Theme.getSize("slider_layerview_size").height
+
+ anchors
+ {
+ right: parent.right
+ verticalCenter: parent.verticalCenter
+ rightMargin: UM.Theme.getSize("default_margin").width
+ }
+
+ // Custom properties
+ upperValue: UM.SimulationView.currentLayer
+ lowerValue: UM.SimulationView.minimumLayer
+ maximumValue: UM.SimulationView.numLayers
+
+ // Update values when layer data changes
+ Connections
+ {
+ target: UM.SimulationView
+ onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
+ onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
+ onCurrentLayerChanged:
+ {
+ // Only pause the simulation when the layer was changed manually, not when the simulation is running
+ if (layerSlider.manuallyChanged)
+ {
+ playButton.pauseSimulation()
+ }
+ layerSlider.setUpperValue(UM.SimulationView.currentLayer)
+ }
+ }
+
+ // Make sure the slider handlers show the correct value after switching views
+ Component.onCompleted:
+ {
+ layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
+ layerSlider.setUpperValue(UM.SimulationView.currentLayer)
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/SimulationView/SimulationViewMenuComponent.qml b/plugins/SimulationView/SimulationViewMenuComponent.qml
new file mode 100644
index 0000000000..957d8170cf
--- /dev/null
+++ b/plugins/SimulationView/SimulationViewMenuComponent.qml
@@ -0,0 +1,556 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.4
+import QtQuick.Controls 1.2
+import QtQuick.Layouts 1.1
+import QtQuick.Controls.Styles 1.1
+import QtGraphicalEffects 1.0
+
+import UM 1.0 as UM
+import Cura 1.0 as Cura
+
+
+Cura.ExpandableComponent
+{
+ id: base
+
+ contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
+
+ Connections
+ {
+ target: UM.Preferences
+ onPreferenceChanged:
+ {
+ if (preference !== "view/only_show_top_layers" && preference !== "view/top_layer_count" && ! preference.match("layerview/"))
+ {
+ return;
+ }
+
+ layerTypeCombobox.currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type")
+ layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex)
+ viewSettings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|")
+ viewSettings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves")
+ viewSettings.show_helpers = UM.Preferences.getValue("layerview/show_helpers")
+ viewSettings.show_skin = UM.Preferences.getValue("layerview/show_skin")
+ viewSettings.show_infill = UM.Preferences.getValue("layerview/show_infill")
+ viewSettings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers")
+ viewSettings.top_layer_count = UM.Preferences.getValue("view/top_layer_count")
+ }
+ }
+
+ headerItem: Item
+ {
+ Label
+ {
+ id: colorSchemeLabel
+ text: catalog.i18nc("@label", "Color scheme")
+ verticalAlignment: Text.AlignVCenter
+ height: parent.height
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium")
+ color: UM.Theme.getColor("text_medium")
+ renderType: Text.NativeRendering
+ }
+
+ Label
+ {
+ text: layerTypeCombobox.currentText
+ verticalAlignment: Text.AlignVCenter
+ anchors
+ {
+ left: colorSchemeLabel.right
+ leftMargin: UM.Theme.getSize("default_margin").width
+ right: parent.right
+ }
+ height: parent.height
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium")
+ color: UM.Theme.getColor("text")
+ renderType: Text.NativeRendering
+ }
+ }
+
+ contentItem: Column
+ {
+ id: viewSettings
+
+ property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
+ property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
+ property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
+ property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
+ property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
+
+ // If we are in compatibility mode, we only show the "line type"
+ property bool show_legend: UM.SimulationView.compatibilityMode ? true : UM.Preferences.getValue("layerview/layer_view_type") == 1
+ property bool show_gradient: UM.SimulationView.compatibilityMode ? false : UM.Preferences.getValue("layerview/layer_view_type") == 2 || UM.Preferences.getValue("layerview/layer_view_type") == 3
+ property bool show_feedrate_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 2
+ property bool show_thickness_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 3
+ property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
+ property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
+
+ width: UM.Theme.getSize("layerview_menu_size").width - 2 * UM.Theme.getSize("default_margin").width
+ height: implicitHeight
+
+ spacing: UM.Theme.getSize("layerview_row_spacing").height
+
+ ListModel // matches SimulationView.py
+ {
+ id: layerViewTypes
+ }
+
+ Component.onCompleted:
+ {
+ layerViewTypes.append({
+ text: catalog.i18nc("@label:listbox", "Material Color"),
+ type_id: 0
+ })
+ layerViewTypes.append({
+ text: catalog.i18nc("@label:listbox", "Line Type"),
+ type_id: 1
+ })
+ layerViewTypes.append({
+ text: catalog.i18nc("@label:listbox", "Feedrate"),
+ type_id: 2
+ })
+ layerViewTypes.append({
+ text: catalog.i18nc("@label:listbox", "Layer thickness"),
+ type_id: 3 // these ids match the switching in the shader
+ })
+ }
+
+ ComboBox
+ {
+ id: layerTypeCombobox
+ width: parent.width
+ model: layerViewTypes
+ visible: !UM.SimulationView.compatibilityMode
+ style: UM.Theme.styles.combobox
+
+ onActivated:
+ {
+ UM.Preferences.setValue("layerview/layer_view_type", index);
+ }
+
+ Component.onCompleted:
+ {
+ currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
+ updateLegends(currentIndex);
+ }
+
+ function updateLegends(type_id)
+ {
+ // Update the visibility of the legends.
+ viewSettings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1);
+ viewSettings.show_gradient = !UM.SimulationView.compatibilityMode && (type_id == 2 || type_id == 3);
+ viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
+ viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
+ }
+ }
+
+ Label
+ {
+ id: compatibilityModeLabel
+ text: catalog.i18nc("@label", "Compatibility Mode")
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ visible: UM.SimulationView.compatibilityMode
+ height: UM.Theme.getSize("layerview_row").height
+ width: parent.width
+ renderType: Text.NativeRendering
+ }
+
+ Item // Spacer
+ {
+ height: UM.Theme.getSize("narrow_margin").width
+ width: width
+ }
+
+ Repeater
+ {
+ model: CuraApplication.getExtrudersModel()
+
+ CheckBox
+ {
+ id: extrudersModelCheckBox
+ checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
+ height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
+ width: parent.width
+ visible: !UM.SimulationView.compatibilityMode
+ enabled: index < 4
+
+ onClicked:
+ {
+ viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
+ UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
+ }
+
+ style: UM.Theme.styles.checkbox
+
+
+ UM.RecolorImage
+ {
+ id: swatch
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: extrudersModelCheckBox.right
+ width: UM.Theme.getSize("layerview_legend_size").width
+ height: UM.Theme.getSize("layerview_legend_size").height
+ source: UM.Theme.getIcon("extruder_button")
+ color: model.color
+ }
+
+ Label
+ {
+ text: model.name
+ elide: Text.ElideRight
+ color: UM.Theme.getColor("setting_control_text")
+ font: UM.Theme.getFont("default")
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ left: extrudersModelCheckBox.left
+ right: extrudersModelCheckBox.right
+ leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width / 2)
+ rightMargin: UM.Theme.getSize("default_margin").width * 2
+ }
+ renderType: Text.NativeRendering
+ }
+ }
+ }
+
+ Repeater
+ {
+ model: ListModel
+ {
+ id: typesLegendModel
+ Component.onCompleted:
+ {
+ typesLegendModel.append({
+ label: catalog.i18nc("@label", "Travels"),
+ initialValue: viewSettings.show_travel_moves,
+ preference: "layerview/show_travel_moves",
+ colorId: "layerview_move_combing"
+ });
+ typesLegendModel.append({
+ label: catalog.i18nc("@label", "Helpers"),
+ initialValue: viewSettings.show_helpers,
+ preference: "layerview/show_helpers",
+ colorId: "layerview_support"
+ });
+ typesLegendModel.append({
+ label: catalog.i18nc("@label", "Shell"),
+ initialValue: viewSettings.show_skin,
+ preference: "layerview/show_skin",
+ colorId: "layerview_inset_0"
+ });
+ typesLegendModel.append({
+ label: catalog.i18nc("@label", "Infill"),
+ initialValue: viewSettings.show_infill,
+ preference: "layerview/show_infill",
+ colorId: "layerview_infill"
+ });
+ }
+ }
+
+ CheckBox
+ {
+ id: legendModelCheckBox
+ checked: model.initialValue
+ onClicked: UM.Preferences.setValue(model.preference, checked)
+ height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
+ width: parent.width
+
+ style: UM.Theme.styles.checkbox
+
+ Rectangle
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: legendModelCheckBox.right
+ width: UM.Theme.getSize("layerview_legend_size").width
+ height: UM.Theme.getSize("layerview_legend_size").height
+ color: UM.Theme.getColor(model.colorId)
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: UM.Theme.getColor("lining")
+ visible: viewSettings.show_legend
+ }
+
+ Label
+ {
+ text: label
+ font: UM.Theme.getFont("default")
+ elide: Text.ElideRight
+ renderType: Text.NativeRendering
+ color: UM.Theme.getColor("setting_control_text")
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: legendModelCheckBox.left
+ anchors.right: legendModelCheckBox.right
+ anchors.leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width / 2)
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
+ }
+ }
+ }
+
+ CheckBox
+ {
+ checked: viewSettings.only_show_top_layers
+ onClicked: UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0)
+ text: catalog.i18nc("@label", "Only Show Top Layers")
+ visible: UM.SimulationView.compatibilityMode
+ style: UM.Theme.styles.checkbox
+ width: parent.width
+ }
+
+ CheckBox
+ {
+ checked: viewSettings.top_layer_count == 5
+ onClicked: UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1)
+ text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
+ width: parent.width
+ visible: UM.SimulationView.compatibilityMode
+ style: UM.Theme.styles.checkbox
+ }
+
+ Repeater
+ {
+ model: ListModel
+ {
+ id: typesLegendModelNoCheck
+ Component.onCompleted:
+ {
+ typesLegendModelNoCheck.append({
+ label: catalog.i18nc("@label", "Top / Bottom"),
+ colorId: "layerview_skin",
+ });
+ typesLegendModelNoCheck.append({
+ label: catalog.i18nc("@label", "Inner Wall"),
+ colorId: "layerview_inset_x",
+ });
+ }
+ }
+
+ Label
+ {
+ text: label
+ visible: viewSettings.show_legend
+ id: typesLegendModelLabel
+
+ height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
+ width: parent.width
+ color: UM.Theme.getColor("setting_control_text")
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ Rectangle
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: typesLegendModelLabel.right
+
+ width: UM.Theme.getSize("layerview_legend_size").width
+ height: UM.Theme.getSize("layerview_legend_size").height
+
+ color: UM.Theme.getColor(model.colorId)
+
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: UM.Theme.getColor("lining")
+ }
+ }
+ }
+
+ // Text for the minimum, maximum and units for the feedrates and layer thickness
+ Item
+ {
+ id: gradientLegend
+ visible: viewSettings.show_gradient
+ width: parent.width
+ height: UM.Theme.getSize("layerview_row").height
+
+ Label //Minimum value.
+ {
+ text:
+ {
+ if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
+ {
+ // Feedrate selected
+ if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
+ {
+ return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
+ }
+ // Layer thickness selected
+ if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
+ {
+ return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
+ }
+ }
+ return catalog.i18nc("@label","min")
+ }
+ anchors.left: parent.left
+ color: UM.Theme.getColor("setting_control_text")
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ }
+
+ Label //Unit in the middle.
+ {
+ text:
+ {
+ if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
+ {
+ // Feedrate selected
+ if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
+ {
+ return "mm/s"
+ }
+ // Layer thickness selected
+ if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
+ {
+ return "mm"
+ }
+ }
+ return ""
+ }
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ color: UM.Theme.getColor("setting_control_text")
+ font: UM.Theme.getFont("default")
+ }
+
+ Label //Maximum value.
+ {
+ text: {
+ if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
+ {
+ // Feedrate selected
+ if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
+ {
+ return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
+ }
+ // Layer thickness selected
+ if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
+ {
+ return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
+ }
+ }
+ return catalog.i18nc("@label","max")
+ }
+
+ anchors.right: parent.right
+ color: UM.Theme.getColor("setting_control_text")
+ font: UM.Theme.getFont("default")
+ }
+ }
+
+ // Gradient colors for feedrate
+ Rectangle
+ {
+ id: feedrateGradient
+ visible: viewSettings.show_feedrate_gradient
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: UM.Theme.getColor("lining")
+
+ LinearGradient
+ {
+ anchors
+ {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_lining").width
+ right: parent.right
+ rightMargin: UM.Theme.getSize("default_lining").width
+ top: parent.top
+ topMargin: UM.Theme.getSize("default_lining").width
+ bottom: parent.bottom
+ bottomMargin: UM.Theme.getSize("default_lining").width
+ }
+ start: Qt.point(0, 0)
+ end: Qt.point(parent.width, 0)
+ gradient: Gradient
+ {
+ GradientStop
+ {
+ position: 0.000
+ color: Qt.rgba(0, 0, 1, 1)
+ }
+ GradientStop
+ {
+ position: 0.25
+ color: Qt.rgba(0.25, 1, 0, 1)
+ }
+ GradientStop
+ {
+ position: 0.375
+ color: Qt.rgba(0.375, 0.5, 0, 1)
+ }
+ GradientStop
+ {
+ position: 1.0
+ color: Qt.rgba(1, 0.5, 0, 1)
+ }
+ }
+ }
+ }
+
+ // Gradient colors for layer thickness (similar to parula colormap)
+ Rectangle
+ {
+ id: thicknessGradient
+ visible: viewSettings.show_thickness_gradient
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: UM.Theme.getColor("lining")
+
+ LinearGradient
+ {
+ anchors
+ {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_lining").width
+ right: parent.right
+ rightMargin: UM.Theme.getSize("default_lining").width
+ top: parent.top
+ topMargin: UM.Theme.getSize("default_lining").width
+ bottom: parent.bottom
+ bottomMargin: UM.Theme.getSize("default_lining").width
+ }
+ start: Qt.point(0, 0)
+ end: Qt.point(parent.width, 0)
+ gradient: Gradient
+ {
+ GradientStop
+ {
+ position: 0.000
+ color: Qt.rgba(0, 0, 0.5, 1)
+ }
+ GradientStop
+ {
+ position: 0.25
+ color: Qt.rgba(0, 0.375, 0.75, 1)
+ }
+ GradientStop
+ {
+ position: 0.5
+ color: Qt.rgba(0, 0.75, 0.5, 1)
+ }
+ GradientStop
+ {
+ position: 0.75
+ color: Qt.rgba(1, 0.75, 0.25, 1)
+ }
+ GradientStop
+ {
+ position: 1.0
+ color: Qt.rgba(1, 1, 0, 1)
+ }
+ }
+ }
+ }
+ }
+
+ FontMetrics
+ {
+ id: fontMetrics
+ font: UM.Theme.getFont("default")
+ }
+}
diff --git a/plugins/SimulationView/__init__.py b/plugins/SimulationView/__init__.py
index 360fdc1de9..420ee60660 100644
--- a/plugins/SimulationView/__init__.py
+++ b/plugins/SimulationView/__init__.py
@@ -8,19 +8,21 @@ from . import SimulationViewProxy, SimulationView
catalog = i18nCatalog("cura")
+
def getMetaData():
return {
"view": {
"name": catalog.i18nc("@item:inlistbox", "Layer view"),
- "view_panel": "SimulationView.qml",
- "weight": 2
+ "weight": 0
}
}
+
def createSimulationViewProxy(engine, script_engine):
return SimulationViewProxy.SimulationViewProxy()
+
def register(app):
simulation_view = SimulationView.SimulationView()
qmlRegisterSingletonType(SimulationViewProxy.SimulationViewProxy, "UM", 1, 0, "SimulationView", simulation_view.getProxy)
- return { "view": SimulationView.SimulationView()}
+ return { "view": simulation_view}
diff --git a/plugins/SimulationView/layers.shader b/plugins/SimulationView/layers.shader
index 30f23a3189..69c7c61ee5 100644
--- a/plugins/SimulationView/layers.shader
+++ b/plugins/SimulationView/layers.shader
@@ -49,12 +49,13 @@ fragment =
// discard movements
discard;
}
- // support: 4, 5, 7, 10
+ // support: 4, 5, 7, 10, 11 (prime tower)
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
+ ((v_line_type >= 4.5) && (v_line_type <= 5.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
- ((v_line_type >= 4.5) && (v_line_type <= 5.5))
+ ((v_line_type >= 10.5) && (v_line_type <= 11.5))
)) {
discard;
}
diff --git a/plugins/SimulationView/layers3d.shader b/plugins/SimulationView/layers3d.shader
index 03e279e9eb..a277606509 100644
--- a/plugins/SimulationView/layers3d.shader
+++ b/plugins/SimulationView/layers3d.shader
@@ -154,7 +154,7 @@ geometry41core =
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
return;
}
- if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) {
+ if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10) || v_line_type[0] == 11)) {
return;
}
if ((u_show_skin == 0) && ((v_line_type[0] == 1) || (v_line_type[0] == 2) || (v_line_type[0] == 3))) {
@@ -256,6 +256,7 @@ fragment41core =
out vec4 frag_color;
uniform mediump vec4 u_ambientColor;
+ uniform mediump vec4 u_minimumAlbedo;
uniform highp vec3 u_lightPosition;
void main()
@@ -263,7 +264,7 @@ fragment41core =
mediump vec4 finalColor = vec4(0.0);
float alpha = f_color.a;
- finalColor.rgb += f_color.rgb * 0.3;
+ finalColor.rgb += f_color.rgb * 0.2 + u_minimumAlbedo.rgb;
highp vec3 normal = normalize(f_normal);
highp vec3 light_dir = normalize(u_lightPosition - f_vertex);
@@ -285,6 +286,7 @@ u_extruder_opacity = [1.0, 1.0, 1.0, 1.0]
u_specularColor = [0.4, 0.4, 0.4, 1.0]
u_ambientColor = [0.3, 0.3, 0.3, 0.0]
u_diffuseColor = [1.0, 0.79, 0.14, 1.0]
+u_minimumAlbedo = [0.1, 0.1, 0.1, 1.0]
u_shininess = 20.0
u_show_travel_moves = 0
diff --git a/plugins/SimulationView/layers_shadow.shader b/plugins/SimulationView/layers_shadow.shader
index 7ceccff21e..6149cc1703 100644
--- a/plugins/SimulationView/layers_shadow.shader
+++ b/plugins/SimulationView/layers_shadow.shader
@@ -45,19 +45,23 @@ fragment =
void main()
{
- if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
+ if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5))
+ { // actually, 8 and 9
// discard movements
discard;
}
- // support: 4, 5, 7, 10
+ // support: 4, 5, 7, 10, 11
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
- ((v_line_type >= 4.5) && (v_line_type <= 5.5))
- )) {
+ ((v_line_type >= 4.5) && (v_line_type <= 5.5)) ||
+ ((v_line_type >= 10.5) && (v_line_type <= 11.5))
+ ))
+ {
discard;
}
+
// skin: 1, 2, 3
if ((u_show_skin == 0) && (
(v_line_type >= 0.5) && (v_line_type <= 3.5)
@@ -65,7 +69,8 @@ fragment =
discard;
}
// infill:
- if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) {
+ if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5))
+ {
// discard movements
discard;
}
@@ -117,12 +122,13 @@ fragment41core =
// discard movements
discard;
}
- // helpers: 4, 5, 7, 10
+ // helpers: 4, 5, 7, 10, 11
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
- ((v_line_type >= 4.5) && (v_line_type <= 5.5))
+ ((v_line_type >= 4.5) && (v_line_type <= 5.5)) ||
+ ((v_line_type >= 10.5) && (v_line_type <= 11.5))
)) {
discard;
}
diff --git a/plugins/SimulationView/plugin.json b/plugins/SimulationView/plugin.json
index 0e7bec0626..3ccf91b9e6 100644
--- a/plugins/SimulationView/plugin.json
+++ b/plugins/SimulationView/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Simulation View",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides the Simulation view.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/SliceInfoPlugin/MoreInfoWindow.qml b/plugins/SliceInfoPlugin/MoreInfoWindow.qml
index 985ebe94a2..e00ad6730d 100644
--- a/plugins/SliceInfoPlugin/MoreInfoWindow.qml
+++ b/plugins/SliceInfoPlugin/MoreInfoWindow.qml
@@ -98,7 +98,7 @@ UM.Dialog
RadioButton
{
id: dontSendButton
- text: catalog.i18nc("@text:window", "I don't want to send these data")
+ text: catalog.i18nc("@text:window", "I don't want to send this data")
exclusiveGroup: group
onClicked:
{
@@ -108,7 +108,7 @@ UM.Dialog
RadioButton
{
id: allowSendButton
- text: catalog.i18nc("@text:window", "Allow sending these data to Ultimaker and help us improve Cura")
+ text: catalog.i18nc("@text:window", "Allow sending this data to Ultimaker and help us improve Cura")
exclusiveGroup: group
onClicked:
{
diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py
index 2e9e557c4a..5149b6a6a6 100755
--- a/plugins/SliceInfoPlugin/SliceInfo.py
+++ b/plugins/SliceInfoPlugin/SliceInfo.py
@@ -5,6 +5,7 @@ import json
import os
import platform
import time
+from typing import cast, Optional, Set
from PyQt5.QtCore import pyqtSlot, QObject
@@ -16,7 +17,7 @@ from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.Qt.Duration import DurationFormat
-from typing import cast, Optional
+
from .SliceInfoJob import SliceInfoJob
@@ -32,30 +33,35 @@ class SliceInfo(QObject, Extension):
def __init__(self, parent = None):
QObject.__init__(self, parent)
Extension.__init__(self)
- Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
- Application.getInstance().getPreferences().addPreference("info/send_slice_info", True)
- Application.getInstance().getPreferences().addPreference("info/asked_send_slice_info", False)
+
+ self._application = Application.getInstance()
+
+ self._application.getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
+ self._application.getPreferences().addPreference("info/send_slice_info", True)
+ self._application.getPreferences().addPreference("info/asked_send_slice_info", False)
self._more_info_dialog = None
self._example_data_content = None
- if not Application.getInstance().getPreferences().getValue("info/asked_send_slice_info"):
+ self._application.initializationFinished.connect(self._onAppInitialized)
+
+ def _onAppInitialized(self):
+ # DO NOT read any preferences values in the constructor because at the time plugins are created, no version
+ # upgrade has been performed yet because version upgrades are plugins too!
+ if not self._application.getPreferences().getValue("info/asked_send_slice_info"):
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."),
lifetime = 0,
dismissable = False,
title = catalog.i18nc("@info:title", "Collecting Data"))
self.send_slice_info_message.addAction("MoreInfo", name = catalog.i18nc("@action:button", "More info"), icon = None,
- description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK)
+ description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK)
self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None,
- description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
+ description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
self.send_slice_info_message.show()
- Application.getInstance().initializationFinished.connect(self._onAppInitialized)
-
- def _onAppInitialized(self):
if self._more_info_dialog is None:
self._more_info_dialog = self._createDialog("MoreInfoWindow.qml")
@@ -95,13 +101,29 @@ class SliceInfo(QObject, Extension):
def setSendSliceInfo(self, enabled: bool):
Application.getInstance().getPreferences().setValue("info/send_slice_info", enabled)
+ def _getUserModifiedSettingKeys(self) -> list:
+ from cura.CuraApplication import CuraApplication
+ application = cast(CuraApplication, Application.getInstance())
+ machine_manager = application.getMachineManager()
+ global_stack = machine_manager.activeMachine
+
+ user_modified_setting_keys = set() # type: Set[str]
+
+ for stack in [global_stack] + list(global_stack.extruders.values()):
+ # Get all settings in user_changes and quality_changes
+ all_keys = stack.userChanges.getAllKeys() | stack.qualityChanges.getAllKeys()
+ user_modified_setting_keys |= all_keys
+
+ return list(sorted(user_modified_setting_keys))
+
def _onWriteStarted(self, output_device):
try:
if not Application.getInstance().getPreferences().getValue("info/send_slice_info"):
Logger.log("d", "'info/send_slice_info' is turned off.")
return # Do nothing, user does not want to send data
- application = Application.getInstance()
+ from cura.CuraApplication import CuraApplication
+ application = cast(CuraApplication, Application.getInstance())
machine_manager = application.getMachineManager()
print_information = application.getPrintInformation()
@@ -164,6 +186,8 @@ class SliceInfo(QObject, Extension):
data["quality_profile"] = global_stack.quality.getMetaData().get("quality_type")
+ data["user_modified_setting_keys"] = self._getUserModifiedSettingKeys()
+
data["models"] = []
# Listing all files placed on the build plate
for node in DepthFirstIterator(application.getController().getScene().getRoot()):
diff --git a/plugins/SliceInfoPlugin/__init__.py b/plugins/SliceInfoPlugin/__init__.py
index 7f1dfa5336..440ca8ec40 100644
--- a/plugins/SliceInfoPlugin/__init__.py
+++ b/plugins/SliceInfoPlugin/__init__.py
@@ -1,12 +1,11 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+
from . import SliceInfo
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
+
def getMetaData():
- return {
- }
+ return {}
def register(app):
return { "extension": SliceInfo.SliceInfo()}
\ No newline at end of file
diff --git a/plugins/SliceInfoPlugin/example_data.json b/plugins/SliceInfoPlugin/example_data.json
index ec953e0842..5fc4175e60 100644
--- a/plugins/SliceInfoPlugin/example_data.json
+++ b/plugins/SliceInfoPlugin/example_data.json
@@ -56,6 +56,7 @@
}
],
"quality_profile": "fast",
+ "user_modified_setting_keys": ["layer_height", "wall_line_width", "infill_sparse_density"],
"models": [
{
"hash": "b72789b9beb5366dff20b1cf501020c3d4d4df7dc2295ecd0fddd0a6436df070",
diff --git a/plugins/SliceInfoPlugin/plugin.json b/plugins/SliceInfoPlugin/plugin.json
index d1c643266b..8ff0e194fb 100644
--- a/plugins/SliceInfoPlugin/plugin.json
+++ b/plugins/SliceInfoPlugin/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Slice info",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Submits anonymous slice info. Can be disabled through preferences.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py
index b9ad5c8829..ec00329f86 100644
--- a/plugins/SolidView/SolidView.py
+++ b/plugins/SolidView/SolidView.py
@@ -12,7 +12,6 @@ from UM.Math.Color import Color
from UM.View.GL.OpenGL import OpenGL
from cura.Settings.ExtruderManager import ExtruderManager
-from cura.Settings.ExtrudersModel import ExtrudersModel
import math
@@ -29,13 +28,16 @@ class SolidView(View):
self._non_printing_shader = None
self._support_mesh_shader = None
- self._extruders_model = ExtrudersModel()
+ self._extruders_model = None
self._theme = None
def beginRendering(self):
scene = self.getController().getScene()
renderer = self.getRenderer()
+ if not self._extruders_model:
+ self._extruders_model = Application.getInstance().getExtrudersModel()
+
if not self._theme:
self._theme = Application.getInstance().getTheme()
@@ -67,8 +69,7 @@ class SolidView(View):
if support_angle_stack is not None and Application.getInstance().getPreferences().getValue("view/show_overhang"):
angle = support_angle_stack.getProperty("support_angle", "value")
# Make sure the overhang angle is valid before passing it to the shader
- # Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None)
- if angle is not None and global_container_stack.getProperty("support_angle", "validationState") in [None, ValidatorState.Valid]:
+ if angle is not None and angle >= 0 and angle <= 90:
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle)))
else:
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang.
diff --git a/plugins/SolidView/__init__.py b/plugins/SolidView/__init__.py
index db2e48f489..34148fcf05 100644
--- a/plugins/SolidView/__init__.py
+++ b/plugins/SolidView/__init__.py
@@ -10,7 +10,8 @@ def getMetaData():
return {
"view": {
"name": i18n_catalog.i18nc("@item:inmenu", "Solid view"),
- "weight": 0
+ "weight": 0,
+ "visible": False
}
}
diff --git a/plugins/SolidView/plugin.json b/plugins/SolidView/plugin.json
index 6d6bda96f0..b3f62221c5 100644
--- a/plugins/SolidView/plugin.json
+++ b/plugins/SolidView/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Solid View",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides a normal solid mesh view.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
\ No newline at end of file
diff --git a/plugins/SupportEraser/plugin.json b/plugins/SupportEraser/plugin.json
index 5ccb639913..fa6d6d230e 100644
--- a/plugins/SupportEraser/plugin.json
+++ b/plugins/SupportEraser/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Support Eraser",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Creates an eraser mesh to block the printing of support in certain places",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/Toolbox/plugin.json b/plugins/Toolbox/plugin.json
index 12d4042b6b..61dc0429f5 100644
--- a/plugins/Toolbox/plugin.json
+++ b/plugins/Toolbox/plugin.json
@@ -1,7 +1,7 @@
{
"name": "Toolbox",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
- "api": 4,
+ "version": "1.0.1",
+ "api": "6.0",
"description": "Find, manage and install new Cura packages."
}
diff --git a/plugins/Toolbox/resources/images/loading.gif b/plugins/Toolbox/resources/images/loading.gif
deleted file mode 100644
index 43cc1ed6d7..0000000000
Binary files a/plugins/Toolbox/resources/images/loading.gif and /dev/null differ
diff --git a/plugins/Toolbox/resources/images/loading.svg b/plugins/Toolbox/resources/images/loading.svg
deleted file mode 100644
index 1ceb4a8d7f..0000000000
--- a/plugins/Toolbox/resources/images/loading.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/plugins/Toolbox/resources/qml/RatingWidget.qml b/plugins/Toolbox/resources/qml/RatingWidget.qml
new file mode 100644
index 0000000000..441cf238f7
--- /dev/null
+++ b/plugins/Toolbox/resources/qml/RatingWidget.qml
@@ -0,0 +1,106 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.1
+import UM 1.0 as UM
+import Cura 1.1 as Cura
+Item
+{
+ id: ratingWidget
+
+ property real rating: 0
+ property int indexHovered: -1
+ property string packageId: ""
+
+ property int userRating: 0
+ property bool canRate: false
+
+ signal rated(int rating)
+
+ width: contentRow.width
+ height: contentRow.height
+ MouseArea
+ {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: ratingWidget.canRate
+ acceptedButtons: Qt.NoButton
+ onExited:
+ {
+ if(ratingWidget.canRate)
+ {
+ ratingWidget.indexHovered = -1
+ }
+ }
+
+ Row
+ {
+ id: contentRow
+ height: childrenRect.height
+ Repeater
+ {
+ model: 5 // We need to get 5 stars
+ Button
+ {
+ id: control
+ hoverEnabled: true
+ onHoveredChanged:
+ {
+ if(hovered && ratingWidget.canRate)
+ {
+ indexHovered = index
+ }
+ }
+
+ ToolTip.visible: control.hovered && !ratingWidget.canRate
+ ToolTip.text: !Cura.API.account.isLoggedIn ? catalog.i18nc("@label", "You need to login first before you can rate"): catalog.i18nc("@label", "You need to install the package before you can rate")
+
+ property bool isStarFilled:
+ {
+ // If the entire widget is hovered, override the actual rating.
+ if(ratingWidget.indexHovered >= 0)
+ {
+ return indexHovered >= index
+ }
+
+ if(ratingWidget.userRating > 0)
+ {
+ return userRating >= index +1
+ }
+
+ return rating >= index + 1
+ }
+
+ contentItem: Item {}
+ height: UM.Theme.getSize("rating_star").height
+ width: UM.Theme.getSize("rating_star").width
+ background: UM.RecolorImage
+ {
+ source: UM.Theme.getIcon(control.isStarFilled ? "star_filled" : "star_empty")
+ sourceSize.width: width
+ sourceSize.height: height
+
+ // Unfilled stars should always have the default color. Only filled stars should change on hover
+ color:
+ {
+ if(!ratingWidget.canRate)
+ {
+ return UM.Theme.getColor("rating_star")
+ }
+ if((ratingWidget.indexHovered >= 0 || ratingWidget.userRating > 0) && isStarFilled)
+ {
+ return UM.Theme.getColor("primary")
+ }
+ return UM.Theme.getColor("rating_star")
+ }
+ }
+ onClicked:
+ {
+ if(ratingWidget.canRate)
+ {
+ rated(index + 1) // Notify anyone who cares about this.
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/Toolbox/resources/qml/SmallRatingWidget.qml b/plugins/Toolbox/resources/qml/SmallRatingWidget.qml
new file mode 100644
index 0000000000..965b81dc0f
--- /dev/null
+++ b/plugins/Toolbox/resources/qml/SmallRatingWidget.qml
@@ -0,0 +1,36 @@
+import QtQuick 2.3
+import QtQuick.Controls 1.4
+import UM 1.1 as UM
+import Cura 1.1 as Cura
+
+Row
+{
+ id: rating
+ height: UM.Theme.getSize("rating_star").height
+ visible: model.average_rating > 0 //Has a rating at all.
+ spacing: UM.Theme.getSize("thick_lining").width
+ width: starIcon.width + spacing + numRatingsLabel.width
+ UM.RecolorImage
+ {
+ id: starIcon
+ source: UM.Theme.getIcon("star_filled")
+ color: model.user_rating == 0 ? UM.Theme.getColor("rating_star") : UM.Theme.getColor("primary")
+ height: UM.Theme.getSize("rating_star").height
+ width: UM.Theme.getSize("rating_star").width
+ sourceSize.height: height
+ sourceSize.width: width
+ }
+
+ Label
+ {
+ id: numRatingsLabel
+ text: model.average_rating != undefined ? model.average_rating.toFixed(1) + " (" + model.num_ratings + " " + catalog.i18nc("@label", "ratings") + ")": ""
+ verticalAlignment: Text.AlignVCenter
+ height: starIcon.height
+ width: contentWidth
+ anchors.verticalCenter: starIcon.verticalCenter
+ color: starIcon.color
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ }
+}
\ No newline at end of file
diff --git a/plugins/Toolbox/resources/qml/Toolbox.qml b/plugins/Toolbox/resources/qml/Toolbox.qml
index 2a56898503..d15d98eed7 100644
--- a/plugins/Toolbox/resources/qml/Toolbox.qml
+++ b/plugins/Toolbox/resources/qml/Toolbox.qml
@@ -10,21 +10,21 @@ Window
{
id: base
property var selection: null
- title: catalog.i18nc("@title", "Toolbox")
+ title: catalog.i18nc("@title", "Marketplace")
modality: Qt.ApplicationModal
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
- width: 720 * screenScaleFactor
- height: 640 * screenScaleFactor
+ width: Math.floor(720 * screenScaleFactor)
+ height: Math.floor(640 * screenScaleFactor)
minimumWidth: width
maximumWidth: minimumWidth
minimumHeight: height
maximumHeight: minimumHeight
- color: UM.Theme.getColor("sidebar")
+ color: UM.Theme.getColor("main_background")
UM.I18nCatalog
{
id: catalog
- name:"cura"
+ name: "cura"
}
Item
{
@@ -33,11 +33,12 @@ Window
{
id: header
}
+
Item
{
id: mainView
width: parent.width
- z: -1
+ z: parent.z - 1
anchors
{
top: header.bottom
@@ -75,6 +76,7 @@ Window
visible: toolbox.viewCategory == "installed"
}
}
+
ToolboxFooter
{
id: footer
@@ -93,6 +95,7 @@ Window
licenseDialog.show();
}
}
+
ToolboxLicenseDialog
{
id: licenseDialog
diff --git a/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml b/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml
index 04b055ed66..b653f1a73b 100644
--- a/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.3
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@@ -15,7 +15,7 @@ Item
{
id: sidebar
}
- Rectangle
+ Item
{
id: header
anchors
@@ -55,10 +55,11 @@ Item
bottomMargin: UM.Theme.getSize("default_margin").height
}
text: details.name || ""
- font: UM.Theme.getFont("large")
+ font: UM.Theme.getFont("large_bold")
wrapMode: Text.WordWrap
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
+ renderType: Text.NativeRendering
}
Label
{
@@ -70,6 +71,7 @@ Item
left: title.left
topMargin: UM.Theme.getSize("default_margin").height
}
+ renderType: Text.NativeRendering
}
Column
{
@@ -82,11 +84,20 @@ Item
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
width: childrenRect.width
+
Label
{
- text: catalog.i18nc("@label", "Contact") + ":"
- font: UM.Theme.getFont("very_small")
+ text: catalog.i18nc("@label", "Website") + ":"
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
+ renderType: Text.NativeRendering
+ }
+ Label
+ {
+ text: catalog.i18nc("@label", "Email") + ":"
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text_medium")
+ renderType: Text.NativeRendering
}
}
Column
@@ -100,23 +111,39 @@ Item
topMargin: UM.Theme.getSize("default_margin").height
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
+
+ Label
+ {
+ text:
+ {
+ if (details.website)
+ {
+ return "" + details.website + ""
+ }
+ return ""
+ }
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ linkColor: UM.Theme.getColor("text_link")
+ onLinkActivated: Qt.openUrlExternally(link)
+ renderType: Text.NativeRendering
+ }
+
Label
{
text:
{
if (details.email)
{
- return ""+details.name+""
- }
- else
- {
- return ""+details.name+""
+ return "" + details.email + ""
}
+ return ""
}
- font: UM.Theme.getFont("very_small")
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
+ renderType: Text.NativeRendering
}
}
Rectangle
diff --git a/plugins/Toolbox/resources/qml/ToolboxBackColumn.qml b/plugins/Toolbox/resources/qml/ToolboxBackColumn.qml
index 5c60e368a9..dba9f19ccd 100644
--- a/plugins/Toolbox/resources/qml/ToolboxBackColumn.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxBackColumn.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.2
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@@ -23,6 +23,7 @@ Item
{
id: button
text: catalog.i18nc("@action:button", "Back")
+ enabled: !toolbox.isDownloading
UM.RecolorImage
{
id: backArrow
@@ -39,7 +40,7 @@ Item
width: width
height: height
}
- color: button.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")
+ color: button.enabled ? (button.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive")
source: UM.Theme.getIcon("arrow_left")
}
width: UM.Theme.getSize("toolbox_back_button").width
@@ -59,10 +60,16 @@ Item
{
id: labelStyle
text: control.text
- color: control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")
- font: UM.Theme.getFont("default_bold")
- horizontalAlignment: Text.AlignRight
+ color: control.enabled ? (control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive")
+ font: UM.Theme.getFont("medium_bold")
+ horizontalAlignment: Text.AlignLeft
+ anchors
+ {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ }
width: control.width
+ renderType: Text.NativeRendering
}
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml b/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml
index 1efcde2110..db4e8c628f 100644
--- a/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml
@@ -1,135 +1,242 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.2
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
+ id: base
+
property var packageData
+ property var technicalDataSheetUrl:
+ {
+ var link = undefined
+ if ("Technical Data Sheet" in packageData.links)
+ {
+ // HACK: This is the way the old API (used in 3.6-beta) used to do it. For safety it's still here,
+ // but it can be removed over time.
+ link = packageData.links["Technical Data Sheet"]
+ }
+ else if ("technicalDataSheet" in packageData.links)
+ {
+ link = packageData.links["technicalDataSheet"]
+ }
+ return link
+ }
+ property var safetyDataSheetUrl:
+ {
+ var sds_name = "safetyDataSheet"
+ return (sds_name in packageData.links) ? packageData.links[sds_name] : undefined
+ }
+ property var printingGuidelinesUrl:
+ {
+ var pg_name = "printingGuidelines"
+ return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
+ }
+
+ property var materialWebsiteUrl:
+ {
+ var pg_name = "website"
+ return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
+ }
anchors.topMargin: UM.Theme.getSize("default_margin").height
height: visible ? childrenRect.height : 0
- visible: packageData.type == "material" && packageData.has_configs
+
+ visible: packageData.type == "material" &&
+ (packageData.has_configs || technicalDataSheetUrl !== undefined ||
+ safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined ||
+ materialWebsiteUrl !== undefined)
+
+ Item
+ {
+ id: combatibilityItem
+ visible: packageData.has_configs
+ width: parent.width
+ // This is a bit of a hack, but the whole QML is pretty messy right now. This needs a big overhaul.
+ height: visible ? heading.height + table.height: 0
+
+ Label
+ {
+ id: heading
+ anchors.topMargin: UM.Theme.getSize("default_margin").height
+ width: parent.width
+ text: catalog.i18nc("@label", "Compatibility")
+ wrapMode: Text.WordWrap
+ color: UM.Theme.getColor("text_medium")
+ font: UM.Theme.getFont("medium")
+ renderType: Text.NativeRendering
+ }
+
+ TableView
+ {
+ id: table
+ anchors.top: heading.bottom
+ anchors.topMargin: UM.Theme.getSize("default_margin").height
+ width: parent.width
+ frameVisible: false
+
+ // Workaround for scroll issues (QTBUG-49652)
+ flickableItem.interactive: false
+ Component.onCompleted:
+ {
+ for (var i = 0; i < flickableItem.children.length; ++i)
+ {
+ flickableItem.children[i].enabled = false
+ }
+ }
+ selectionMode: 0
+ model: packageData.supported_configs
+ headerDelegate: Rectangle
+ {
+ color: UM.Theme.getColor("main_background")
+ height: UM.Theme.getSize("toolbox_chart_row").height
+ Label
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ elide: Text.ElideRight
+ text: styleData.value || ""
+ color: UM.Theme.getColor("text")
+ font: UM.Theme.getFont("default_bold")
+ renderType: Text.NativeRendering
+ }
+ Rectangle
+ {
+ anchors.bottom: parent.bottom
+ height: UM.Theme.getSize("default_lining").height
+ width: parent.width
+ color: "black"
+ }
+ }
+ rowDelegate: Item
+ {
+ height: UM.Theme.getSize("toolbox_chart_row").height
+ Label
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ elide: Text.ElideRight
+ text: styleData.value || ""
+ color: UM.Theme.getColor("text_medium")
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ }
+ }
+ itemDelegate: Item
+ {
+ height: UM.Theme.getSize("toolbox_chart_row").height
+ Label
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ elide: Text.ElideRight
+ text: styleData.value || ""
+ color: UM.Theme.getColor("text_medium")
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ }
+ }
+
+ Component
+ {
+ id: columnTextDelegate
+ Label
+ {
+ anchors.fill: parent
+ verticalAlignment: Text.AlignVCenter
+ text: styleData.value || ""
+ elide: Text.ElideRight
+ color: UM.Theme.getColor("text_medium")
+ font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
+ }
+ }
+
+ TableViewColumn
+ {
+ role: "machine"
+ title: "Machine"
+ width: Math.floor(table.width * 0.25)
+ delegate: columnTextDelegate
+ }
+ TableViewColumn
+ {
+ role: "print_core"
+ title: "Print Core"
+ width: Math.floor(table.width * 0.2)
+ }
+ TableViewColumn
+ {
+ role: "build_plate"
+ title: "Build Plate"
+ width: Math.floor(table.width * 0.225)
+ }
+ TableViewColumn
+ {
+ role: "support_material"
+ title: "Support"
+ width: Math.floor(table.width * 0.225)
+ }
+ TableViewColumn
+ {
+ role: "quality"
+ title: "Quality"
+ width: Math.floor(table.width * 0.1)
+ }
+ }
+ }
+
Label
{
- id: heading
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- width: parent.width
- text: catalog.i18nc("@label", "Compatibility")
- wrapMode: Text.WordWrap
- color: UM.Theme.getColor("text_medium")
- font: UM.Theme.getFont("medium")
- }
- TableView
- {
- id: table
- anchors.top: heading.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- width: parent.width
- frameVisible: false
+ id: data_sheet_links
+ anchors.top: combatibilityItem.bottom
+ anchors.topMargin: UM.Theme.getSize("default_margin").height / 2
+ visible: base.technicalDataSheetUrl !== undefined ||
+ base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined ||
+ base.materialWebsiteUrl !== undefined
+ height: visible ? contentHeight : 0
+ text:
+ {
+ var result = ""
+ if (base.technicalDataSheetUrl !== undefined)
+ {
+ var tds_name = catalog.i18nc("@action:label", "Technical Data Sheet")
+ result += "%2".arg(base.technicalDataSheetUrl).arg(tds_name)
+ }
+ if (base.safetyDataSheetUrl !== undefined)
+ {
+ if (result.length > 0)
+ {
+ result += " "
+ }
+ var sds_name = catalog.i18nc("@action:label", "Safety Data Sheet")
+ result += "%2".arg(base.safetyDataSheetUrl).arg(sds_name)
+ }
+ if (base.printingGuidelinesUrl !== undefined)
+ {
+ if (result.length > 0)
+ {
+ result += " "
+ }
+ var pg_name = catalog.i18nc("@action:label", "Printing Guidelines")
+ result += "%2".arg(base.printingGuidelinesUrl).arg(pg_name)
+ }
+ if (base.materialWebsiteUrl !== undefined)
+ {
+ if (result.length > 0)
+ {
+ result += " "
+ }
+ var pg_name = catalog.i18nc("@action:label", "Website")
+ result += "%2".arg(base.materialWebsiteUrl).arg(pg_name)
+ }
- // Workaround for scroll issues (QTBUG-49652)
- flickableItem.interactive: false
- Component.onCompleted:
- {
- for (var i = 0; i < flickableItem.children.length; ++i)
- {
- flickableItem.children[i].enabled = false
- }
- }
- selectionMode: 0
- model: packageData.supported_configs
- headerDelegate: Rectangle
- {
- color: UM.Theme.getColor("sidebar")
- height: UM.Theme.getSize("toolbox_chart_row").height
- Label
- {
- anchors.verticalCenter: parent.verticalCenter
- elide: Text.ElideRight
- text: styleData.value || ""
- color: UM.Theme.getColor("text")
- font: UM.Theme.getFont("default_bold")
- }
- Rectangle
- {
- anchors.bottom: parent.bottom
- height: UM.Theme.getSize("default_lining").height
- width: parent.width
- color: "black"
- }
- }
- rowDelegate: Item
- {
- height: UM.Theme.getSize("toolbox_chart_row").height
- Label
- {
- anchors.verticalCenter: parent.verticalCenter
- elide: Text.ElideRight
- text: styleData.value || ""
- color: UM.Theme.getColor("text_medium")
- font: UM.Theme.getFont("default")
- }
- }
- itemDelegate: Item
- {
- height: UM.Theme.getSize("toolbox_chart_row").height
- Label
- {
- anchors.verticalCenter: parent.verticalCenter
- elide: Text.ElideRight
- text: styleData.value || ""
- color: UM.Theme.getColor("text_medium")
- font: UM.Theme.getFont("default")
- }
- }
-
- Component
- {
- id: columnTextDelegate
- Label
- {
- anchors.fill: parent
- verticalAlignment: Text.AlignVCenter
- text: styleData.value || ""
- elide: Text.ElideRight
- color: UM.Theme.getColor("text_medium")
- font: UM.Theme.getFont("default")
- }
- }
-
- TableViewColumn
- {
- role: "machine"
- title: "Machine"
- width: Math.floor(table.width * 0.25)
- delegate: columnTextDelegate
- }
- TableViewColumn
- {
- role: "print_core"
- title: "Print Core"
- width: Math.floor(table.width * 0.2)
- }
- TableViewColumn
- {
- role: "build_plate"
- title: "Build Plate"
- width: Math.floor(table.width * 0.225)
- }
- TableViewColumn
- {
- role: "support_material"
- title: "Support"
- width: Math.floor(table.width * 0.225)
- }
- TableViewColumn
- {
- role: "quality"
- title: "Quality"
- width: Math.floor(table.width * 0.1)
+ return result
}
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ linkColor: UM.Theme.getColor("text_link")
+ onLinkActivated: Qt.openUrlExternally(link)
+ renderType: Text.NativeRendering
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml b/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml
index 4aa8b883b7..e238132680 100644
--- a/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.2
+import QtQuick 2.10
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
@@ -17,7 +17,7 @@ UM.Dialog
// This dialog asks the user whether he/she wants to open a project file as a project or import models.
id: base
- title: catalog.i18nc("@title:window", "Confirm uninstall ") + toolbox.pluginToUninstall
+ title: catalog.i18nc("@title:window", "Confirm uninstall") + toolbox.pluginToUninstall
width: 450 * screenScaleFactor
height: 50 * screenScaleFactor + dialogText.height + buttonBar.height
@@ -66,6 +66,7 @@ UM.Dialog
anchors.right: parent.right
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
}
// Buttons
diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailList.qml b/plugins/Toolbox/resources/qml/ToolboxDetailList.qml
index 2b4cd838bf..4e44ea7d0b 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDetailList.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDetailList.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.2
+import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@@ -26,10 +26,19 @@ Item
}
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
spacing: UM.Theme.getSize("default_margin").height
+
Repeater
{
model: toolbox.packagesModel
- delegate: ToolboxDetailTile {}
+ delegate: Loader
+ {
+ // FIXME: When using asynchronous loading, on Mac and Windows, the tile may fail to load complete,
+ // leaving an empty space below the title part. We turn it off for now to make it work on Mac and
+ // Windows.
+ // Can be related to this QT bug: https://bugreports.qt.io/browse/QTBUG-50992
+ asynchronous: false
+ source: "ToolboxDetailTile.qml"
+ }
}
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml b/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml
index cf4bfcd545..fef2732af9 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml
@@ -1,17 +1,18 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.3
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
+import Cura 1.1 as Cura
+
Item
{
id: page
- property var details: base.selection
+ property var details: base.selection || {}
anchors.fill: parent
- width: parent.width
ToolboxBackColumn
{
id: sidebar
@@ -25,15 +26,12 @@ Item
right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width
}
- height: UM.Theme.getSize("toolbox_detail_header").height
- Image
+ height: childrenRect.height + 3 * UM.Theme.getSize("default_margin").width
+ Rectangle
{
id: thumbnail
width: UM.Theme.getSize("toolbox_thumbnail_medium").width
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
- fillMode: Image.PreserveAspectFit
- source: details.icon_url || "../images/logobot.svg"
- mipmap: true
anchors
{
top: parent.top
@@ -41,6 +39,14 @@ Item
leftMargin: UM.Theme.getSize("wide_margin").width
topMargin: UM.Theme.getSize("wide_margin").height
}
+ color: UM.Theme.getColor("main_background")
+ Image
+ {
+ anchors.fill: parent
+ fillMode: Image.PreserveAspectFit
+ source: details === null ? "" : (details.icon_url || "../images/logobot.svg")
+ mipmap: true
+ }
}
Label
@@ -51,16 +57,21 @@ Item
top: thumbnail.top
left: thumbnail.right
leftMargin: UM.Theme.getSize("default_margin").width
- right: parent.right
- rightMargin: UM.Theme.getSize("wide_margin").width
- bottomMargin: UM.Theme.getSize("default_margin").height
}
- text: details.name || ""
- font: UM.Theme.getFont("large")
+ text: details === null ? "" : (details.name || "")
+ font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text")
- wrapMode: Text.WordWrap
- width: parent.width
- height: UM.Theme.getSize("toolbox_property_label").height
+ width: contentWidth
+ height: contentHeight
+ renderType: Text.NativeRendering
+ }
+
+ SmallRatingWidget
+ {
+ anchors.left: title.right
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.verticalCenter: title.verticalCenter
+ property var model: details
}
Column
@@ -77,27 +88,38 @@ Item
height: childrenRect.height
Label
{
- text: catalog.i18nc("@label", "Version") + ":"
- font: UM.Theme.getFont("very_small")
+ text: catalog.i18nc("@label", "Your rating") + ":"
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
+ renderType: Text.NativeRendering
+ }
+ Label
+ {
+ text: catalog.i18nc("@label", "Version") + ":"
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text_medium")
+ renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Last updated") + ":"
- font: UM.Theme.getFont("very_small")
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
+ renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Author") + ":"
- font: UM.Theme.getFont("very_small")
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
+ renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Downloads") + ":"
- font: UM.Theme.getFont("very_small")
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
+ renderType: Text.NativeRendering
}
}
Column
@@ -112,54 +134,97 @@ Item
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
height: childrenRect.height
+ RatingWidget
+ {
+ id: rating
+ visible: details.type == "plugin"
+ packageId: details.id != undefined ? details.id: ""
+ userRating: details.user_rating != undefined ? details.user_rating: 0
+ canRate: toolbox.isInstalled(details.id) && Cura.API.account.isLoggedIn
+
+ onRated:
+ {
+ toolbox.ratePackage(details.id, rating)
+ // HACK: This is a far from optimal solution, but without major refactoring, this is the best we can
+ // do. Since a rework of this is scheduled, it shouldn't live that long...
+ var index = toolbox.pluginsAvailableModel.find("id", details.id)
+ if(index != -1)
+ {
+ if(details.user_rating == 0) // User never rated before.
+ {
+ toolbox.pluginsAvailableModel.setProperty(index, "num_ratings", details.num_ratings + 1)
+ }
+
+ toolbox.pluginsAvailableModel.setProperty(index, "user_rating", rating)
+
+
+ // Hack; This is because the current selection is an outdated copy, so we need to re-copy it.
+ base.selection = toolbox.pluginsAvailableModel.getItem(index)
+ return
+ }
+ index = toolbox.pluginsShowcaseModel.find("id", details.id)
+ if(index != -1)
+ {
+ if(details.user_rating == 0) // User never rated before.
+ {
+ toolbox.pluginsShowcaseModel.setProperty(index, "user_rating", rating)
+ }
+ toolbox.pluginsShowcaseModel.setProperty(index, "num_ratings", details.num_ratings + 1)
+
+ // Hack; This is because the current selection is an outdated copy, so we need to re-copy it.
+ base.selection = toolbox.pluginsShowcaseModel.getItem(index)
+ }
+ }
+ }
Label
{
- text: details.version || catalog.i18nc("@label", "Unknown")
- font: UM.Theme.getFont("very_small")
+ text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
+ renderType: Text.NativeRendering
}
Label
{
text:
{
+ if (details === null)
+ {
+ return ""
+ }
var date = new Date(details.last_updated)
return date.toLocaleString(UM.Preferences.getValue("general/language"))
}
- font: UM.Theme.getFont("very_small")
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
+ renderType: Text.NativeRendering
}
Label
{
text:
{
- if (details.author_email)
+ if (details === null)
{
- return "" + details.author_name + ""
+ return ""
}
else
{
return "" + details.author_name + ""
}
}
- font: UM.Theme.getFont("very_small")
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
+ renderType: Text.NativeRendering
}
Label
{
- text: details.download_count || catalog.i18nc("@label", "Unknown")
- font: UM.Theme.getFont("very_small")
+ text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
+ renderType: Text.NativeRendering
}
}
- Rectangle
- {
- color: UM.Theme.getColor("lining")
- width: parent.width
- height: UM.Theme.getSize("default_lining").height
- anchors.bottom: parent.bottom
- }
}
ToolboxDetailList
{
diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml
index 355fa5dece..c7bb1f60ac 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.2
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@@ -31,17 +31,19 @@ Item
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("medium_bold")
+ renderType: Text.NativeRendering
}
Label
{
anchors.top: packageName.bottom
width: parent.width
text: model.description
- maximumLineCount: 3
+ maximumLineCount: 25
elide: Text.ElideRight
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
}
}
@@ -52,7 +54,7 @@ Item
anchors.top: tile.top
width: childrenRect.width
height: childrenRect.height
-
+ packageData: model
}
ToolboxCompatibilityChart
diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml
index cd1e4cdbda..60fe095537 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml
@@ -1,40 +1,91 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.7
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
+import Cura 1.1 as Cura
Column
{
property bool installed: toolbox.isInstalled(model.id)
property bool canUpdate: toolbox.canUpdate(model.id)
+ property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
+ property var packageData
+
width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height
- ToolboxProgressButton
+ Item
{
- id: installButton
- active: toolbox.isDownloading && toolbox.activePackage == model
- complete: installed
- readyAction: function()
+ width: installButton.width
+ height: installButton.height
+ ToolboxProgressButton
{
- toolbox.activePackage = model
- toolbox.startDownload(model.download_url)
+ id: installButton
+ active: toolbox.isDownloading && toolbox.activePackage == model
+ onReadyAction:
+ {
+ toolbox.activePackage = model
+ toolbox.startDownload(model.download_url)
+ }
+ onActiveAction: toolbox.cancelDownload()
+
+ // Don't allow installing while another download is running
+ enabled: installed || (!(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired)
+ opacity: enabled ? 1.0 : 0.5
+ visible: !updateButton.visible && !installed// Don't show when the update button is visible
}
- activeAction: function()
+
+ Cura.SecondaryButton
{
- toolbox.cancelDownload()
+ visible: installed
+ onClicked: toolbox.viewCategory = "installed"
+ text: catalog.i18nc("@action:button", "Installed")
+ fixedWidthMode: true
+ width: installButton.width
+ height: installButton.height
}
- completeAction: function()
+ }
+
+ Label
+ {
+ wrapMode: Text.WordWrap
+ text: catalog.i18nc("@label:The string between and is the highlighted link", "Log in is required to install or update")
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ linkColor: UM.Theme.getColor("text_link")
+ visible: loginRequired
+ width: installButton.width
+ renderType: Text.NativeRendering
+
+ MouseArea
{
- toolbox.viewCategory = "installed"
+ anchors.fill: parent
+ onClicked: Cura.API.account.login()
+ }
+ }
+
+ Label
+ {
+ property var whereToBuyUrl:
+ {
+ var pg_name = "whereToBuy"
+ return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
+ }
+
+ renderType: Text.NativeRendering
+ text: catalog.i18nc("@label:The string between and is the highlighted link", "Buy material spools")
+ linkColor: UM.Theme.getColor("text_link")
+ visible: whereToBuyUrl != undefined
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: Qt.openUrlExternally(parent.whereToBuyUrl)
}
- // Don't allow installing while another download is running
- enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model)
- opacity: enabled ? 1.0 : 0.5
- visible: !updateButton.visible // Don't show when the update button is visible
}
ToolboxProgressButton
@@ -44,24 +95,28 @@ Column
readyLabel: catalog.i18nc("@action:button", "Update")
activeLabel: catalog.i18nc("@action:button", "Updating")
completeLabel: catalog.i18nc("@action:button", "Updated")
- readyAction: function()
+
+ onReadyAction:
{
toolbox.activePackage = model
toolbox.update(model.id)
}
- activeAction: function()
- {
- toolbox.cancelDownload()
- }
+ onActiveAction: toolbox.cancelDownload()
// Don't allow installing while another download is running
- enabled: !(toolbox.isDownloading && toolbox.activePackage != model)
+ enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
opacity: enabled ? 1.0 : 0.5
visible: canUpdate
}
+
Connections
{
target: toolbox
onInstallChanged: installed = toolbox.isInstalled(model.id)
onMetadataChanged: canUpdate = toolbox.canUpdate(model.id)
+ onFilterChanged:
+ {
+ installed = toolbox.isInstalled(model.id)
+ canUpdate = toolbox.canUpdate(model.id)
+ }
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsGrid.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsGrid.qml
index c586828969..a9fcb39b28 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDownloadsGrid.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsGrid.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.7
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
@@ -22,9 +22,10 @@ Column
text: gridArea.heading
width: parent.width
color: UM.Theme.getColor("text_medium")
- font: UM.Theme.getFont("medium")
+ font: UM.Theme.getFont("large")
+ renderType: Text.NativeRendering
}
- GridLayout
+ Grid
{
id: grid
width: parent.width - 2 * parent.padding
@@ -34,10 +35,12 @@ Column
Repeater
{
model: gridArea.model
- delegate: ToolboxDownloadsGridTile
+ delegate: Loader
{
- Layout.preferredWidth: (grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns
- Layout.preferredHeight: UM.Theme.getSize("toolbox_thumbnail_small").height
+ asynchronous: true
+ width: Math.round((grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns)
+ height: UM.Theme.getSize("toolbox_thumbnail_small").height
+ source: "ToolboxDownloadsGridTile.qml"
}
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.qml
index ebd4c006f8..a11c6ee963 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.qml
@@ -1,103 +1,27 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.3
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
import UM 1.1 as UM
+import Cura 1.1 as Cura
Item
{
- property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfPackagesByAuthor(model.id) : 1
+ id: toolboxDownloadsGridTile
+ property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1
property int installedPackages: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
height: childrenRect.height
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
- Rectangle
- {
- id: highlight
- anchors.fill: parent
- opacity: 0.0
- color: UM.Theme.getColor("primary")
- }
- Row
- {
- width: parent.width
- height: childrenRect.height
- spacing: Math.floor(UM.Theme.getSize("narrow_margin").width)
- Rectangle
- {
- id: thumbnail
- width: UM.Theme.getSize("toolbox_thumbnail_small").width
- height: UM.Theme.getSize("toolbox_thumbnail_small").height
- color: "white"
- border.width: UM.Theme.getSize("default_lining").width
- border.color: UM.Theme.getColor("lining")
- Image
- {
- anchors.centerIn: parent
- width: UM.Theme.getSize("toolbox_thumbnail_small").width - UM.Theme.getSize("wide_margin").width
- height: UM.Theme.getSize("toolbox_thumbnail_small").height - UM.Theme.getSize("wide_margin").width
- fillMode: Image.PreserveAspectFit
- source: model.icon_url || "../images/logobot.svg"
- mipmap: true
- }
- UM.RecolorImage
- {
- width: (parent.width * 0.4) | 0
- height: (parent.height * 0.4) | 0
- anchors
- {
- bottom: parent.bottom
- right: parent.right
- }
- sourceSize.width: width
- sourceSize.height: height
- visible: installedPackages != 0
- color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
- source: "../images/installed_check.svg"
- }
- }
- Column
- {
- width: parent.width - thumbnail.width - parent.spacing
- spacing: Math.floor(UM.Theme.getSize("narrow_margin").width)
- Label
- {
- id: name
- text: model.name
- width: parent.width
- wrapMode: Text.WordWrap
- color: UM.Theme.getColor("text")
- font: UM.Theme.getFont("default_bold")
- }
- Label
- {
- id: info
- text: model.description
- maximumLineCount: 2
- elide: Text.ElideRight
- width: parent.width
- wrapMode: Text.WordWrap
- color: UM.Theme.getColor("text_medium")
- font: UM.Theme.getFont("very_small")
- }
- }
- }
+
MouseArea
{
anchors.fill: parent
hoverEnabled: true
- onEntered:
- {
- thumbnail.border.color = UM.Theme.getColor("primary")
- highlight.opacity = 0.1
- }
- onExited:
- {
- thumbnail.border.color = UM.Theme.getColor("lining")
- highlight.opacity = 0.0
- }
+ onEntered: thumbnail.border.color = UM.Theme.getColor("primary")
+ onExited: thumbnail.border.color = UM.Theme.getColor("lining")
onClicked:
{
base.selection = model
@@ -127,4 +51,83 @@ Item
}
}
}
+
+ Rectangle
+ {
+ id: thumbnail
+ width: UM.Theme.getSize("toolbox_thumbnail_small").width
+ height: UM.Theme.getSize("toolbox_thumbnail_small").height
+ color: UM.Theme.getColor("main_background")
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: UM.Theme.getColor("lining")
+
+ Image
+ {
+ anchors.centerIn: parent
+ width: UM.Theme.getSize("toolbox_thumbnail_small").width - UM.Theme.getSize("wide_margin").width
+ height: UM.Theme.getSize("toolbox_thumbnail_small").height - UM.Theme.getSize("wide_margin").width
+ fillMode: Image.PreserveAspectFit
+ source: model.icon_url || "../images/logobot.svg"
+ mipmap: true
+ }
+ UM.RecolorImage
+ {
+ width: (parent.width * 0.4) | 0
+ height: (parent.height * 0.4) | 0
+ anchors
+ {
+ bottom: parent.bottom
+ right: parent.right
+ }
+ sourceSize.height: height
+ visible: installedPackages != 0
+ color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
+ source: "../images/installed_check.svg"
+ }
+ }
+ Item
+ {
+ anchors
+ {
+ left: thumbnail.right
+ leftMargin: Math.floor(UM.Theme.getSize("narrow_margin").width)
+ right: parent.right
+ top: parent.top
+ bottom: parent.bottom
+ }
+
+ Label
+ {
+ id: name
+ text: model.name
+ width: parent.width
+ elide: Text.ElideRight
+ color: UM.Theme.getColor("text")
+ font: UM.Theme.getFont("default_bold")
+ }
+ Label
+ {
+ id: info
+ text: model.description
+ elide: Text.ElideRight
+ width: parent.width
+ wrapMode: Text.WordWrap
+ color: UM.Theme.getColor("text")
+ font: UM.Theme.getFont("default")
+ anchors.top: name.bottom
+ anchors.bottom: rating.top
+ verticalAlignment: Text.AlignVCenter
+ maximumLineCount: 2
+ }
+ SmallRatingWidget
+ {
+ id: rating
+ anchors
+ {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+ }
+ }
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml
index 1089fcc51e..3e0dda4f4a 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml
@@ -12,7 +12,9 @@ ScrollView
width: parent.width
height: parent.height
style: UM.Theme.styles.scrollview
+
flickableItem.flickableDirection: Flickable.VerticalFlick
+
Column
{
width: base.width
@@ -30,7 +32,7 @@ ScrollView
id: allPlugins
width: parent.width
heading: toolbox.viewCategory == "material" ? catalog.i18nc("@label", "Community Contributions") : catalog.i18nc("@label", "Community Plugins")
- model: toolbox.viewCategory == "material" ? toolbox.authorsModel : toolbox.packagesModel
+ model: toolbox.viewCategory == "material" ? toolbox.materialsAvailableModel : toolbox.pluginsAvailableModel
}
ToolboxDownloadsGrid
diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml
index 46f5debfdd..795622cf82 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.7
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@@ -23,30 +23,34 @@ Rectangle
text: catalog.i18nc("@label", "Featured")
width: parent.width
color: UM.Theme.getColor("text_medium")
- font: UM.Theme.getFont("medium")
+ font: UM.Theme.getFont("large")
+ renderType: Text.NativeRendering
}
Grid
{
height: childrenRect.height
spacing: UM.Theme.getSize("wide_margin").width
columns: 3
- anchors
- {
- horizontalCenter: parent.horizontalCenter
- }
+ anchors.horizontalCenter: parent.horizontalCenter
+
Repeater
{
- model: {
- if ( toolbox.viewCategory == "plugin" )
+ model:
+ {
+ if (toolbox.viewCategory == "plugin")
{
return toolbox.pluginsShowcaseModel
}
- if ( toolbox.viewCategory == "material" )
+ if (toolbox.viewCategory == "material")
{
return toolbox.materialsShowcaseModel
}
}
- delegate: ToolboxDownloadsShowcaseTile {}
+ delegate: Loader
+ {
+ asynchronous: true
+ source: "ToolboxDownloadsShowcaseTile.qml"
+ }
}
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcaseTile.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcaseTile.qml
index 15d1ae302c..3699746b86 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcaseTile.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcaseTile.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
-// Toolbox is released under the terms of the LGPLv3 or higher.
+// Cura is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.7
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
@@ -9,96 +9,84 @@ import UM 1.1 as UM
Rectangle
{
- property int packageCount: toolbox.viewCategory == "material" ? toolbox.getTotalNumberOfPackagesByAuthor(model.id) : 1
+ property int packageCount: toolbox.viewCategory == "material" ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1
property int installedPackages: toolbox.viewCategory == "material" ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
id: tileBase
width: UM.Theme.getSize("toolbox_thumbnail_large").width + (2 * UM.Theme.getSize("default_lining").width)
- height: thumbnail.height + packageNameBackground.height + (2 * UM.Theme.getSize("default_lining").width)
+ height: thumbnail.height + packageName.height + rating.height + UM.Theme.getSize("default_margin").width
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
- color: "transparent"
- Rectangle
+ color: UM.Theme.getColor("main_background")
+ Image
{
id: thumbnail
- color: "white"
- width: UM.Theme.getSize("toolbox_thumbnail_large").width
- height: UM.Theme.getSize("toolbox_thumbnail_large").height
+ height: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height
+ width: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height
+ fillMode: Image.PreserveAspectFit
+ source: model.icon_url || "../images/logobot.svg"
+ mipmap: true
anchors
{
top: parent.top
+ topMargin: UM.Theme.getSize("default_margin").height
horizontalCenter: parent.horizontalCenter
- topMargin: UM.Theme.getSize("default_lining").width
}
- Image
+ }
+ Label
+ {
+ id: packageName
+ text: model.name
+ anchors
{
- anchors.centerIn: parent
- width: UM.Theme.getSize("toolbox_thumbnail_large").width - 2 * UM.Theme.getSize("default_margin").width
- height: UM.Theme.getSize("toolbox_thumbnail_large").height - 2 * UM.Theme.getSize("default_margin").height
- fillMode: Image.PreserveAspectFit
- source: model.icon_url || "../images/logobot.svg"
- mipmap: true
+ horizontalCenter: parent.horizontalCenter
+ top: thumbnail.bottom
}
- UM.RecolorImage
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ renderType: Text.NativeRendering
+ height: UM.Theme.getSize("toolbox_heading_label").height
+ width: parent.width - UM.Theme.getSize("default_margin").width
+ wrapMode: Text.WordWrap
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium_bold")
+ color: UM.Theme.getColor("text")
+ }
+ UM.RecolorImage
+ {
+ width: (parent.width * 0.20) | 0
+ height: width
+ anchors
{
- width: (parent.width * 0.3) | 0
- height: (parent.height * 0.3) | 0
- anchors
- {
- bottom: parent.bottom
- right: parent.right
- bottomMargin: UM.Theme.getSize("default_lining").width
- }
- sourceSize.width: width
- sourceSize.height: height
- visible: installedPackages != 0
- color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
- source: "../images/installed_check.svg"
+ bottom: bottomBorder.top
+ right: parent.right
}
+ visible: installedPackages != 0
+ color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
+ source: "../images/installed_check.svg"
+ }
+
+ SmallRatingWidget
+ {
+ id: rating
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: UM.Theme.getSize("narrow_margin").height
+ anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle
{
- id: packageNameBackground
+ id: bottomBorder
color: UM.Theme.getColor("primary")
- anchors
- {
- top: thumbnail.bottom
- horizontalCenter: parent.horizontalCenter
- }
- height: UM.Theme.getSize("toolbox_heading_label").height
+ anchors.bottom: parent.bottom
width: parent.width
- Label
- {
- id: packageName
- text: model.name
- anchors
- {
- horizontalCenter: parent.horizontalCenter
- }
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter
- height: UM.Theme.getSize("toolbox_heading_label").height
- width: parent.width
- wrapMode: Text.WordWrap
- color: UM.Theme.getColor("button_text")
- font: UM.Theme.getFont("medium_bold")
- }
+ height: UM.Theme.getSize("toolbox_header_highlight").height
}
+
MouseArea
{
anchors.fill: parent
hoverEnabled: true
- onEntered:
- {
- packageName.color = UM.Theme.getColor("button_text_hover")
- packageNameBackground.color = UM.Theme.getColor("primary_hover")
- tileBase.border.color = UM.Theme.getColor("primary_hover")
- }
- onExited:
- {
- packageName.color = UM.Theme.getColor("button_text")
- packageNameBackground.color = UM.Theme.getColor("primary")
- tileBase.border.color = UM.Theme.getColor("lining")
- }
+ onEntered: tileBase.border.color = UM.Theme.getColor("primary")
+ onExited: tileBase.border.color = UM.Theme.getColor("lining")
onClicked:
{
base.selection = model
diff --git a/plugins/Toolbox/resources/qml/ToolboxErrorPage.qml b/plugins/Toolbox/resources/qml/ToolboxErrorPage.qml
index 600ae2b39f..e57e63dbb9 100644
--- a/plugins/Toolbox/resources/qml/ToolboxErrorPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxErrorPage.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.7
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
@@ -18,5 +18,6 @@ Rectangle
{
centerIn: parent
}
+ renderType: Text.NativeRendering
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxFooter.qml b/plugins/Toolbox/resources/qml/ToolboxFooter.qml
index 5c2a6577ad..6f46e939ff 100644
--- a/plugins/Toolbox/resources/qml/ToolboxFooter.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxFooter.qml
@@ -1,22 +1,24 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.2
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick 2.10
+import QtQuick.Controls 2.3
+
import UM 1.1 as UM
+import Cura 1.0 as Cura
Item
{
id: footer
width: parent.width
anchors.bottom: parent.bottom
- height: visible ? Math.floor(UM.Theme.getSize("toolbox_footer").height) : 0
+ height: visible ? UM.Theme.getSize("toolbox_footer").height : 0
+
Label
{
text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.")
color: UM.Theme.getColor("text")
- height: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
+ height: UM.Theme.getSize("toolbox_footer_button").height
verticalAlignment: Text.AlignVCenter
anchors
{
@@ -26,12 +28,12 @@ Item
right: restartButton.right
rightMargin: UM.Theme.getSize("default_margin").width
}
-
+ renderType: Text.NativeRendering
}
- Button
+
+ Cura.PrimaryButton
{
id: restartButton
- text: catalog.i18nc("@info:button", "Quit Cura")
anchors
{
top: parent.top
@@ -39,26 +41,11 @@ Item
right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width
}
- iconName: "dialog-restart"
+ height: UM.Theme.getSize("toolbox_footer_button").height
+ text: catalog.i18nc("@info:button", "Quit Cura")
onClicked: toolbox.restart()
- style: ButtonStyle
- {
- background: Rectangle
- {
- implicitWidth: UM.Theme.getSize("toolbox_footer_button").width
- implicitHeight: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
- color: control.hovered ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary")
- }
- label: Label
- {
- color: UM.Theme.getColor("button_text")
- font: UM.Theme.getFont("default_bold")
- text: control.text
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter
- }
- }
}
+
ToolboxShadow
{
visible: footer.visible
diff --git a/plugins/Toolbox/resources/qml/ToolboxHeader.qml b/plugins/Toolbox/resources/qml/ToolboxHeader.qml
index 9c9f967d54..087402d564 100644
--- a/plugins/Toolbox/resources/qml/ToolboxHeader.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxHeader.qml
@@ -21,11 +21,13 @@ Item
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
}
+
ToolboxTabButton
{
+ id: pluginsTabButton
text: catalog.i18nc("@title:tab", "Plugins")
active: toolbox.viewCategory == "plugin" && enabled
- enabled: toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
+ enabled: !toolbox.isDownloading && toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
onClicked:
{
toolbox.filterModelByProp("packages", "type", "plugin")
@@ -36,9 +38,10 @@ Item
ToolboxTabButton
{
+ id: materialsTabButton
text: catalog.i18nc("@title:tab", "Materials")
active: toolbox.viewCategory == "material" && enabled
- enabled: toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
+ enabled: !toolbox.isDownloading && toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
onClicked:
{
toolbox.filterModelByProp("authors", "package_types", "material")
@@ -49,8 +52,10 @@ Item
}
ToolboxTabButton
{
+ id: installedTabButton
text: catalog.i18nc("@title:tab", "Installed")
active: toolbox.viewCategory == "installed"
+ enabled: !toolbox.isDownloading
anchors
{
right: parent.right
diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml
index 939cf0b505..a85a69cbac 100644
--- a/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml
@@ -1,11 +1,12 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.7
+import QtQuick 2.10
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
+
import UM 1.1 as UM
ScrollView
@@ -16,47 +17,44 @@ ScrollView
height: parent.height
style: UM.Theme.styles.scrollview
flickableItem.flickableDirection: Flickable.VerticalFlick
+
Column
{
spacing: UM.Theme.getSize("default_margin").height
+ visible: toolbox.pluginsInstalledModel.items.length > 0
+ height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
+
anchors
{
right: parent.right
left: parent.left
- leftMargin: UM.Theme.getSize("wide_margin").width
- topMargin: UM.Theme.getSize("wide_margin").height
- bottomMargin: UM.Theme.getSize("wide_margin").height
+ margins: UM.Theme.getSize("default_margin").width
top: parent.top
}
- height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
+
Label
{
- visible: toolbox.pluginsInstalledModel.items.length > 0
- width: parent.width
+ width: page.width
text: catalog.i18nc("@title:tab", "Plugins")
color: UM.Theme.getColor("text_medium")
- font: UM.Theme.getFont("medium")
+ font: UM.Theme.getFont("large")
+ renderType: Text.NativeRendering
}
Rectangle
{
- visible: toolbox.pluginsInstalledModel.items.length > 0
color: "transparent"
width: parent.width
- height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
+ height: childrenRect.height + UM.Theme.getSize("default_margin").width
border.color: UM.Theme.getColor("lining")
border.width: UM.Theme.getSize("default_lining").width
Column
{
- height: childrenRect.height
anchors
{
top: parent.top
right: parent.right
left: parent.left
- leftMargin: UM.Theme.getSize("default_margin").width
- rightMargin: UM.Theme.getSize("default_margin").width
- topMargin: UM.Theme.getSize("default_lining").width
- bottomMargin: UM.Theme.getSize("default_lining").width
+ margins: UM.Theme.getSize("default_margin").width
}
Repeater
{
@@ -68,32 +66,27 @@ ScrollView
}
Label
{
- visible: toolbox.materialsInstalledModel.items.length > 0
- width: page.width
text: catalog.i18nc("@title:tab", "Materials")
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
+ renderType: Text.NativeRendering
}
+
Rectangle
{
- visible: toolbox.materialsInstalledModel.items.length > 0
color: "transparent"
width: parent.width
- height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
+ height: childrenRect.height + UM.Theme.getSize("default_margin").width
border.color: UM.Theme.getColor("lining")
border.width: UM.Theme.getSize("default_lining").width
Column
{
- height: Math.max( UM.Theme.getSize("wide_margin").height, childrenRect.height)
anchors
{
top: parent.top
right: parent.right
left: parent.left
- leftMargin: UM.Theme.getSize("default_margin").width
- rightMargin: UM.Theme.getSize("default_margin").width
- topMargin: UM.Theme.getSize("default_lining").width
- bottomMargin: UM.Theme.getSize("default_lining").width
+ margins: UM.Theme.getSize("default_margin").width
}
Repeater
{
diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml
index b16564fdd2..f50c3f3ac6 100644
--- a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.7
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@@ -30,6 +30,7 @@ Item
CheckBox
{
id: disableButton
+ anchors.verticalCenter: pluginInfo.verticalCenter
checked: isEnabled
visible: model.type == "plugin"
width: visible ? UM.Theme.getSize("checkbox").width : 0
@@ -49,17 +50,20 @@ Item
width: parent.width
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap
- font: UM.Theme.getFont("default_bold")
+ font: UM.Theme.getFont("large_bold")
color: pluginInfo.color
+ renderType: Text.NativeRendering
}
Label
{
text: model.description
+ font: UM.Theme.getFont("default")
maximumLineCount: 3
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
color: pluginInfo.color
+ renderType: Text.NativeRendering
}
}
Column
@@ -80,6 +84,7 @@ Item
return model.author_name
}
}
+ font: UM.Theme.getFont("medium")
width: parent.width
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap
@@ -88,16 +93,19 @@ Item
onLinkActivated: Qt.openUrlExternally("mailto:" + model.author_email + "?Subject=Cura: " + model.name + " Plugin")
color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
linkColor: UM.Theme.getColor("text_link")
+ renderType: Text.NativeRendering
}
Label
{
text: model.version
+ font: UM.Theme.getFont("default")
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
+ renderType: Text.NativeRendering
}
}
ToolboxInstalledTileActions
diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml
index 8fd88b1cfd..61af84fbe5 100644
--- a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml
@@ -1,15 +1,18 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.7
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
+import Cura 1.1 as Cura
+
Column
{
property bool canUpdate: false
property bool canDowngrade: false
+ property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height
@@ -21,6 +24,7 @@ Column
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
width: parent.width
+ renderType: Text.NativeRendering
}
ToolboxProgressButton
@@ -30,59 +34,49 @@ Column
readyLabel: catalog.i18nc("@action:button", "Update")
activeLabel: catalog.i18nc("@action:button", "Updating")
completeLabel: catalog.i18nc("@action:button", "Updated")
- readyAction: function()
+ onReadyAction:
{
toolbox.activePackage = model
toolbox.update(model.id)
}
- activeAction: function()
- {
- toolbox.cancelDownload()
- }
+ onActiveAction: toolbox.cancelDownload()
+
// Don't allow installing while another download is running
- enabled: !(toolbox.isDownloading && toolbox.activePackage != model)
+ enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
opacity: enabled ? 1.0 : 0.5
visible: canUpdate
}
- Button
+ Label
+ {
+ wrapMode: Text.WordWrap
+ text: catalog.i18nc("@label:The string between and is the highlighted link", "Log in is required to update")
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ linkColor: UM.Theme.getColor("text_link")
+ visible: loginRequired
+ width: updateButton.width
+ renderType: Text.NativeRendering
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: Cura.API.account.login()
+ }
+ }
+
+ Cura.SecondaryButton
{
id: removeButton
text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall")
visible: !model.is_bundled && model.is_installed
enabled: !toolbox.isDownloading
- style: ButtonStyle
- {
- background: Rectangle
- {
- implicitWidth: UM.Theme.getSize("toolbox_action_button").width
- implicitHeight: UM.Theme.getSize("toolbox_action_button").height
- color: "transparent"
- border
- {
- width: UM.Theme.getSize("default_lining").width
- color:
- {
- if (control.hovered)
- {
- return UM.Theme.getColor("primary_hover")
- }
- else
- {
- return UM.Theme.getColor("lining")
- }
- }
- }
- }
- label: Label
- {
- text: control.text
- color: UM.Theme.getColor("text")
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter
- font: UM.Theme.getFont("default")
- }
- }
+
+ width: UM.Theme.getSize("toolbox_action_button").width
+ height: UM.Theme.getSize("toolbox_action_button").height
+
+ fixedWidthMode: true
+
onClicked: toolbox.checkPackageUsageAndUninstall(model.id)
Connections
{
diff --git a/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml b/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml
index b8baf7bc83..40b22c268d 100644
--- a/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.2
+import QtQuick 2.10
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
@@ -32,6 +32,7 @@ UM.Dialog
anchors.right: parent.right
text: licenseDialog.pluginName + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?")
wrapMode: Text.Wrap
+ renderType: Text.NativeRendering
}
TextArea
{
diff --git a/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml b/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml
index 1ba271dcab..025239bd43 100644
--- a/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.7
+import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
@@ -18,5 +18,6 @@ Rectangle
{
centerIn: parent
}
+ renderType: Text.NativeRendering
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml
index 2744e40ec9..4d4ae92e73 100644
--- a/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml
@@ -5,6 +5,7 @@ import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
+import Cura 1.0 as Cura
Item
@@ -18,16 +19,19 @@ Item
property var activeLabel: catalog.i18nc("@action:button", "Cancel")
property var completeLabel: catalog.i18nc("@action:button", "Installed")
- property var readyAction: null // Action when button is ready and clicked (likely install)
- property var activeAction: null // Action when button is active and clicked (likely cancel)
- property var completeAction: null // Action when button is complete and clicked (likely go to installed)
+ signal readyAction() // Action when button is ready and clicked (likely install)
+ signal activeAction() // Action when button is active and clicked (likely cancel)
+ signal completeAction() // Action when button is complete and clicked (likely go to installed)
width: UM.Theme.getSize("toolbox_action_button").width
height: UM.Theme.getSize("toolbox_action_button").height
- Button
+ Cura.PrimaryButton
{
id: button
+ width: UM.Theme.getSize("toolbox_action_button").width
+ height: UM.Theme.getSize("toolbox_action_button").height
+ fixedWidthMode: true
text:
{
if (complete)
@@ -47,114 +51,17 @@ Item
{
if (complete)
{
- return completeAction()
+ completeAction()
}
else if (active)
{
- return activeAction()
+ activeAction()
}
else
{
- return readyAction()
+ readyAction()
}
}
- style: ButtonStyle
- {
- background: Rectangle
- {
- implicitWidth: UM.Theme.getSize("toolbox_action_button").width
- implicitHeight: UM.Theme.getSize("toolbox_action_button").height
- color:
- {
- if (base.complete)
- {
- return "transparent"
- }
- else
- {
- if (control.hovered)
- {
- return UM.Theme.getColor("primary_hover")
- }
- else
- {
- return UM.Theme.getColor("primary")
- }
- }
- }
- border
- {
- width:
- {
- if (base.complete)
- {
- UM.Theme.getSize("default_lining").width
- }
- else
- {
- return 0
- }
- }
- color:
- {
- if (control.hovered)
- {
- return UM.Theme.getColor("primary_hover")
- }
- else
- {
- return UM.Theme.getColor("lining")
- }
- }
- }
- }
- label: Label
- {
- text: control.text
- color:
- {
- if (base.complete)
- {
- return UM.Theme.getColor("text")
- }
- else
- {
- if (control.hovered)
- {
- return UM.Theme.getColor("button_text_hover")
- }
- else
- {
- return UM.Theme.getColor("button_text")
- }
- }
- }
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter
- font:
- {
- if (base.complete)
- {
- return UM.Theme.getFont("default")
- }
- else
- {
- return UM.Theme.getFont("default_bold")
- }
- }
- }
- }
- }
-
- AnimatedImage
- {
- id: loader
- visible: active
- source: visible ? "../images/loading.gif" : ""
- width: UM.Theme.getSize("toolbox_loader").width
- height: UM.Theme.getSize("toolbox_loader").height
- anchors.right: button.left
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- anchors.verticalCenter: button.verticalCenter
+ busy: active
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxTabButton.qml b/plugins/Toolbox/resources/qml/ToolboxTabButton.qml
index 22fb6d73ca..5e1aeaa636 100644
--- a/plugins/Toolbox/resources/qml/ToolboxTabButton.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxTabButton.qml
@@ -1,51 +1,51 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
-import QtQuick 2.2
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick 2.10
+import QtQuick.Controls 2.3
import UM 1.1 as UM
Button
{
+ id: control
property bool active: false
- style: ButtonStyle
+ hoverEnabled: true
+
+ background: Item
{
- background: Rectangle
+ implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
+ implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
+ Rectangle
{
- color: "transparent"
- implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
- implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
- Rectangle
- {
- visible: control.active
- color: UM.Theme.getColor("sidebar_header_highlight_hover")
- anchors.bottom: parent.bottom
- width: parent.width
- height: UM.Theme.getSize("sidebar_header_highlight").height
- }
- }
- label: Label
- {
- text: control.text
- color:
- {
- if(control.hovered)
- {
- return UM.Theme.getColor("topbar_button_text_hovered");
- }
- if(control.active)
- {
- return UM.Theme.getColor("topbar_button_text_active");
- }
- else
- {
- return UM.Theme.getColor("topbar_button_text_inactive");
- }
- }
- font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic")
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter
+ visible: control.active
+ color: UM.Theme.getColor("primary")
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: UM.Theme.getSize("toolbox_header_highlight").height
}
}
-}
+ contentItem: Label
+ {
+ id: label
+ text: control.text
+ color:
+ {
+ if(control.hovered)
+ {
+ return UM.Theme.getColor("toolbox_header_button_text_hovered");
+ }
+ if(control.active)
+ {
+ return UM.Theme.getColor("toolbox_header_button_text_active");
+ }
+ else
+ {
+ return UM.Theme.getColor("toolbox_header_button_text_inactive");
+ }
+ }
+ font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ renderType: Text.NativeRendering
+ }
+}
\ No newline at end of file
diff --git a/plugins/Toolbox/src/AuthorsModel.py b/plugins/Toolbox/src/AuthorsModel.py
index 45424d7e42..877f8256ee 100644
--- a/plugins/Toolbox/src/AuthorsModel.py
+++ b/plugins/Toolbox/src/AuthorsModel.py
@@ -2,18 +2,19 @@
# Cura is released under the terms of the LGPLv3 or higher.
import re
-from typing import Dict
+from typing import Dict, List, Optional, Union
from PyQt5.QtCore import Qt, pyqtProperty, pyqtSignal
from UM.Qt.ListModel import ListModel
+
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
class AuthorsModel(ListModel):
- def __init__(self, parent = None):
+ def __init__(self, parent = None) -> None:
super().__init__(parent)
- self._metadata = None
+ self._metadata = None # type: Optional[List[Dict[str, Union[str, List[str], int]]]]
self.addRoleName(Qt.UserRole + 1, "id")
self.addRoleName(Qt.UserRole + 2, "name")
@@ -25,36 +26,40 @@ class AuthorsModel(ListModel):
self.addRoleName(Qt.UserRole + 8, "description")
# List of filters for queries. The result is the union of the each list of results.
- self._filter = {} # type: Dict[str,str]
+ self._filter = {} # type: Dict[str, str]
- def setMetadata(self, data):
- self._metadata = data
- self._update()
+ def setMetadata(self, data: List[Dict[str, Union[str, List[str], int]]]):
+ if self._metadata != data:
+ self._metadata = data
+ self._update()
- def _update(self):
- items = []
+ def _update(self) -> None:
+ items = [] # type: List[Dict[str, Union[str, List[str], int, None]]]
+ if not self._metadata:
+ self.setItems(items)
+ return
for author in self._metadata:
items.append({
- "id": author["author_id"],
- "name": author["display_name"],
- "email": author["email"] if "email" in author else None,
- "website": author["website"],
- "package_count": author["package_count"] if "package_count" in author else 0,
- "package_types": author["package_types"] if "package_types" in author else [],
- "icon_url": author["icon_url"] if "icon_url" in author else None,
- "description": "Material and quality profiles from {author_name}".format(author_name = author["display_name"])
+ "id": author.get("author_id"),
+ "name": author.get("display_name"),
+ "email": author.get("email"),
+ "website": author.get("website"),
+ "package_count": author.get("package_count", 0),
+ "package_types": author.get("package_types", []),
+ "icon_url": author.get("icon_url"),
+ "description": "Material and quality profiles from {author_name}".format(author_name = author.get("display_name", ""))
})
# Filter on all the key-word arguments.
for key, value in self._filter.items():
if key is "package_types":
- key_filter = lambda item, value = value: value in item["package_types"]
+ key_filter = lambda item, value = value: value in item["package_types"] # type: ignore
elif "*" in value:
- key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value)
+ key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value) # type: ignore
else:
- key_filter = lambda item, key = key, value = value: self._matchString(item, key, value)
- items = filter(key_filter, items)
+ key_filter = lambda item, key = key, value = value: self._matchString(item, key, value) # type: ignore
+ items = filter(key_filter, items) # type: ignore
# Execute all filters.
filtered_items = list(items)
diff --git a/plugins/Toolbox/src/PackagesModel.py b/plugins/Toolbox/src/PackagesModel.py
index 8b9199b127..d94fdf6bb7 100644
--- a/plugins/Toolbox/src/PackagesModel.py
+++ b/plugins/Toolbox/src/PackagesModel.py
@@ -5,10 +5,14 @@ import re
from typing import Dict
from PyQt5.QtCore import Qt, pyqtProperty
+
+from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
+
from .ConfigsModel import ConfigsModel
-## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
+
+## Model that holds Cura packages. By setting the filter property the instances held by this model can be changed.
class PackagesModel(ListModel):
def __init__(self, parent = None):
super().__init__(parent)
@@ -29,26 +33,39 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 12, "last_updated")
self.addRoleName(Qt.UserRole + 13, "is_bundled")
self.addRoleName(Qt.UserRole + 14, "is_active")
- self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
+ self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
self.addRoleName(Qt.UserRole + 16, "has_configs")
self.addRoleName(Qt.UserRole + 17, "supported_configs")
self.addRoleName(Qt.UserRole + 18, "download_count")
self.addRoleName(Qt.UserRole + 19, "tags")
+ self.addRoleName(Qt.UserRole + 20, "links")
+ self.addRoleName(Qt.UserRole + 21, "website")
+ self.addRoleName(Qt.UserRole + 22, "login_required")
+ self.addRoleName(Qt.UserRole + 23, "average_rating")
+ self.addRoleName(Qt.UserRole + 24, "num_ratings")
+ self.addRoleName(Qt.UserRole + 25, "user_rating")
# List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str]
def setMetadata(self, data):
- self._metadata = data
- self._update()
+ if self._metadata != data:
+ self._metadata = data
+ self._update()
def _update(self):
items = []
- for package in self._metadata:
+ if self._metadata is None:
+ Logger.logException("w", "Failed to load packages for Marketplace")
+ self.setItems(items)
+ return
+ for package in self._metadata:
has_configs = False
configs_model = None
+
+ links_dict = {}
if "data" in package:
if "supported_configs" in package["data"]:
if len(package["data"]["supported_configs"]) > 0:
@@ -56,41 +73,51 @@ class PackagesModel(ListModel):
configs_model = ConfigsModel()
configs_model.setConfigs(package["data"]["supported_configs"])
+ # Links is a list of dictionaries with "title" and "url". Convert this list into a dict so it's easier
+ # to process.
+ link_list = package["data"]["links"] if "links" in package["data"] else []
+ links_dict = {d["title"]: d["url"] for d in link_list}
+
if "author_id" not in package["author"] or "display_name" not in package["author"]:
package["author"]["author_id"] = ""
package["author"]["display_name"] = ""
- # raise Exception("Detected a package with malformed author data.")
items.append({
- "id": package["package_id"],
- "type": package["package_type"],
- "name": package["display_name"],
- "version": package["package_version"],
- "author_id": package["author"]["author_id"],
- "author_name": package["author"]["display_name"],
- "author_email": package["author"]["email"] if "email" in package["author"] else None,
- "description": package["description"] if "description" in package else None,
- "icon_url": package["icon_url"] if "icon_url" in package else None,
- "image_urls": package["image_urls"] if "image_urls" in package else None,
- "download_url": package["download_url"] if "download_url" in package else None,
- "last_updated": package["last_updated"] if "last_updated" in package else None,
- "is_bundled": package["is_bundled"] if "is_bundled" in package else False,
- "is_active": package["is_active"] if "is_active" in package else False,
- "is_installed": package["is_installed"] if "is_installed" in package else False,
- "has_configs": has_configs,
- "supported_configs": configs_model,
- "download_count": package["download_count"] if "download_count" in package else 0,
- "tags": package["tags"] if "tags" in package else []
+ "id": package["package_id"],
+ "type": package["package_type"],
+ "name": package["display_name"],
+ "version": package["package_version"],
+ "author_id": package["author"]["author_id"],
+ "author_name": package["author"]["display_name"],
+ "author_email": package["author"]["email"] if "email" in package["author"] else None,
+ "description": package["description"] if "description" in package else None,
+ "icon_url": package["icon_url"] if "icon_url" in package else None,
+ "image_urls": package["image_urls"] if "image_urls" in package else None,
+ "download_url": package["download_url"] if "download_url" in package else None,
+ "last_updated": package["last_updated"] if "last_updated" in package else None,
+ "is_bundled": package["is_bundled"] if "is_bundled" in package else False,
+ "is_active": package["is_active"] if "is_active" in package else False,
+ "is_installed": package["is_installed"] if "is_installed" in package else False,
+ "has_configs": has_configs,
+ "supported_configs": configs_model,
+ "download_count": package["download_count"] if "download_count" in package else 0,
+ "tags": package["tags"] if "tags" in package else [],
+ "links": links_dict,
+ "website": package["website"] if "website" in package else None,
+ "login_required": "login-required" in package.get("tags", []),
+ "average_rating": float(package.get("rating", {}).get("average", 0)),
+ "num_ratings": package.get("rating", {}).get("count", 0),
+ "user_rating": package.get("rating", {}).get("user_rating", 0)
})
# Filter on all the key-word arguments.
for key, value in self._filter.items():
if key is "tags":
- key_filter = lambda item, value = value: value in item["tags"]
+ key_filter = lambda item, v = value: v in item["tags"]
elif "*" in value:
- key_filter = lambda candidate, key = key, value = value: self._matchRegExp(candidate, key, value)
+ key_filter = lambda candidate, k = key, v = value: self._matchRegExp(candidate, k, v)
else:
- key_filter = lambda candidate, key = key, value = value: self._matchString(candidate, key, value)
+ key_filter = lambda candidate, k = key, v = value: self._matchString(candidate, k, v)
items = filter(key_filter, items)
# Execute all filters.
diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py
index c4205b8ed5..7d8d359831 100644
--- a/plugins/Toolbox/src/Toolbox.py
+++ b/plugins/Toolbox/src/Toolbox.py
@@ -1,12 +1,11 @@
# Copyright (c) 2018 Ultimaker B.V.
# Toolbox is released under the terms of the LGPLv3 or higher.
-from typing import Dict, Optional, Union, Any, cast
import json
import os
import tempfile
import platform
-from typing import cast, List
+from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
@@ -14,110 +13,99 @@ from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkRepl
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.Extension import Extension
-from UM.Qt.ListModel import ListModel
from UM.i18n import i18nCatalog
from UM.Version import Version
-import cura
+from cura import ApplicationMetadata
+from cura import UltimakerCloudAuthentication
from cura.CuraApplication import CuraApplication
+
from .AuthorsModel import AuthorsModel
from .PackagesModel import PackagesModel
+if TYPE_CHECKING:
+ from cura.Settings.GlobalStack import GlobalStack
+
i18n_catalog = i18nCatalog("cura")
## The Toolbox class is responsible of communicating with the server through the API
class Toolbox(QObject, Extension):
- DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" #type: str
- DEFAULT_CLOUD_API_VERSION = 1 #type: int
-
def __init__(self, application: CuraApplication) -> None:
super().__init__()
- self._application = application #type: CuraApplication
+ self._application = application # type: CuraApplication
- self._sdk_version = None # type: Optional[int]
- self._cloud_api_version = None # type: Optional[int]
- self._cloud_api_root = None # type: Optional[str]
- self._api_url = None # type: Optional[str]
+ self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int]
+ self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: str
+ self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str
+ self._api_url = None # type: Optional[str]
# Network:
- self._download_request = None #type: Optional[QNetworkRequest]
- self._download_reply = None #type: Optional[QNetworkReply]
- self._download_progress = 0 #type: float
- self._is_downloading = False #type: bool
- self._network_manager = None #type: Optional[QNetworkAccessManager]
- self._request_header = [
- b"User-Agent",
- str.encode(
- "%s/%s (%s %s)" % (
- self._application.getApplicationName(),
- self._application.getVersion(),
- platform.system(),
- platform.machine(),
- )
- )
- ]
- self._request_urls = {} # type: Dict[str, QUrl]
- self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
- self._old_plugin_ids = [] # type: List[str]
+ self._download_request = None # type: Optional[QNetworkRequest]
+ self._download_reply = None # type: Optional[QNetworkReply]
+ self._download_progress = 0 # type: float
+ self._is_downloading = False # type: bool
+ self._network_manager = None # type: Optional[QNetworkAccessManager]
+ self._request_headers = [] # type: List[Tuple[bytes, bytes]]
+ self._updateRequestHeader()
- # Data:
- self._metadata = {
+
+ self._request_urls = {} # type: Dict[str, QUrl]
+ self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
+ self._old_plugin_ids = set() # type: Set[str]
+ self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
+
+ # The responses as given by the server parsed to a list.
+ self._server_response_data = {
"authors": [],
- "packages": [],
- "plugins_showcase": [],
- "plugins_available": [],
- "plugins_installed": [],
- "materials_showcase": [],
- "materials_available": [],
- "materials_installed": [],
- "materials_generic": []
- } # type: Dict[str, List[Any]]
+ "packages": []
+ } # type: Dict[str, List[Any]]
# Models:
self._models = {
"authors": AuthorsModel(self),
"packages": PackagesModel(self),
- "plugins_showcase": PackagesModel(self),
- "plugins_available": PackagesModel(self),
- "plugins_installed": PackagesModel(self),
- "materials_showcase": AuthorsModel(self),
- "materials_available": PackagesModel(self),
- "materials_installed": PackagesModel(self),
- "materials_generic": PackagesModel(self)
- } # type: Dict[str, ListModel]
+ } # type: Dict[str, Union[AuthorsModel, PackagesModel]]
+
+ self._plugins_showcase_model = PackagesModel(self)
+ self._plugins_available_model = PackagesModel(self)
+ self._plugins_installed_model = PackagesModel(self)
+
+ self._materials_showcase_model = AuthorsModel(self)
+ self._materials_available_model = AuthorsModel(self)
+ self._materials_installed_model = PackagesModel(self)
+ self._materials_generic_model = PackagesModel(self)
# These properties are for keeping track of the UI state:
# ----------------------------------------------------------------------
# View category defines which filter to use, and therefore effectively
# which category is currently being displayed. For example, possible
# values include "plugin" or "material", but also "installed".
- self._view_category = "plugin" #type: str
+ self._view_category = "plugin" # type: str
# View page defines which type of page layout to use. For example,
# possible values include "overview", "detail" or "author".
- self._view_page = "loading" #type: str
+ self._view_page = "loading" # type: str
# Active package refers to which package is currently being downloaded,
# installed, or otherwise modified.
- self._active_package = None # type: Optional[Dict[str, Any]]
+ self._active_package = None # type: Optional[Dict[str, Any]]
- self._dialog = None #type: Optional[QObject]
- self._confirm_reset_dialog = None #type: Optional[QObject]
+ self._dialog = None # type: Optional[QObject]
+ self._confirm_reset_dialog = None # type: Optional[QObject]
self._resetUninstallVariables()
- self._restart_required = False #type: bool
+ self._restart_required = False # type: bool
# variables for the license agreement dialog
- self._license_dialog_plugin_name = "" #type: str
- self._license_dialog_license_content = "" #type: str
- self._license_dialog_plugin_file_location = "" #type: str
- self._restart_dialog_message = "" #type: str
+ self._license_dialog_plugin_name = "" # type: str
+ self._license_dialog_license_content = "" # type: str
+ self._license_dialog_plugin_file_location = "" # type: str
+ self._restart_dialog_message = "" # type: str
self._application.initializationFinished.connect(self._onAppInitialized)
-
-
+ self._application.getCuraAPI().account.loginStateChanged.connect(self._updateRequestHeader)
# Signals:
# --------------------------------------------------------------------------
@@ -137,11 +125,37 @@ class Toolbox(QObject, Extension):
showLicenseDialog = pyqtSignal()
uninstallVariablesChanged = pyqtSignal()
- def _resetUninstallVariables(self):
- self._package_id_to_uninstall = None
+ def _updateRequestHeader(self):
+ self._request_headers = [
+ (b"User-Agent",
+ str.encode(
+ "%s/%s (%s %s)" % (
+ self._application.getApplicationName(),
+ self._application.getVersion(),
+ platform.system(),
+ platform.machine(),
+ )
+ ))
+ ]
+ access_token = self._application.getCuraAPI().account.accessToken
+ if access_token:
+ self._request_headers.append((b"Authorization", "Bearer {}".format(access_token).encode()))
+
+ def _resetUninstallVariables(self) -> None:
+ self._package_id_to_uninstall = None # type: Optional[str]
self._package_name_to_uninstall = ""
- self._package_used_materials = []
- self._package_used_qualities = []
+ self._package_used_materials = [] # type: List[Tuple[GlobalStack, str, str]]
+ self._package_used_qualities = [] # type: List[Tuple[GlobalStack, str, str]]
+
+ @pyqtSlot(str, int)
+ def ratePackage(self, package_id: str, rating: int) -> None:
+ url = QUrl("{base_url}/packages/{package_id}/ratings".format(base_url=self._api_url, package_id = package_id))
+
+ self._rate_request = QNetworkRequest(url)
+ for header_name, header_value in self._request_headers:
+ cast(QNetworkRequest, self._rate_request).setRawHeader(header_name, header_value)
+ data = "{\"data\": {\"cura_version\": \"%s\", \"rating\": %i}}" % (Version(self._application.getVersion()), rating)
+ self._rate_reply = cast(QNetworkAccessManager, self._network_manager).put(self._rate_request, data.encode())
@pyqtSlot(result = str)
def getLicenseDialogPluginName(self) -> str:
@@ -166,54 +180,16 @@ class Toolbox(QObject, Extension):
def _onAppInitialized(self) -> None:
self._plugin_registry = self._application.getPluginRegistry()
self._package_manager = self._application.getPackageManager()
- self._sdk_version = self._getSDKVersion()
- self._cloud_api_version = self._getCloudAPIVersion()
- self._cloud_api_root = self._getCloudAPIRoot()
self._api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format(
- cloud_api_root=self._cloud_api_root,
- cloud_api_version=self._cloud_api_version,
- sdk_version=self._sdk_version
+ cloud_api_root = self._cloud_api_root,
+ cloud_api_version = self._cloud_api_version,
+ sdk_version = self._sdk_version
)
self._request_urls = {
- "authors": QUrl("{base_url}/authors".format(base_url=self._api_url)),
- "packages": QUrl("{base_url}/packages".format(base_url=self._api_url)),
- "plugins_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)),
- "plugins_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url=self._api_url)),
- "materials_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)),
- "materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url=self._api_url)),
- "materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".format(base_url=self._api_url))
+ "authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
+ "packages": QUrl("{base_url}/packages".format(base_url = self._api_url))
}
- # Get the API root for the packages API depending on Cura version settings.
- def _getCloudAPIRoot(self) -> str:
- if not hasattr(cura, "CuraVersion"):
- return self.DEFAULT_CLOUD_API_ROOT
- if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"): # type: ignore
- return self.DEFAULT_CLOUD_API_ROOT
- if not cura.CuraVersion.CuraCloudAPIRoot: # type: ignore
- return self.DEFAULT_CLOUD_API_ROOT
- return cura.CuraVersion.CuraCloudAPIRoot # type: ignore
-
- # Get the cloud API version from CuraVersion
- def _getCloudAPIVersion(self) -> int:
- if not hasattr(cura, "CuraVersion"):
- return self.DEFAULT_CLOUD_API_VERSION
- if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"): # type: ignore
- return self.DEFAULT_CLOUD_API_VERSION
- if not cura.CuraVersion.CuraCloudAPIVersion: # type: ignore
- return self.DEFAULT_CLOUD_API_VERSION
- return cura.CuraVersion.CuraCloudAPIVersion # type: ignore
-
- # Get the packages version depending on Cura version settings.
- def _getSDKVersion(self) -> int:
- if not hasattr(cura, "CuraVersion"):
- return self._plugin_registry.APIVersion
- if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
- return self._plugin_registry.APIVersion
- if not cura.CuraVersion.CuraSDKVersion: # type: ignore
- return self._plugin_registry.APIVersion
- return cura.CuraVersion.CuraSDKVersion # type: ignore
-
@pyqtSlot()
def browsePackages(self) -> None:
# Create the network manager:
@@ -229,10 +205,6 @@ class Toolbox(QObject, Extension):
# Make remote requests:
self._makeRequestByType("packages")
self._makeRequestByType("authors")
- self._makeRequestByType("plugins_showcase")
- self._makeRequestByType("materials_showcase")
- self._makeRequestByType("materials_available")
- self._makeRequestByType("materials_generic")
# Gather installed packages:
self._updateInstalledModels()
@@ -241,7 +213,7 @@ class Toolbox(QObject, Extension):
self._dialog = self._createDialog("Toolbox.qml")
if not self._dialog:
- Logger.log("e", "Unexpected error trying to create the 'Toolbox' dialog.")
+ Logger.log("e", "Unexpected error trying to create the 'Marketplace' dialog.")
return
self._dialog.show()
@@ -250,7 +222,7 @@ class Toolbox(QObject, Extension):
self.enabledChanged.emit()
def _createDialog(self, qml_name: str) -> Optional[QObject]:
- Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name)
+ Logger.log("d", "Marketplace: Creating dialog [%s].", qml_name)
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if not plugin_path:
return None
@@ -258,24 +230,33 @@ class Toolbox(QObject, Extension):
dialog = self._application.createQmlComponent(path, {"toolbox": self})
if not dialog:
- raise Exception("Failed to create toolbox dialog")
+ raise Exception("Failed to create Marketplace dialog")
return dialog
- def _convertPluginMetadata(self, plugin: Dict[str, Any]) -> Dict[str, Any]:
- formatted = {
- "package_id": plugin["id"],
- "package_type": "plugin",
- "display_name": plugin["plugin"]["name"],
- "package_version": plugin["plugin"]["version"],
- "sdk_version": plugin["plugin"]["api"],
- "author": {
- "author_id": plugin["plugin"]["author"],
- "display_name": plugin["plugin"]["author"]
- },
- "is_installed": True,
- "description": plugin["plugin"]["description"]
- }
- return formatted
+ def _convertPluginMetadata(self, plugin_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
+ try:
+ highest_sdk_version_supported = Version(0)
+ for supported_version in plugin_data["plugin"]["supported_sdk_versions"]:
+ if supported_version > highest_sdk_version_supported:
+ highest_sdk_version_supported = supported_version
+
+ formatted = {
+ "package_id": plugin_data["id"],
+ "package_type": "plugin",
+ "display_name": plugin_data["plugin"]["name"],
+ "package_version": plugin_data["plugin"]["version"],
+ "sdk_version": highest_sdk_version_supported,
+ "author": {
+ "author_id": plugin_data["plugin"]["author"],
+ "display_name": plugin_data["plugin"]["author"]
+ },
+ "is_installed": True,
+ "description": plugin_data["plugin"]["description"]
+ }
+ return formatted
+ except KeyError:
+ Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data))
+ return None
@pyqtSlot()
def _updateInstalledModels(self) -> None:
@@ -285,29 +266,36 @@ class Toolbox(QObject, Extension):
installed_package_ids = self._package_manager.getAllInstalledPackageIDs()
scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs()
- self._old_plugin_ids = []
- self._old_plugin_metadata = [] # type: List[Dict[str, Any]]
+ self._old_plugin_ids = set()
+ self._old_plugin_metadata = dict()
for plugin_id in old_plugin_ids:
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
if plugin_id not in installed_package_ids and plugin_id not in scheduled_to_remove_package_ids:
- Logger.log('i', 'Found a plugin that was installed with the old plugin browser: %s', plugin_id)
+ Logger.log("i", "Found a plugin that was installed with the old plugin browser: %s", plugin_id)
old_metadata = self._plugin_registry.getMetaData(plugin_id)
new_metadata = self._convertPluginMetadata(old_metadata)
-
- self._old_plugin_ids.append(plugin_id)
- self._old_plugin_metadata.append(new_metadata)
+ if new_metadata is None:
+ # Something went wrong converting it.
+ continue
+ self._old_plugin_ids.add(plugin_id)
+ self._old_plugin_metadata[new_metadata["package_id"]] = new_metadata
all_packages = self._package_manager.getAllInstalledPackagesInfo()
if "plugin" in all_packages:
- self._metadata["plugins_installed"] = all_packages["plugin"] + self._old_plugin_metadata
- self._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
+ # For old plugins, we only want to include the old custom plugin that were installed via the old toolbox.
+ # The bundled plugins will be included in JSON files in the "bundled_packages" folder, so the bundled
+ # plugins should be excluded from the old plugins list/dict.
+ all_plugin_package_ids = set(package["package_id"] for package in all_packages["plugin"])
+ self._old_plugin_ids = set(plugin_id for plugin_id in self._old_plugin_ids
+ if plugin_id not in all_plugin_package_ids)
+ self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids}
+
+ self._plugins_installed_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values()))
self.metadataChanged.emit()
if "material" in all_packages:
- self._metadata["materials_installed"] = all_packages["material"]
- # TODO: ADD MATERIALS HERE ONCE MATERIALS PORTION OF TOOLBOX IS LIVE
- self._models["materials_installed"].setMetadata(self._metadata["materials_installed"])
+ self._materials_installed_model.setMetadata(all_packages["material"])
self.metadataChanged.emit()
@pyqtSlot(str)
@@ -344,26 +332,26 @@ class Toolbox(QObject, Extension):
self.uninstall(package_id)
@pyqtProperty(str, notify = uninstallVariablesChanged)
- def pluginToUninstall(self):
+ def pluginToUninstall(self) -> str:
return self._package_name_to_uninstall
@pyqtProperty(str, notify = uninstallVariablesChanged)
- def uninstallUsedMaterials(self):
+ def uninstallUsedMaterials(self) -> str:
return "\n".join(["%s (%s)" % (str(global_stack.getName()), material) for global_stack, extruder_nr, material in self._package_used_materials])
@pyqtProperty(str, notify = uninstallVariablesChanged)
- def uninstallUsedQualities(self):
+ def uninstallUsedQualities(self) -> str:
return "\n".join(["%s (%s)" % (str(global_stack.getName()), quality) for global_stack, extruder_nr, quality in self._package_used_qualities])
@pyqtSlot()
- def closeConfirmResetDialog(self):
+ def closeConfirmResetDialog(self) -> None:
if self._confirm_reset_dialog is not None:
self._confirm_reset_dialog.close()
## Uses "uninstall variables" to reset qualities and materials, then uninstall
# It's used as an action on Confirm reset on Uninstall
@pyqtSlot()
- def resetMaterialsQualitiesAndUninstall(self):
+ def resetMaterialsQualitiesAndUninstall(self) -> None:
application = CuraApplication.getInstance()
material_manager = application.getMaterialManager()
quality_manager = application.getQualityManager()
@@ -376,9 +364,9 @@ class Toolbox(QObject, Extension):
default_quality_group = quality_manager.getDefaultQualityType(global_stack)
machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack)
- self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
-
- self.uninstall(self._package_id_to_uninstall)
+ if self._package_id_to_uninstall is not None:
+ self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
+ self.uninstall(self._package_id_to_uninstall)
self._resetUninstallVariables()
self.closeConfirmResetDialog()
@@ -461,7 +449,7 @@ class Toolbox(QObject, Extension):
def getRemotePackage(self, package_id: str) -> Optional[Dict]:
# TODO: make the lookup in a dict, not a loop. canUpdate is called for every item.
remote_package = None
- for package in self._metadata["packages"]:
+ for package in self._server_response_data["packages"]:
if package["package_id"] == package_id:
remote_package = package
break
@@ -471,12 +459,11 @@ class Toolbox(QObject, Extension):
# --------------------------------------------------------------------------
@pyqtSlot(str, result = bool)
def canUpdate(self, package_id: str) -> bool:
- if self.isOldPlugin(package_id):
- return True
-
local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None:
- return False
+ local_package = self.getOldPluginPackageMetadata(package_id)
+ if local_package is None:
+ return False
remote_package = self.getRemotePackage(package_id)
if remote_package is None:
@@ -484,7 +471,19 @@ class Toolbox(QObject, Extension):
local_version = Version(local_package["package_version"])
remote_version = Version(remote_package["package_version"])
- return remote_version > local_version
+ can_upgrade = False
+ if remote_version > local_version:
+ can_upgrade = True
+ # A package with the same version can be built to have different SDK versions. So, for a package with the same
+ # version, we also need to check if the current one has a lower SDK version. If so, this package should also
+ # be upgradable.
+ elif remote_version == local_version:
+ # First read sdk_version_semver. If that doesn't exist, read just sdk_version (old version system).
+ remote_sdk_version = Version(remote_package.get("sdk_version_semver", remote_package.get("sdk_version", 0)))
+ local_sdk_version = Version(local_package.get("sdk_version_semver", local_package.get("sdk_version", 0)))
+ can_upgrade = local_sdk_version < remote_sdk_version
+
+ return can_upgrade
@pyqtSlot(str, result = bool)
def canDowngrade(self, package_id: str) -> bool:
@@ -504,22 +503,28 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, result = bool)
def isInstalled(self, package_id: str) -> bool:
- return self._package_manager.isPackageInstalled(package_id)
+ result = self._package_manager.isPackageInstalled(package_id)
+ # Also check the old plugins list if it's not found in the package manager.
+ if not result:
+ result = self.isOldPlugin(package_id)
+ return result
@pyqtSlot(str, result = int)
def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
count = 0
- for package in self._metadata["materials_installed"]:
- if package["author"]["author_id"] == author_id:
+ for package in self._materials_installed_model.items:
+ if package["author_id"] == author_id:
count += 1
return count
+ # This slot is only used to get the number of material packages by author, not any other type of packages.
@pyqtSlot(str, result = int)
- def getTotalNumberOfPackagesByAuthor(self, author_id: str) -> int:
+ def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
count = 0
- for package in self._metadata["materials_available"]:
- if package["author"]["author_id"] == author_id:
- count += 1
+ for package in self._server_response_data["packages"]:
+ if package["package_type"] == "material":
+ if package["author"]["author_id"] == author_id:
+ count += 1
return count
@pyqtSlot(str, result = bool)
@@ -529,33 +534,32 @@ class Toolbox(QObject, Extension):
return False
# Check for plugins that were installed with the old plugin browser
- @pyqtSlot(str, result = bool)
def isOldPlugin(self, plugin_id: str) -> bool:
- if plugin_id in self._old_plugin_ids:
- return True
- return False
+ return plugin_id in self._old_plugin_ids
- def loadingComplete(self) -> bool:
+ def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
+ return self._old_plugin_metadata.get(plugin_id)
+
+ def isLoadingComplete(self) -> bool:
populated = 0
- for list in self._metadata.items():
- if len(list) > 0:
+ for metadata_list in self._server_response_data.items():
+ if metadata_list:
populated += 1
- if populated == len(self._metadata.items()):
- return True
- return False
+ return populated == len(self._server_response_data.items())
# Make API Calls
# --------------------------------------------------------------------------
- def _makeRequestByType(self, type: str) -> None:
- Logger.log("i", "Toolbox: Requesting %s metadata from server.", type)
- request = QNetworkRequest(self._request_urls[type])
- request.setRawHeader(*self._request_header)
+ def _makeRequestByType(self, request_type: str) -> None:
+ Logger.log("i", "Requesting %s metadata from server.", request_type)
+ request = QNetworkRequest(self._request_urls[request_type])
+ for header_name, header_value in self._request_headers:
+ request.setRawHeader(header_name, header_value)
if self._network_manager:
self._network_manager.get(request)
@pyqtSlot(str)
def startDownload(self, url: str) -> None:
- Logger.log("i", "Toolbox: Attempting to download & install package from %s.", url)
+ Logger.log("i", "Attempting to download & install package from %s.", url)
url = QUrl(url)
self._download_request = QNetworkRequest(url)
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
@@ -564,7 +568,8 @@ class Toolbox(QObject, Extension):
if hasattr(QNetworkRequest, "RedirectPolicyAttribute"):
# Patch for Qt 5.9+
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)
- cast(QNetworkRequest, self._download_request).setRawHeader(*self._request_header)
+ for header_name, header_value in self._request_headers:
+ cast(QNetworkRequest, self._download_request).setRawHeader(header_name, header_value)
self._download_reply = cast(QNetworkAccessManager, self._network_manager).get(self._download_request)
self.setDownloadProgress(0)
self.setIsDownloading(True)
@@ -572,15 +577,15 @@ class Toolbox(QObject, Extension):
@pyqtSlot()
def cancelDownload(self) -> None:
- Logger.log("i", "Toolbox: User cancelled the download of a plugin.")
+ Logger.log("i", "User cancelled the download of a package.")
self.resetDownload()
def resetDownload(self) -> None:
if self._download_reply:
try:
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
- except TypeError: #Raised when the method is not connected to the signal yet.
- pass #Don't need to disconnect.
+ except TypeError: # Raised when the method is not connected to the signal yet.
+ pass # Don't need to disconnect.
self._download_reply.abort()
self._download_reply = None
self._download_request = None
@@ -607,7 +612,7 @@ class Toolbox(QObject, Extension):
return
if reply.operation() == QNetworkAccessManager.GetOperation:
- for type, url in self._request_urls.items():
+ for response_type, url in self._request_urls.items():
if reply.url() == url:
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
try:
@@ -620,48 +625,34 @@ class Toolbox(QObject, Extension):
return
# Create model and apply metadata:
- if not self._models[type]:
- Logger.log("e", "Could not find the %s model.", type)
+ if not self._models[response_type]:
+ Logger.log("e", "Could not find the %s model.", response_type)
break
+
+ self._server_response_data[response_type] = json_data["data"]
+ self._models[response_type].setMetadata(self._server_response_data[response_type])
- # HACK: Eventually get rid of the code from here...
- if type is "plugins_showcase" or type is "materials_showcase":
- self._metadata["plugins_showcase"] = json_data["data"]["plugin"]["packages"]
- self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
- self._metadata["materials_showcase"] = json_data["data"]["material"]["authors"]
- self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
- else:
- # ...until here.
- # This hack arises for multiple reasons but the main
- # one is because there are not separate API calls
- # for different kinds of showcases.
- self._metadata[type] = json_data["data"]
- self._models[type].setMetadata(self._metadata[type])
-
- # Do some auto filtering
- # TODO: Make multiple API calls in the future to handle this
- if type is "packages":
- self._models[type].setFilter({"type": "plugin"})
- if type is "authors":
- self._models[type].setFilter({"package_types": "material"})
- if type is "materials_generic":
- self._models[type].setFilter({"tags": "generic"})
+ if response_type is "packages":
+ self._models[response_type].setFilter({"type": "plugin"})
+ self.reBuildMaterialsModels()
+ self.reBuildPluginsModels()
+ elif response_type is "authors":
+ self._models[response_type].setFilter({"package_types": "material"})
+ self._models[response_type].setFilter({"tags": "generic"})
self.metadataChanged.emit()
- if self.loadingComplete() is True:
+ if self.isLoadingComplete():
self.setViewPage("overview")
- return
except json.decoder.JSONDecodeError:
- Logger.log("w", "Toolbox: Received invalid JSON for %s.", type)
+ Logger.log("w", "Received invalid JSON for %s.", response_type)
break
else:
+ Logger.log("w", "Unable to connect with the server, we got a response code %s while trying to connect to %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
self.setViewPage("errored")
self.resetDownload()
- return
-
- else:
+ elif reply.operation() == QNetworkAccessManager.PutOperation:
# Ignore any operation that is not a get operation
pass
@@ -671,7 +662,13 @@ class Toolbox(QObject, Extension):
self.setDownloadProgress(new_progress)
if bytes_sent == bytes_total:
self.setIsDownloading(False)
- cast(QNetworkReply, self._download_reply).downloadProgress.disconnect(self._onDownloadProgress)
+ self._download_reply = cast(QNetworkReply, self._download_reply)
+ self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
+
+ # Check if the download was sucessfull
+ if self._download_reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
+ Logger.log("w", "Failed to download package. The following error was returned: %s", json.loads(bytes(self._download_reply.readAll()).decode("utf-8")))
+ return
# Must not delete the temporary file on Windows
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
file_path = self._temp_plugin_file.name
@@ -680,11 +677,11 @@ class Toolbox(QObject, Extension):
self._temp_plugin_file.close()
self._onDownloadComplete(file_path)
- def _onDownloadComplete(self, file_path: str):
- Logger.log("i", "Toolbox: Download complete.")
+ def _onDownloadComplete(self, file_path: str) -> None:
+ Logger.log("i", "Download complete.")
package_info = self._package_manager.getPackageInfo(file_path)
if not package_info:
- Logger.log("w", "Toolbox: Package file [%s] was not a valid CuraPackage.", file_path)
+ Logger.log("w", "Package file [%s] was not a valid CuraPackage.", file_path)
return
license_content = self._package_manager.getPackageLicense(file_path)
@@ -693,7 +690,6 @@ class Toolbox(QObject, Extension):
return
self.install(file_path)
- return
# Getter & Setters for Properties:
# --------------------------------------------------------------------------
@@ -716,69 +712,77 @@ class Toolbox(QObject, Extension):
return self._is_downloading
def setActivePackage(self, package: Dict[str, Any]) -> None:
- self._active_package = package
- self.activePackageChanged.emit()
+ if self._active_package != package:
+ self._active_package = package
+ self.activePackageChanged.emit()
+ ## The active package is the package that is currently being downloaded
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
def activePackage(self) -> Optional[Dict[str, Any]]:
return self._active_package
def setViewCategory(self, category: str = "plugin") -> None:
- self._view_category = category
- self.viewChanged.emit()
+ if self._view_category != category:
+ self._view_category = category
+ self.viewChanged.emit()
@pyqtProperty(str, fset = setViewCategory, notify = viewChanged)
def viewCategory(self) -> str:
return self._view_category
def setViewPage(self, page: str = "overview") -> None:
- self._view_page = page
- self.viewChanged.emit()
+ if self._view_page != page:
+ self._view_page = page
+ self.viewChanged.emit()
@pyqtProperty(str, fset = setViewPage, notify = viewChanged)
def viewPage(self) -> str:
return self._view_page
-
-
- # Expose Models:
+ # Exposed Models:
# --------------------------------------------------------------------------
- @pyqtProperty(QObject, notify = metadataChanged)
+ @pyqtProperty(QObject, constant=True)
def authorsModel(self) -> AuthorsModel:
return cast(AuthorsModel, self._models["authors"])
- @pyqtProperty(QObject, notify = metadataChanged)
+ @pyqtProperty(QObject, constant=True)
def packagesModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["packages"])
- @pyqtProperty(QObject, notify = metadataChanged)
+ @pyqtProperty(QObject, constant=True)
def pluginsShowcaseModel(self) -> PackagesModel:
- return cast(PackagesModel, self._models["plugins_showcase"])
+ return self._plugins_showcase_model
- @pyqtProperty(QObject, notify = metadataChanged)
+ @pyqtProperty(QObject, constant=True)
+ def pluginsAvailableModel(self) -> PackagesModel:
+ return self._plugins_available_model
+
+ @pyqtProperty(QObject, constant=True)
def pluginsInstalledModel(self) -> PackagesModel:
- return cast(PackagesModel, self._models["plugins_installed"])
+ return self._plugins_installed_model
- @pyqtProperty(QObject, notify = metadataChanged)
+ @pyqtProperty(QObject, constant=True)
def materialsShowcaseModel(self) -> AuthorsModel:
- return cast(AuthorsModel, self._models["materials_showcase"])
+ return self._materials_showcase_model
- @pyqtProperty(QObject, notify = metadataChanged)
+ @pyqtProperty(QObject, constant=True)
+ def materialsAvailableModel(self) -> AuthorsModel:
+ return self._materials_available_model
+
+ @pyqtProperty(QObject, constant=True)
def materialsInstalledModel(self) -> PackagesModel:
- return cast(PackagesModel, self._models["materials_installed"])
+ return self._materials_installed_model
- @pyqtProperty(QObject, notify=metadataChanged)
+ @pyqtProperty(QObject, constant=True)
def materialsGenericModel(self) -> PackagesModel:
- return cast(PackagesModel, self._models["materials_generic"])
-
-
+ return self._materials_generic_model
# Filter Models:
# --------------------------------------------------------------------------
@pyqtSlot(str, str, str)
def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
if not self._models[model_type]:
- Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", model_type)
+ Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter({filter_type: parameter})
self.filterChanged.emit()
@@ -786,7 +790,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, "QVariantMap")
def setFilters(self, model_type: str, filter_dict: dict) -> None:
if not self._models[model_type]:
- Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", model_type)
+ Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter(filter_dict)
self.filterChanged.emit()
@@ -794,7 +798,52 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str)
def removeFilters(self, model_type: str) -> None:
if not self._models[model_type]:
- Logger.log("w", "Toolbox: Couldn't remove filters on %s model because it doesn't exist.", model_type)
+ Logger.log("w", "Couldn't remove filters on %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter({})
self.filterChanged.emit()
+
+ # HACK(S):
+ # --------------------------------------------------------------------------
+ def reBuildMaterialsModels(self) -> None:
+ materials_showcase_metadata = []
+ materials_available_metadata = []
+ materials_generic_metadata = []
+
+ processed_authors = [] # type: List[str]
+
+ for item in self._server_response_data["packages"]:
+ if item["package_type"] == "material":
+
+ author = item["author"]
+ if author["author_id"] in processed_authors:
+ continue
+
+ # Generic materials to be in the same section
+ if "generic" in item["tags"]:
+ materials_generic_metadata.append(item)
+ else:
+ if "showcase" in item["tags"]:
+ materials_showcase_metadata.append(author)
+ else:
+ materials_available_metadata.append(author)
+
+ processed_authors.append(author["author_id"])
+
+ self._materials_showcase_model.setMetadata(materials_showcase_metadata)
+ self._materials_available_model.setMetadata(materials_available_metadata)
+ self._materials_generic_model.setMetadata(materials_generic_metadata)
+
+ def reBuildPluginsModels(self) -> None:
+ plugins_showcase_metadata = []
+ plugins_available_metadata = []
+
+ for item in self._server_response_data["packages"]:
+ if item["package_type"] == "plugin":
+ if "showcase" in item["tags"]:
+ plugins_showcase_metadata.append(item)
+ else:
+ plugins_available_metadata.append(item)
+
+ self._plugins_showcase_model.setMetadata(plugins_showcase_metadata)
+ self._plugins_available_model.setMetadata(plugins_available_metadata)
diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py
index 9344bf54da..c0db104c82 100644
--- a/plugins/UFPWriter/UFPWriter.py
+++ b/plugins/UFPWriter/UFPWriter.py
@@ -1,5 +1,6 @@
#Copyright (c) 2018 Ultimaker B.V.
#Cura is released under the terms of the LGPLv3 or higher.
+
from typing import cast
from Charon.VirtualFile import VirtualFile #To open UFP files.
@@ -9,10 +10,12 @@ from io import StringIO #For converting g-code to bytes.
from UM.Application import Application
from UM.Logger import Logger
from UM.Mesh.MeshWriter import MeshWriter #The writer we need to implement.
+from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from UM.PluginRegistry import PluginRegistry #To get the g-code writer.
from PyQt5.QtCore import QBuffer
from cura.Snapshot import Snapshot
+from cura.Utils.Threading import call_on_qt_thread
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@@ -20,15 +23,28 @@ catalog = i18nCatalog("cura")
class UFPWriter(MeshWriter):
def __init__(self):
- super().__init__()
+ super().__init__(add_to_recent_files = False)
+
+ MimeTypeDatabase.addMimeType(
+ MimeType(
+ name = "application/x-ufp",
+ comment = "Cura UFP File",
+ suffixes = ["ufp"]
+ )
+ )
+
self._snapshot = None
- Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._createSnapshot)
def _createSnapshot(self, *args):
# must be called from the main thread because of OpenGL
Logger.log("d", "Creating thumbnail image...")
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
+ # This needs to be called on the main thread (Qt thread) because the serialization of material containers can
+ # trigger loading other containers. Because those loaded containers are QtObjects, they must be created on the
+ # Qt thread. The File read/write operations right now are executed on separated threads because they are scheduled
+ # by the Job class.
+ @call_on_qt_thread
def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
archive = VirtualFile()
archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
@@ -45,6 +61,8 @@ class UFPWriter(MeshWriter):
gcode.write(gcode_textio.getvalue().encode("UTF-8"))
archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
+ self._createSnapshot()
+
#Store the thumbnail.
if self._snapshot:
archive.addContentType(extension = "png", mime_type = "image/png")
@@ -60,5 +78,54 @@ class UFPWriter(MeshWriter):
else:
Logger.log("d", "Thumbnail not created, cannot save it")
+ # Store the material.
+ application = Application.getInstance()
+ machine_manager = application.getMachineManager()
+ material_manager = application.getMaterialManager()
+ global_stack = machine_manager.activeMachine
+
+ material_extension = "xml.fdm_material"
+ material_mime_type = "application/x-ultimaker-material-profile"
+
+ try:
+ archive.addContentType(extension = material_extension, mime_type = material_mime_type)
+ except:
+ Logger.log("w", "The material extension: %s was already added", material_extension)
+
+ added_materials = []
+ for extruder_stack in global_stack.extruders.values():
+ material = extruder_stack.material
+ try:
+ material_file_name = material.getMetaData()["base_file"] + ".xml.fdm_material"
+ except KeyError:
+ Logger.log("w", "Unable to get base_file for the material %s", material.getId())
+ continue
+ material_file_name = "/Materials/" + material_file_name
+
+ # The same material should not be added again.
+ if material_file_name in added_materials:
+ continue
+
+ material_root_id = material.getMetaDataEntry("base_file")
+ material_group = material_manager.getMaterialGroup(material_root_id)
+ if material_group is None:
+ Logger.log("e", "Cannot find material container with root id [%s]", material_root_id)
+ return False
+
+ material_container = material_group.root_material_node.getContainer()
+ try:
+ serialized_material = material_container.serialize()
+ except NotImplementedError:
+ Logger.log("e", "Unable serialize material container with root id: %s", material_root_id)
+ return False
+
+ material_file = archive.getStream(material_file_name)
+ material_file.write(serialized_material.encode("UTF-8"))
+ archive.addRelation(virtual_path = material_file_name,
+ relation_type = "http://schemas.ultimaker.org/package/2018/relationships/material",
+ origin = "/3D/model.gcode")
+
+ added_materials.append(material_file_name)
+
archive.close()
return True
diff --git a/plugins/UFPWriter/__init__.py b/plugins/UFPWriter/__init__.py
index a2ec99044f..9db6b042f8 100644
--- a/plugins/UFPWriter/__init__.py
+++ b/plugins/UFPWriter/__init__.py
@@ -11,16 +11,6 @@ except ImportError:
from UM.i18n import i18nCatalog #To translate the file format description.
from UM.Mesh.MeshWriter import MeshWriter #For the binary mode flag.
-from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
-
-
-MimeTypeDatabase.addMimeType(
- MimeType(
- name = "application/x-cura-stl-file",
- comment = "Cura UFP File",
- suffixes = ["ufp"]
- )
-)
i18n_catalog = i18nCatalog("cura")
diff --git a/plugins/UFPWriter/plugin.json b/plugins/UFPWriter/plugin.json
index 7d10b89ad4..288d6acf77 100644
--- a/plugins/UFPWriter/plugin.json
+++ b/plugins/UFPWriter/plugin.json
@@ -1,8 +1,8 @@
{
"name": "UFP Writer",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides support for writing Ultimaker Format Packages.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/ClusterControlItem.qml b/plugins/UM3NetworkPrinting/ClusterControlItem.qml
deleted file mode 100644
index 5cf550955c..0000000000
--- a/plugins/UM3NetworkPrinting/ClusterControlItem.qml
+++ /dev/null
@@ -1,241 +0,0 @@
-import QtQuick 2.2
-import QtQuick.Controls 1.4
-
-import UM 1.3 as UM
-import Cura 1.0 as Cura
-
-Component
-{
- Rectangle
- {
- id: base
- property var manager: Cura.MachineManager.printerOutputDevices[0]
- property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
- property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
-
- visible: manager != null
- anchors.fill: parent
- color: UM.Theme.getColor("viewport_background")
-
- UM.I18nCatalog
- {
- id: catalog
- name: "cura"
- }
-
- Label
- {
- id: activePrintersLabel
- font: UM.Theme.getFont("large")
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right:parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- text: Cura.MachineManager.printerOutputDevices[0].name
- elide: Text.ElideRight
- }
-
- Rectangle
- {
- id: printJobArea
- border.width: UM.Theme.getSize("default_lining").width
- border.color: lineColor
- anchors.top: activePrintersLabel.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin:UM.Theme.getSize("default_margin").width
- radius: cornerRadius
- height: childrenRect.height
-
- Item
- {
- id: printJobTitleBar
- width: parent.width
- height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
-
- Label
- {
- id: printJobTitleLabel
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.top: parent.top
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- text: catalog.i18nc("@title", "Print jobs")
- font: UM.Theme.getFont("default")
- opacity: 0.75
- }
- Rectangle
- {
- anchors.bottom: parent.bottom
- height: UM.Theme.getSize("default_lining").width
- color: lineColor
- width: parent.width
- }
- }
-
- Column
- {
- id: printJobColumn
- anchors.top: printJobTitleBar.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
-
- Item
- {
- width: parent.width
- height: childrenRect.height
- opacity: 0.65
- Label
- {
- text: catalog.i18nc("@label", "Printing")
- font: UM.Theme.getFont("very_small")
-
- }
- Label
- {
- text: manager.activePrintJobs.length
- font: UM.Theme.getFont("small")
- anchors.right: parent.right
- }
- }
- Item
- {
- width: parent.width
- height: childrenRect.height
- opacity: 0.65
- Label
- {
- text: catalog.i18nc("@label", "Queued")
- font: UM.Theme.getFont("very_small")
- }
- Label
- {
- text: manager.queuedPrintJobs.length
- font: UM.Theme.getFont("small")
- anchors.right: parent.right
- }
- }
- }
- OpenPanelButton
- {
- anchors.top: printJobColumn.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: UM.Theme.getSize("default_margin").height
- id: configButton
- onClicked: base.manager.openPrintJobControlPanel()
- text: catalog.i18nc("@action:button", "View print jobs")
- }
-
- Item
- {
- // spacer
- anchors.top: configButton.bottom
- width: UM.Theme.getSize("default_margin").width
- height: UM.Theme.getSize("default_margin").height
- }
- }
-
-
- Rectangle
- {
- id: printersArea
- border.width: UM.Theme.getSize("default_lining").width
- border.color: lineColor
- anchors.top: printJobArea.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin:UM.Theme.getSize("default_margin").width
- radius: cornerRadius
- height: childrenRect.height
-
- Item
- {
- id: printersTitleBar
- width: parent.width
- height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
-
- Label
- {
- id: printersTitleLabel
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.top: parent.top
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- text: catalog.i18nc("@label:title", "Printers")
- font: UM.Theme.getFont("default")
- opacity: 0.75
- }
- Rectangle
- {
- anchors.bottom: parent.bottom
- height: UM.Theme.getSize("default_lining").width
- color: lineColor
- width: parent.width
- }
- }
- Column
- {
- id: printersColumn
- anchors.top: printersTitleBar.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
-
- Repeater
- {
- model: manager.connectedPrintersTypeCount
- Item
- {
- width: parent.width
- height: childrenRect.height
- opacity: 0.65
- Label
- {
- text: modelData.machine_type
- font: UM.Theme.getFont("very_small")
- }
-
- Label
- {
- text: modelData.count
- font: UM.Theme.getFont("small")
- anchors.right: parent.right
- }
- }
- }
- }
- OpenPanelButton
- {
- anchors.top: printersColumn.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: UM.Theme.getSize("default_margin").height
- id: printerConfigButton
- onClicked: base.manager.openPrinterControlPanel()
-
- text: catalog.i18nc("@action:button", "View printers")
- }
-
- Item
- {
- // spacer
- anchors.top: printerConfigButton.bottom
- width: UM.Theme.getSize("default_margin").width
- height: UM.Theme.getSize("default_margin").height
- }
- }
- }
-}
diff --git a/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml b/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml
deleted file mode 100644
index 0e86d55de8..0000000000
--- a/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml
+++ /dev/null
@@ -1,118 +0,0 @@
-import QtQuick 2.2
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-
-import UM 1.3 as UM
-import Cura 1.0 as Cura
-
-Component
-{
- Rectangle
- {
- id: monitorFrame
- width: maximumWidth
- height: maximumHeight
- color: UM.Theme.getColor("viewport_background")
- property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight")
- property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
- property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
-
- UM.I18nCatalog
- {
- id: catalog
- name: "cura"
- }
-
- Label
- {
- id: activePrintersLabel
- font: UM.Theme.getFont("large")
-
- anchors {
- top: parent.top
- topMargin: UM.Theme.getSize("default_margin").height * 2 // a bit more spacing to give it some breathing room
- horizontalCenter: parent.horizontalCenter
- }
-
- text: OutputDevice.printers.length == 0 ? catalog.i18nc("@label: arg 1 is group name", "%1 is not set up to host a group of connected Ultimaker 3 printers").arg(Cura.MachineManager.printerOutputDevices[0].name) : ""
-
- visible: OutputDevice.printers.length == 0
- }
-
- Item
- {
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.top: parent.top
- anchors.horizontalCenter: parent.horizontalCenter
-
- width: Math.min(800 * screenScaleFactor, maximumWidth)
- height: children.height
- visible: OutputDevice.printers.length != 0
-
- Label
- {
- id: addRemovePrintersLabel
- anchors.right: parent.right
- text: catalog.i18nc("@label link to connect manager", "Add/Remove printers")
- font: UM.Theme.getFont("default")
- color: UM.Theme.getColor("text")
- linkColor: UM.Theme.getColor("text_link")
- }
-
- MouseArea
- {
- anchors.fill: addRemovePrintersLabel
- hoverEnabled: true
- onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
- onEntered: addRemovePrintersLabel.font.underline = true
- onExited: addRemovePrintersLabel.font.underline = false
- }
- }
-
- ScrollView
- {
- id: printerScrollView
- anchors.margins: UM.Theme.getSize("default_margin").width
- anchors.top: activePrintersLabel.bottom
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_lining").width // To ensure border can be drawn.
- anchors.rightMargin: UM.Theme.getSize("default_lining").width
- anchors.right: parent.right
-
- ListView
- {
- anchors.fill: parent
- spacing: -UM.Theme.getSize("default_lining").height
-
- model: OutputDevice.printers
-
- delegate: PrinterInfoBlock
- {
- printer: modelData
- width: Math.min(800 * screenScaleFactor, maximumWidth)
- height: 125 * screenScaleFactor
-
- // Add a 1 pix margin, as the border is sometimes cut off otherwise.
- anchors.horizontalCenter: parent.horizontalCenter
- }
- }
- }
-
- PrinterVideoStream
- {
- visible: OutputDevice.activePrinter != null
- anchors.fill:parent
- }
-
- onVisibleChanged:
- {
- if (!monitorFrame.visible)
- {
- // After switching the Tab ensure that active printer is Null, the video stream image
- // might be active
- OutputDevice.setActivePrinter(null)
- }
- }
- }
-}
diff --git a/plugins/UM3NetworkPrinting/MonitorItem.qml b/plugins/UM3NetworkPrinting/MonitorItem.qml
deleted file mode 100644
index bbbc3feee6..0000000000
--- a/plugins/UM3NetworkPrinting/MonitorItem.qml
+++ /dev/null
@@ -1,54 +0,0 @@
-import QtQuick 2.2
-
-
-import UM 1.3 as UM
-import Cura 1.0 as Cura
-
-Component
-{
- Item
- {
- width: maximumWidth
- height: maximumHeight
- Image
- {
- id: cameraImage
- width: Math.min(sourceSize.width === 0 ? 800 * screenScaleFactor : sourceSize.width, maximumWidth)
- height: Math.floor((sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width)
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- z: 1
- Component.onCompleted:
- {
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
- {
- OutputDevice.activePrinter.camera.start()
- }
- }
- onVisibleChanged:
- {
- if(visible)
- {
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
- {
- OutputDevice.activePrinter.camera.start()
- }
- } else
- {
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
- {
- OutputDevice.activePrinter.camera.stop()
- }
- }
- }
- source:
- {
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null && OutputDevice.activePrinter.camera.latestImage)
- {
- return OutputDevice.activePrinter.camera.latestImage;
- }
- return "";
- }
- }
- }
-}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/OpenPanelButton.qml b/plugins/UM3NetworkPrinting/OpenPanelButton.qml
deleted file mode 100644
index 4bc1728f76..0000000000
--- a/plugins/UM3NetworkPrinting/OpenPanelButton.qml
+++ /dev/null
@@ -1,71 +0,0 @@
-import QtQuick 2.2
-import QtQuick.Controls 1.1
-import QtQuick.Controls.Styles 1.1
-
-import UM 1.1 as UM
-
-Button {
- objectName: "openPanelSaveAreaButton"
- id: openPanelSaveAreaButton
-
- UM.I18nCatalog { id: catalog; name: "cura"; }
-
- height: UM.Theme.getSize("save_button_save_to_button").height
- tooltip: catalog.i18nc("@info:tooltip", "Opens the print jobs page with your default web browser.")
- text: catalog.i18nc("@action:button", "View print jobs")
-
- // FIXME: This button style is copied and duplicated from SaveButton.qml
- style: ButtonStyle {
- background: Rectangle
- {
- border.width: UM.Theme.getSize("default_lining").width
- border.color:
- {
- if(!control.enabled)
- return UM.Theme.getColor("action_button_disabled_border");
- else if(control.pressed)
- return UM.Theme.getColor("print_button_ready_pressed_border");
- else if(control.hovered)
- return UM.Theme.getColor("print_button_ready_hovered_border");
- else
- return UM.Theme.getColor("print_button_ready_border");
- }
- color:
- {
- if(!control.enabled)
- return UM.Theme.getColor("action_button_disabled");
- else if(control.pressed)
- return UM.Theme.getColor("print_button_ready_pressed");
- else if(control.hovered)
- return UM.Theme.getColor("print_button_ready_hovered");
- else
- return UM.Theme.getColor("print_button_ready");
- }
-
- Behavior on color { ColorAnimation { duration: 50; } }
-
- implicitWidth: actualLabel.contentWidth + (UM.Theme.getSize("sidebar_margin").width * 2)
-
- Label {
- id: actualLabel
- anchors.centerIn: parent
- color:
- {
- if(!control.enabled)
- return UM.Theme.getColor("action_button_disabled_text");
- else if(control.pressed)
- return UM.Theme.getColor("print_button_ready_text");
- else if(control.hovered)
- return UM.Theme.getColor("print_button_ready_text");
- else
- return UM.Theme.getColor("print_button_ready_text");
- }
- font: UM.Theme.getFont("action_button")
- text: control.text;
- }
- }
- label: Item { }
- }
-
-
-}
diff --git a/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml b/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml
deleted file mode 100644
index 267516091b..0000000000
--- a/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml
+++ /dev/null
@@ -1,33 +0,0 @@
-import QtQuick 2.2
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-
-import UM 1.2 as UM
-
-
-Item
-{
- id: extruderInfo
- property var printCoreConfiguration
-
- width: Math.round(parent.width / 2)
- height: childrenRect.height
- Label
- {
- id: materialLabel
- text: printCoreConfiguration.activeMaterial != null ? printCoreConfiguration.activeMaterial.name : ""
- elide: Text.ElideRight
- width: parent.width
- font: UM.Theme.getFont("very_small")
- }
- Label
- {
- id: printCoreLabel
- text: printCoreConfiguration.hotendID
- anchors.top: materialLabel.bottom
- elide: Text.ElideRight
- width: parent.width
- font: UM.Theme.getFont("very_small")
- opacity: 0.5
- }
-}
diff --git a/plugins/UM3NetworkPrinting/PrintWindow.qml b/plugins/UM3NetworkPrinting/PrintWindow.qml
deleted file mode 100644
index 9793b218fc..0000000000
--- a/plugins/UM3NetworkPrinting/PrintWindow.qml
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (c) 2018 Ultimaker B.V.
-// Cura is released under the terms of the LGPLv3 or higher.
-
-import QtQuick 2.2
-import QtQuick.Window 2.2
-import QtQuick.Controls 1.2
-
-import UM 1.1 as UM
-
-UM.Dialog
-{
- id: base;
-
- minimumWidth: 500 * screenScaleFactor
- minimumHeight: 140 * screenScaleFactor
- maximumWidth: minimumWidth
- maximumHeight: minimumHeight
- width: minimumWidth
- height: minimumHeight
-
- visible: true
- modality: Qt.ApplicationModal
- onVisibleChanged:
- {
- if(visible)
- {
- resetPrintersModel()
- }
- else
- {
- OutputDevice.cancelPrintSelection()
- }
- }
- title: catalog.i18nc("@title:window", "Print over network")
-
- property var printersModel: ListModel{}
- function resetPrintersModel() {
- printersModel.clear()
- printersModel.append({ name: "Automatic", key: ""})
-
- for (var index in OutputDevice.printers)
- {
- printersModel.append({name: OutputDevice.printers[index].name, key: OutputDevice.printers[index].key})
- }
- }
-
- Column
- {
- id: printerSelection
- anchors.fill: parent
- anchors.top: parent.top
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- height: 50 * screenScaleFactor
- Label
- {
- id: manualPrinterSelectionLabel
- anchors
- {
- left: parent.left
- topMargin: UM.Theme.getSize("default_margin").height
- right: parent.right
- }
- text: catalog.i18nc("@label", "Printer selection")
- wrapMode: Text.Wrap
- height: 20 * screenScaleFactor
- }
-
- ComboBox
- {
- id: printerSelectionCombobox
- model: base.printersModel
- textRole: "name"
-
- width: parent.width
- height: 40 * screenScaleFactor
- Behavior on height { NumberAnimation { duration: 100 } }
- }
-
- SystemPalette
- {
- id: palette
- }
-
- UM.I18nCatalog { id: catalog; name: "cura"; }
- }
-
- leftButtons: [
- Button
- {
- text: catalog.i18nc("@action:button","Cancel")
- enabled: true
- onClicked: {
- base.visible = false;
- printerSelectionCombobox.currentIndex = 0
- OutputDevice.cancelPrintSelection()
- }
- }
- ]
-
- rightButtons: [
- Button
- {
- text: catalog.i18nc("@action:button","Print")
- enabled: true
- onClicked: {
- base.visible = false;
- OutputDevice.selectPrinter(printerSelectionCombobox.model.get(printerSelectionCombobox.currentIndex).key)
- // reset to defaults
- printerSelectionCombobox.currentIndex = 0
- }
- }
- ]
-}
diff --git a/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml b/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml
deleted file mode 100644
index 0217767a40..0000000000
--- a/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml
+++ /dev/null
@@ -1,431 +0,0 @@
-import QtQuick 2.2
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-
-import UM 1.3 as UM
-
-Rectangle
-{
- function strPadLeft(string, pad, length)
- {
- return (new Array(length + 1).join(pad) + string).slice(-length);
- }
-
- function getPrettyTime(time)
- {
- return OutputDevice.formatDuration(time)
- }
-
- function formatPrintJobPercent(printJob)
- {
- if (printJob == null)
- {
- return "";
- }
- if (printJob.timeTotal === 0)
- {
- return "";
- }
- return Math.min(100, Math.round(printJob.timeElapsed / printJob.timeTotal * 100)) + "%";
- }
-
- function printerStatusText(printer)
- {
- switch (printer.state)
- {
- case "pre_print":
- return catalog.i18nc("@label:status", "Preparing to print")
- case "printing":
- return catalog.i18nc("@label:status", "Printing");
- case "idle":
- return catalog.i18nc("@label:status", "Available");
- case "unreachable":
- return catalog.i18nc("@label:status", "Lost connection with the printer");
- case "maintenance":
- return catalog.i18nc("@label:status", "Unavailable");
- default:
- return catalog.i18nc("@label:status", "Unknown");
- }
- }
-
- id: printerDelegate
-
- property var printer: null
- property var printJob: printer != null ? printer.activePrintJob: null
-
- border.width: UM.Theme.getSize("default_lining").width
- border.color: mouse.containsMouse ? emphasisColor : lineColor
- z: mouse.containsMouse ? 1 : 0 // Push this item up a bit on mouse over to ensure that the highlighted bottom border is visible.
-
- MouseArea
- {
- id: mouse
- anchors.fill:parent
- onClicked: OutputDevice.setActivePrinter(printer)
- hoverEnabled: true;
-
- // Only clickable if no printer is selected
- enabled: OutputDevice.activePrinter == null && printer.state !== "unreachable"
- }
-
- Row
- {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.margins: UM.Theme.getSize("default_margin").width
-
- Rectangle
- {
- width: Math.round(parent.width / 3)
- height: parent.height
-
- Label // Print job name
- {
- id: jobNameLabel
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
-
- text: printJob != null ? printJob.name : ""
- font: UM.Theme.getFont("default_bold")
- elide: Text.ElideRight
-
- }
-
- Label
- {
- id: jobOwnerLabel
- anchors.top: jobNameLabel.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- text: printJob != null ? printJob.owner : ""
- opacity: 0.50
- elide: Text.ElideRight
- }
-
- Label
- {
- id: totalTimeLabel
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- text: printJob != null ? getPrettyTime(printJob.timeTotal) : ""
- opacity: 0.65
- font: UM.Theme.getFont("default")
- elide: Text.ElideRight
- }
- }
-
- Rectangle
- {
- width: Math.round(parent.width / 3 * 2)
- height: parent.height
-
- Label // Friendly machine name
- {
- id: printerNameLabel
- anchors.top: parent.top
- anchors.left: parent.left
- width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width)
- text: printer.name
- font: UM.Theme.getFont("default_bold")
- elide: Text.ElideRight
- }
-
- Label // Machine variant
- {
- id: printerTypeLabel
- anchors.top: printerNameLabel.bottom
- width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
- text: printer.type
- anchors.left: parent.left
- elide: Text.ElideRight
- font: UM.Theme.getFont("very_small")
- opacity: 0.50
- }
-
- Rectangle // Camera icon
- {
- id: showCameraIcon
- width: 40 * screenScaleFactor
- height: width
- radius: width
- anchors.right: printProgressArea.left
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- color: emphasisColor
- opacity: printer != null && printer.state === "unreachable" ? 0.3 : 1
-
- Image
- {
- width: parent.width
- height: width
- anchors.right: parent.right
- anchors.rightMargin: parent.rightMargin
- source: "camera-icon.svg"
- }
- }
-
- Row // PrintCore config
- {
- id: extruderInfo
- anchors.bottom: parent.bottom
-
- width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
- height: childrenRect.height
-
- spacing: UM.Theme.getSize("default_margin").width
-
- PrintCoreConfiguration
- {
- id: leftExtruderInfo
- width: Math.round((parent.width - extruderSeperator.width) / 2)
- printCoreConfiguration: printer.extruders[0]
- }
-
- Rectangle
- {
- id: extruderSeperator
- width: UM.Theme.getSize("default_lining").width
- height: parent.height
- color: lineColor
- }
-
- PrintCoreConfiguration
- {
- id: rightExtruderInfo
- width: Math.round((parent.width - extruderSeperator.width) / 2)
- printCoreConfiguration: printer.extruders[1]
- }
- }
-
- Rectangle // Print progress
- {
- id: printProgressArea
- anchors.right: parent.right
- anchors.top: parent.top
- height: showExtended ? parent.height: printProgressTitleBar.height
- width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
- border.width: UM.Theme.getSize("default_lining").width
- border.color: lineColor
- radius: cornerRadius
- property var showExtended: {
- if(printJob != null)
- {
- var extendStates = ["sent_to_printer", "wait_for_configuration", "printing", "pre_print", "post_print", "wait_cleanup", "queued"];
- return extendStates.indexOf(printJob.state) !== -1;
- }
- return printer.state == "disabled"
- }
-
- Item // Status and Percent
- {
- id: printProgressTitleBar
-
- property var showPercent: {
- return printJob != null && (["printing", "post_print", "pre_print", "sent_to_printer"].indexOf(printJob.state) !== -1);
- }
-
- width: parent.width
- //TODO: hardcoded value
- height: 40 * screenScaleFactor
- anchors.left: parent.left
-
- Label
- {
- id: statusText
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: progressText.left
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- anchors.verticalCenter: parent.verticalCenter
- text: {
- if (printer.state == "disabled")
- {
- return catalog.i18nc("@label:status", "Disabled");
- }
-
- if (printer.state === "unreachable")
- {
- return printerStatusText(printer);
- }
-
- if (printJob != null)
- {
- switch (printJob.state)
- {
- case "printing":
- case "post_print":
- return catalog.i18nc("@label:status", "Printing")
- case "wait_for_configuration":
- return catalog.i18nc("@label:status", "Reserved")
- case "wait_cleanup":
- case "wait_user_action":
- return catalog.i18nc("@label:status", "Finished")
- case "pre_print":
- case "sent_to_printer":
- return catalog.i18nc("@label", "Preparing to print")
- case "queued":
- return catalog.i18nc("@label:status", "Action required");
- case "pausing":
- case "paused":
- return catalog.i18nc("@label:status", "Paused");
- case "resuming":
- return catalog.i18nc("@label:status", "Resuming");
- case "aborted":
- return catalog.i18nc("@label:status", "Print aborted");
- default:
- // If print job has unknown status show printer.status
- return printerStatusText(printer);
- }
- }
- return printerStatusText(printer);
- }
-
- elide: Text.ElideRight
- font: UM.Theme.getFont("small")
- }
-
- Label
- {
- id: progressText
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- anchors.top: statusText.top
-
- text: formatPrintJobPercent(printJob)
- visible: printProgressTitleBar.showPercent
- //TODO: Hardcoded value
- opacity: 0.65
- font: UM.Theme.getFont("very_small")
- }
-
- Image
- {
- width: statusText.height
- height: width
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- anchors.top: statusText.top
-
- visible: !printProgressTitleBar.showPercent
-
- source: {
- if (printer.state == "disabled")
- {
- return "blocked-icon.svg";
- }
-
- if (printer.state === "unreachable")
- {
- return "";
- }
-
- if (printJob != null)
- {
- if(printJob.state === "queued")
- {
- return "action-required-icon.svg";
- }
- else if (printJob.state === "wait_cleanup")
- {
- return "checkmark-icon.svg";
- }
- }
- return ""; // We're not going to show it, so it will not be resolved as a url.
- }
- }
-
- Rectangle
- {
- //TODO: This will become a progress bar in the future
- width: parent.width
- height: UM.Theme.getSize("default_lining").height
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- visible: printProgressArea.showExtended
- color: lineColor
- }
- }
-
- Column
- {
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
-
- anchors.top: printProgressTitleBar.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
-
- width: parent.width - 2 * UM.Theme.getSize("default_margin").width
-
- visible: printProgressArea.showExtended
-
- Label // Status detail
- {
- text:
- {
- if (printer.state == "disabled")
- {
- return catalog.i18nc("@label", "Not accepting print jobs");
- }
-
- if (printer.state === "unreachable")
- {
- return "";
- }
-
- if(printJob != null)
- {
- switch (printJob.state)
- {
- case "printing":
- case "post_print":
- return catalog.i18nc("@label", "Finishes at: ") + OutputDevice.getTimeCompleted(printJob.timeTotal - printJob.timeElapsed)
- case "wait_cleanup":
- return catalog.i18nc("@label", "Clear build plate")
- case "sent_to_printer":
- case "pre_print":
- return catalog.i18nc("@label", "Preparing to print")
- case "wait_for_configuration":
- return catalog.i18nc("@label", "Not accepting print jobs")
- case "queued":
- return catalog.i18nc("@label", "Waiting for configuration change");
- default:
- return "";
- }
- }
- return "";
- }
- anchors.left: parent.left
- anchors.right: parent.right
- elide: Text.ElideRight
- wrapMode: Text.Wrap
-
- font: UM.Theme.getFont("default")
- }
-
- Label // Status 2nd row
- {
- text: {
- if(printJob != null)
- {
- if(printJob.state == "printing" || printJob.state == "post_print")
- {
- return OutputDevice.getDateCompleted(printJob.timeTotal - printJob.timeElapsed)
- }
- }
- return "";
- }
-
- elide: Text.ElideRight
- font: UM.Theme.getFont("default")
- }
- }
- }
- }
- }
-}
diff --git a/plugins/UM3NetworkPrinting/PrinterTile.qml b/plugins/UM3NetworkPrinting/PrinterTile.qml
deleted file mode 100644
index 3d03e93688..0000000000
--- a/plugins/UM3NetworkPrinting/PrinterTile.qml
+++ /dev/null
@@ -1,54 +0,0 @@
-import QtQuick 2.2
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-
-import UM 1.3 as UM
-import Cura 1.0 as Cura
-
-Rectangle
-{
- id: base
- width: 250 * screenScaleFactor
- height: 250 * screenScaleFactor
- signal clicked()
- MouseArea
- {
- anchors.fill:parent
- onClicked: base.clicked()
- }
- Rectangle
- {
- // TODO: Actually add UM icon / picture
- width: 100 * screenScaleFactor
- height: 100 * screenScaleFactor
- border.width: UM.Theme.getSize("default_lining").width
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- }
- Label
- {
- id: nameLabel
- anchors.bottom: ipLabel.top
- anchors.bottomMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- text: modelData.friendly_name.toString()
- font: UM.Theme.getFont("large")
- elide: Text.ElideMiddle;
- height: UM.Theme.getSize("section").height;
- }
- Label
- {
- id: ipLabel
- text: modelData.ip_address.toString()
- anchors.bottom: parent.bottom
- anchors.bottomMargin: UM.Theme.getSize("default_margin").height
- font: UM.Theme.getFont("default")
- height:10 * screenScaleFactor
- anchors.horizontalCenter: parent.horizontalCenter
- }
-}
-
diff --git a/plugins/UM3NetworkPrinting/PrinterVideoStream.qml b/plugins/UM3NetworkPrinting/PrinterVideoStream.qml
deleted file mode 100644
index 68758e095e..0000000000
--- a/plugins/UM3NetworkPrinting/PrinterVideoStream.qml
+++ /dev/null
@@ -1,99 +0,0 @@
-import QtQuick 2.2
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-
-import UM 1.3 as UM
-
-
-Item
-{
- Rectangle
- {
- anchors.fill:parent
- color: UM.Theme.getColor("viewport_overlay")
- opacity: 0.5
- }
-
- MouseArea
- {
- anchors.fill: parent
- onClicked: OutputDevice.setActivePrinter(null)
- z: 0
- }
-
- Button
- {
- id: backButton
- anchors.bottom: cameraImage.top
- anchors.bottomMargin: UM.Theme.getSize("default_margin").width
- anchors.right: cameraImage.right
-
- // TODO: Hardcoded sizes
- width: 20 * screenScaleFactor
- height: 20 * screenScaleFactor
-
- onClicked: OutputDevice.setActivePrinter(null)
-
- style: ButtonStyle
- {
- label: Item
- {
- UM.RecolorImage
- {
- anchors.verticalCenter: parent.verticalCenter
- anchors.horizontalCenter: parent.horizontalCenter
- width: control.width
- height: control.height
- sourceSize.width: width
- sourceSize.height: width
- source: UM.Theme.getIcon("cross1")
- }
- }
- background: Item {}
- }
- }
-
- Image
- {
- id: cameraImage
- width: Math.min(sourceSize.width === 0 ? 800 * screenScaleFactor : sourceSize.width, maximumWidth)
- height: Math.round((sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width)
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- z: 1
- onVisibleChanged:
- {
- if(visible)
- {
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
- {
- OutputDevice.activePrinter.camera.start()
- }
- } else
- {
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
- {
- OutputDevice.activePrinter.camera.stop()
- }
- }
- }
- source:
- {
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null && OutputDevice.activePrinter.camera.latestImage)
- {
- return OutputDevice.activePrinter.camera.latestImage;
- }
- return "";
- }
- }
-
- MouseArea
- {
- anchors.fill: cameraImage
- onClicked:
- {
- OutputDevice.setActivePrinter(null)
- }
- z: 1
- }
-}
diff --git a/plugins/UM3NetworkPrinting/SendMaterialJob.py b/plugins/UM3NetworkPrinting/SendMaterialJob.py
deleted file mode 100644
index 8491e79c29..0000000000
--- a/plugins/UM3NetworkPrinting/SendMaterialJob.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# Copyright (c) 2018 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-import json #To understand the list of materials from the printer reply.
-import os #To walk over material files.
-import os.path #To filter on material files.
-from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest #To listen to the reply from the printer.
-from typing import Any, Dict, Set, TYPE_CHECKING
-import urllib.parse #For getting material IDs from their file names.
-
-from UM.Job import Job #The interface we're implementing.
-from UM.Logger import Logger
-from UM.MimeTypeDatabase import MimeTypeDatabase #To strip the extensions of the material profile files.
-from UM.Resources import Resources
-from UM.Settings.ContainerRegistry import ContainerRegistry #To find the GUIDs of materials.
-
-from cura.CuraApplication import CuraApplication #For the resource types.
-
-if TYPE_CHECKING:
- from .ClusterUM3OutputDevice import ClusterUM3OutputDevice
-
-## Asynchronous job to send material profiles to the printer.
-#
-# This way it won't freeze up the interface while sending those materials.
-class SendMaterialJob(Job):
- def __init__(self, device: "ClusterUM3OutputDevice") -> None:
- super().__init__()
- self.device = device #type: ClusterUM3OutputDevice
-
- def run(self) -> None:
- self.device.get("materials/", on_finished = self.sendMissingMaterials)
-
- def sendMissingMaterials(self, reply: QNetworkReply) -> None:
- if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: #Got an error from the HTTP request.
- Logger.log("e", "Couldn't request current material storage on printer. Not syncing materials.")
- return
-
- remote_materials_list = reply.readAll().data().decode("utf-8")
- try:
- remote_materials_list = json.loads(remote_materials_list)
- except json.JSONDecodeError:
- Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.")
- return
- try:
- remote_materials_by_guid = {material["guid"]: material for material in remote_materials_list} #Index by GUID.
- except KeyError:
- Logger.log("e", "Request material storage on printer: Printer's answer was missing GUIDs.")
- return
-
- container_registry = ContainerRegistry.getInstance()
- local_materials_list = filter(lambda material: ("GUID" in material and "version" in material and "id" in material), container_registry.findContainersMetadata(type = "material"))
- local_materials_by_guid = {material["GUID"]: material for material in local_materials_list if material["id"] == material["base_file"]}
- for material in local_materials_list: #For each GUID get the material with the highest version number.
- try:
- if int(material["version"]) > local_materials_by_guid[material["GUID"]]["version"]:
- local_materials_by_guid[material["GUID"]] = material
- except ValueError:
- Logger.log("e", "Material {material_id} has invalid version number {number}.".format(material_id = material["id"], number = material["version"]))
- continue
-
- materials_to_send = set() #type: Set[Dict[str, Any]]
- for guid, material in local_materials_by_guid.items():
- if guid not in remote_materials_by_guid:
- materials_to_send.add(material["id"])
- continue
- try:
- if int(material["version"]) > remote_materials_by_guid[guid]["version"]:
- materials_to_send.add(material["id"])
- continue
- except KeyError:
- Logger.log("e", "Current material storage on printer was an invalid reply (missing version).")
- return
-
- for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.MaterialInstanceContainer):
- try:
- mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
- except MimeTypeDatabase.MimeTypeNotFoundError:
- continue #Not the sort of file we'd like to send then.
- _, file_name = os.path.split(file_path)
- material_id = urllib.parse.unquote_plus(mime_type.stripExtension(file_name))
- if material_id not in materials_to_send:
- continue
-
- parts = []
- with open(file_path, "rb") as f:
- parts.append(self.device._createFormPart("name=\"file\"; filename=\"{file_name}\"".format(file_name = file_name), f.read()))
- signature_file_path = file_path + ".sig"
- if os.path.exists(signature_file_path):
- _, signature_file_name = os.path.split(signature_file_path)
- with open(signature_file_path, "rb") as f:
- parts.append(self.device._createFormPart("name=\"signature_file\"; filename=\"{file_name}\"".format(file_name = signature_file_name), f.read()))
-
- Logger.log("d", "Syncing material {material_id} with cluster.".format(material_id = material_id))
- self.device.postFormWithParts(target = "materials/", parts = parts, on_finished = self.sendingFinished)
-
- def sendingFinished(self, reply: QNetworkReply):
- if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
- Logger.log("e", "Received error code from printer when syncing material: {code}".format(code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)))
- Logger.log("e", reply.readAll().data().decode("utf-8"))
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/UM3InfoComponents.qml b/plugins/UM3NetworkPrinting/UM3InfoComponents.qml
deleted file mode 100644
index a19d1be60d..0000000000
--- a/plugins/UM3NetworkPrinting/UM3InfoComponents.qml
+++ /dev/null
@@ -1,125 +0,0 @@
-import UM 1.2 as UM
-import Cura 1.0 as Cura
-
-import QtQuick 2.2
-import QtQuick.Controls 1.1
-import QtQuick.Layouts 1.1
-import QtQuick.Window 2.1
-
-Item
-{
- id: base
-
- property string activeQualityDefinitionId: Cura.MachineManager.activeQualityDefinitionId
- property bool isUM3: activeQualityDefinitionId == "ultimaker3" || activeQualityDefinitionId.match("ultimaker_") != null
- property bool printerConnected: Cura.MachineManager.printerConnected
- property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
- property bool authenticationRequested: printerConnected && (Cura.MachineManager.printerOutputDevices[0].authenticationState == 2 || Cura.MachineManager.printerOutputDevices[0].authenticationState == 5) // AuthState.AuthenticationRequested or AuthenticationReceived.
-
- Row
- {
- objectName: "networkPrinterConnectButton"
- visible: isUM3
- spacing: UM.Theme.getSize("default_margin").width
-
- Button
- {
- height: UM.Theme.getSize("save_button_save_to_button").height
- tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer")
- text: catalog.i18nc("@action:button", "Request Access")
- style: UM.Theme.styles.sidebar_action_button
- onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication()
- visible: printerConnected && !printerAcceptsCommands && !authenticationRequested
- }
-
- Button
- {
- height: UM.Theme.getSize("save_button_save_to_button").height
- tooltip: catalog.i18nc("@info:tooltip", "Connect to a printer")
- text: catalog.i18nc("@action:button", "Connect")
- style: UM.Theme.styles.sidebar_action_button
- onClicked: connectActionDialog.show()
- visible: !printerConnected
- }
- }
-
- UM.Dialog
- {
- id: connectActionDialog
- Loader
- {
- anchors.fill: parent
- source: "DiscoverUM3Action.qml"
- }
- rightButtons: Button
- {
- text: catalog.i18nc("@action:button", "Close")
- iconName: "dialog-close"
- onClicked: connectActionDialog.reject()
- }
- }
-
-
- Column
- {
- objectName: "networkPrinterConnectionInfo"
- visible: isUM3
- spacing: UM.Theme.getSize("default_margin").width
- anchors.fill: parent
-
- Button
- {
- tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer")
- text: catalog.i18nc("@action:button", "Request Access")
- onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication()
- visible: printerConnected && !printerAcceptsCommands && !authenticationRequested
- }
-
- Row
- {
- visible: printerConnected
- spacing: UM.Theme.getSize("default_margin").width
-
- anchors.left: parent.left
- anchors.right: parent.right
- height: childrenRect.height
-
- Column
- {
- Repeater
- {
- model: Cura.ExtrudersModel { simpleNames: true }
- Label { text: model.name }
- }
- }
- Column
- {
- Repeater
- {
- id: nozzleColumn
- model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].hotendIds : null
- Label { text: nozzleColumn.model[index] }
- }
- }
- Column
- {
- Repeater
- {
- id: materialColumn
- model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].materialNames : null
- Label { text: materialColumn.model[index] }
- }
- }
- }
-
- Button
- {
- tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura")
- text: catalog.i18nc("@action:button", "Activate Configuration")
- visible: false // printerConnected && !isClusterPrinter()
- onClicked: manager.loadConfigurationFromPrinter()
- }
- }
-
- UM.I18nCatalog{id: catalog; name:"cura"}
-}
diff --git a/plugins/UM3NetworkPrinting/__init__.py b/plugins/UM3NetworkPrinting/__init__.py
index b68086cb75..3da7795589 100644
--- a/plugins/UM3NetworkPrinting/__init__.py
+++ b/plugins/UM3NetworkPrinting/__init__.py
@@ -1,14 +1,15 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from .src import DiscoverUM3Action
+from .src import UM3OutputDevicePlugin
-from . import DiscoverUM3Action
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
-
-from . import UM3OutputDevicePlugin
def getMetaData():
return {}
+
def register(app):
- return { "output_device": UM3OutputDevicePlugin.UM3OutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}
\ No newline at end of file
+ return {
+ "output_device": UM3OutputDevicePlugin.UM3OutputDevicePlugin(),
+ "machine_action": DiscoverUM3Action.DiscoverUM3Action()
+ }
diff --git a/plugins/UM3NetworkPrinting/camera-icon.svg b/plugins/UM3NetworkPrinting/camera-icon.svg
deleted file mode 100644
index 29adfa5875..0000000000
--- a/plugins/UM3NetworkPrinting/camera-icon.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/plugin.json b/plugins/UM3NetworkPrinting/plugin.json
index e7b59fadd6..088b4dae6a 100644
--- a/plugins/UM3NetworkPrinting/plugin.json
+++ b/plugins/UM3NetworkPrinting/plugin.json
@@ -2,7 +2,7 @@
"name": "UM3 Network Connection",
"author": "Ultimaker B.V.",
"description": "Manages network connections to Ultimaker 3 printers.",
- "version": "1.0.0",
- "api": 4,
+ "version": "1.0.1",
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3 Extended.png b/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3 Extended.png
new file mode 100644
index 0000000000..1ce19c2933
Binary files /dev/null and b/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3 Extended.png differ
diff --git a/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3.png b/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3.png
new file mode 100644
index 0000000000..4639cb3fde
Binary files /dev/null and b/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3.png differ
diff --git a/plugins/UM3NetworkPrinting/resources/png/Ultimaker S5.png b/plugins/UM3NetworkPrinting/resources/png/Ultimaker S5.png
new file mode 100644
index 0000000000..29ba428e38
Binary files /dev/null and b/plugins/UM3NetworkPrinting/resources/png/Ultimaker S5.png differ
diff --git a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml
new file mode 100644
index 0000000000..c0369cac0b
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml
@@ -0,0 +1,56 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.3
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.3
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+Rectangle
+{
+ id: base
+
+ property var enabled: true
+
+ property var iconSource: null
+ color: enabled ? UM.Theme.getColor("monitor_icon_primary") : UM.Theme.getColor("monitor_icon_disabled")
+ height: width
+ radius: Math.round(0.5 * width)
+ width: 24 * screenScaleFactor
+
+ UM.RecolorImage
+ {
+ id: icon
+ anchors
+ {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ }
+ color: UM.Theme.getColor("monitor_icon_accent")
+ height: width
+ source: iconSource
+ width: Math.round(parent.width / 2)
+ }
+
+ MouseArea
+ {
+ id: clickArea
+ anchors.fill: parent
+ hoverEnabled: base.enabled
+ onClicked:
+ {
+ if (base.enabled)
+ {
+ if (OutputDevice.activeCameraUrl != "")
+ {
+ OutputDevice.setActiveCameraUrl("")
+ }
+ else
+ {
+ OutputDevice.setActiveCameraUrl(modelData.cameraUrl)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml
similarity index 93%
rename from plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
rename to plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml
index 127b3c35bd..3883a7e285 100644
--- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
+++ b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml
@@ -1,3 +1,6 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
import UM 1.2 as UM
import Cura 1.0 as Cura
@@ -25,7 +28,7 @@ Cura.MachineAction
// Check if there is another instance with the same key
if (!manager.existsKey(printerKey))
{
- manager.setKey(printerKey)
+ manager.associateActiveMachineWithPrinterDevice(base.selectedDevice)
manager.setGroupName(printerName) // TODO To change when the groups have a name
completed()
}
@@ -61,6 +64,7 @@ Cura.MachineAction
width: parent.width
text: catalog.i18nc("@title:window", "Connect to Networked Printer")
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
font.pointSize: 18
}
@@ -69,6 +73,7 @@ Cura.MachineAction
id: pageDescription
width: parent.width
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
text: catalog.i18nc("@label", "To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your printer from the list below:")
}
@@ -179,6 +184,7 @@ Cura.MachineAction
text: listview.model[index].name
color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text
elide: Text.ElideRight
+ renderType: Text.NativeRendering
}
MouseArea
@@ -201,6 +207,7 @@ Cura.MachineAction
anchors.left: parent.left
anchors.right: parent.right
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
text: catalog.i18nc("@label", "If your printer is not listed, read the network printing troubleshooting guide").arg("https://ultimaker.com/en/troubleshooting");
onLinkActivated: Qt.openUrlExternally(link)
}
@@ -216,8 +223,9 @@ Cura.MachineAction
width: parent.width
wrapMode: Text.WordWrap
text: base.selectedDevice ? base.selectedDevice.name : ""
- font: UM.Theme.getFont("large")
+ font: UM.Theme.getFont("large_bold")
elide: Text.ElideRight
+ renderType: Text.NativeRendering
}
Grid
{
@@ -228,12 +236,14 @@ Cura.MachineAction
{
width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
text: catalog.i18nc("@label", "Type")
}
Label
{
width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
text:
{
if(base.selectedDevice)
@@ -265,24 +275,28 @@ Cura.MachineAction
{
width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
text: catalog.i18nc("@label", "Firmware version")
}
Label
{
width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
text: base.selectedDevice ? base.selectedDevice.firmwareVersion : ""
}
Label
{
width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
text: catalog.i18nc("@label", "Address")
}
Label
{
width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
text: base.selectedDevice ? base.selectedDevice.ipAddress : ""
}
}
@@ -291,6 +305,7 @@ Cura.MachineAction
{
width: parent.width
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
text:{
// The property cluster size does not exist for older UM3 devices.
if(!base.selectedDevice || base.selectedDevice.clusterSize == null || base.selectedDevice.clusterSize == 1)
@@ -312,6 +327,7 @@ Cura.MachineAction
{
width: parent.width
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
visible: base.selectedDevice != null && !base.completeProperties
text: catalog.i18nc("@label", "The printer at this address has not yet responded." )
}
@@ -355,16 +371,16 @@ Cura.MachineAction
Label
{
- text: catalog.i18nc("@alabel","Enter the IP address or hostname of your printer on the network.")
+ text: catalog.i18nc("@alabel", "Enter the IP address or hostname of your printer on the network.")
width: parent.width
wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
}
TextField
{
id: addressField
width: parent.width
- maximumLength: 40
validator: RegExpValidator
{
regExp: /[a-zA-Z0-9\.\-\_]*/
diff --git a/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml b/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml
new file mode 100644
index 0000000000..fae8280488
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml
@@ -0,0 +1,90 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+/**
+ * The expandable component has 3 major sub components:
+ * - The headerItem Always visible and should hold some info about what happens if the component is expanded
+ * - The popupItem The content that needs to be shown if the component is expanded.
+ */
+Item
+{
+ id: base
+
+ property bool expanded: false
+ property bool enabled: true
+ property var borderWidth: 1
+ property color borderColor: UM.Theme.getColor("monitor_card_border")
+ property color headerBackgroundColor: UM.Theme.getColor("monitor_icon_accent")
+ property color headerHoverColor: UM.Theme.getColor("monitor_card_hover")
+ property color drawerBackgroundColor: UM.Theme.getColor("monitor_icon_accent")
+ property alias headerItem: header.children
+ property alias drawerItem: drawer.children
+
+ width: parent.width
+ height: childrenRect.height
+
+ Rectangle
+ {
+ id: header
+ border
+ {
+ color: borderColor
+ width: borderWidth
+ }
+ color: base.enabled && headerMouseArea.containsMouse ? headerHoverColor : headerBackgroundColor
+ height: childrenRect.height
+ width: parent.width
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ Behavior on color
+ {
+ ColorAnimation
+ {
+ duration: 100
+ }
+ }
+ }
+
+ MouseArea
+ {
+ id: headerMouseArea
+ anchors.fill: header
+ onClicked:
+ {
+ if (!base.enabled) return
+ base.expanded = !base.expanded
+ }
+ hoverEnabled: base.enabled
+ }
+
+ Rectangle
+ {
+ id: drawer
+ anchors
+ {
+ top: header.bottom
+ topMargin: -1
+ }
+ border
+ {
+ color: borderColor
+ width: borderWidth
+ }
+ clip: true
+ color: headerBackgroundColor
+ height: base.expanded ? childrenRect.height : 0
+ width: parent.width
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ Behavior on height
+ {
+ NumberAnimation
+ {
+ duration: 100
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/GenericPopUp.qml b/plugins/UM3NetworkPrinting/resources/qml/GenericPopUp.qml
new file mode 100644
index 0000000000..74d9377f3e
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/GenericPopUp.qml
@@ -0,0 +1,227 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Dialogs 1.1
+import QtGraphicalEffects 1.0
+import UM 1.3 as UM
+
+/**
+ * This is a generic pop-up element which can be supplied with a target and a content item. The
+ * content item will appear to the left, right, above, or below the target depending on the value of
+ * the direction property
+ */
+Popup
+{
+ id: base
+
+ /**
+ * The target item is what the pop-up is "tied" to, usually a button
+ */
+ property var target
+
+ /**
+ * Which direction should the pop-up "point"?
+ * Possible values include:
+ * - "up"
+ * - "down"
+ * - "left"
+ * - "right"
+ */
+ property string direction: "down"
+
+ /**
+ * We save the default direction so that if a pop-up was flipped but later has space (i.e. it
+ * moved), we can unflip it back to the default direction.
+ */
+ property string originalDirection: ""
+
+ /**
+ * Should the popup close when you click outside it? For example, this is
+ * disabled by the InfoBlurb component since it's opened and closed using mouse
+ * hovers, not clicks.
+ */
+ property bool closeOnClick: true
+
+ /**
+ * Use white for context menus, dark grey for info blurbs!
+ */
+ property var color: "#ffffff" // TODO: Theme!
+
+ Component.onCompleted:
+ {
+ recalculatePosition()
+
+ // Set the direction here so it's only set once and never mutated
+ originalDirection = (' ' + direction).slice(1)
+ }
+
+ background: Item
+ {
+ anchors.fill: parent
+
+ DropShadow
+ {
+ anchors.fill: pointedRectangle
+ color: UM.Theme.getColor("monitor_shadow")
+ radius: UM.Theme.getSize("monitor_shadow_radius").width
+ source: pointedRectangle
+ transparentBorder: true
+ verticalOffset: 2 * screenScaleFactor
+ }
+
+ Item
+ {
+ id: pointedRectangle
+ anchors
+ {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ }
+ height: parent.height - 10 * screenScaleFactor // Because of the shadow
+ width: parent.width - 10 * screenScaleFactor // Because of the shadow
+
+ Rectangle
+ {
+ id: point
+ anchors
+ {
+ horizontalCenter:
+ {
+ switch(direction)
+ {
+ case "left":
+ return bloop.left
+ case "right":
+ return bloop.right
+ default:
+ return bloop.horizontalCenter
+ }
+ }
+ verticalCenter:
+ {
+ switch(direction)
+ {
+ case "up":
+ return bloop.top
+ case "down":
+ return bloop.bottom
+ default:
+ return bloop.verticalCenter
+ }
+ }
+ }
+ color: base.color
+ height: 12 * screenScaleFactor
+ transform: Rotation
+ {
+ angle: 45
+ origin.x: point.width / 2
+ origin.y: point.height / 2
+ }
+ width: height
+ }
+
+ Rectangle
+ {
+ id: bloop
+ anchors
+ {
+ fill: parent
+ leftMargin: direction == "left" ? 8 * screenScaleFactor : 0
+ rightMargin: direction == "right" ? 8 * screenScaleFactor : 0
+ topMargin: direction == "up" ? 8 * screenScaleFactor : 0
+ bottomMargin: direction == "down" ? 8 * screenScaleFactor : 0
+ }
+ color: base.color
+ width: parent.width
+ }
+ }
+ }
+
+ visible: false
+ onClosed: visible = false
+ onOpened:
+ {
+ // Flip orientation if necessary
+ recalculateOrientation()
+
+ // Fix position if necessary
+ recalculatePosition()
+
+ // Show the pop up
+ visible = true
+ }
+ closePolicy: closeOnClick ? Popup.CloseOnPressOutside : Popup.NoAutoClose
+
+ clip: true
+
+ padding: UM.Theme.getSize("monitor_shadow_radius").width
+ topPadding: direction == "up" ? padding + 8 * screenScaleFactor : padding
+ bottomPadding: direction == "down" ? padding + 8 * screenScaleFactor : padding
+ leftPadding: direction == "left" ? padding + 8 * screenScaleFactor : padding
+ rightPadding: direction == "right" ? padding + 8 * screenScaleFactor : padding
+
+ function recalculatePosition() {
+
+ // Stupid pop-up logic causes the pop-up to resize, so let's compute what it SHOULD be
+ const realWidth = contentItem.implicitWidth + leftPadding + rightPadding
+ const realHeight = contentItem.implicitHeight + topPadding + bottomPadding
+
+ var centered = {
+ x: target.x + target.width / 2 - realWidth / 2,
+ y: target.y + target.height / 2 - realHeight / 2
+ }
+
+ switch(direction)
+ {
+ case "left":
+ x = target.x + target.width
+ y = centered.y
+ break
+ case "right":
+ x = target.x - realWidth
+ y = centered.y
+ break
+ case "up":
+ x = centered.x
+ y = target.y + target.height
+ break
+ case "down":
+ x = centered.x
+ y = target.y - realHeight
+ break
+ }
+ }
+
+ function recalculateOrientation() {
+ var availableSpace
+ var targetPosition = target.mapToItem(monitorFrame, 0, 0)
+
+ // Stupid pop-up logic causes the pop-up to resize, so let's compute what it SHOULD be
+ const realWidth = contentItem.implicitWidth + leftPadding + rightPadding
+ const realHeight = contentItem.implicitHeight + topPadding + bottomPadding
+
+ switch(originalDirection)
+ {
+ case "up":
+ availableSpace = monitorFrame.height - (targetPosition.y + target.height)
+ direction = availableSpace < realHeight ? "down" : originalDirection
+ break
+ case "down":
+ availableSpace = targetPosition.y
+ direction = availableSpace < realHeight ? "up" : originalDirection
+ break
+ case "right":
+ availableSpace = targetPosition.x
+ direction = availableSpace < realWidth ? "left" : originalDirection
+ break
+ case "left":
+ availableSpace = monitorFrame.width - (targetPosition.x + target.width)
+ direction = availableSpace < realWidth ? "right" : originalDirection
+ break
+ }
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorBuildplateConfiguration.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorBuildplateConfiguration.qml
new file mode 100644
index 0000000000..d1a0c207c5
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorBuildplateConfiguration.qml
@@ -0,0 +1,74 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+
+/**
+ * This component comprises a buildplate icon and the buildplate name. It is
+ * used by the MonitorPrinterConfiguration component along with two instances
+ * of MonitorExtruderConfiguration.
+ *
+ * NOTE: For most labels, a fixed height with vertical alignment is used to make
+ * layouts more deterministic (like the fixed-size textboxes used in original
+ * mock-ups). This is also a stand-in for CSS's 'line-height' property. Denoted
+ * with '// FIXED-LINE-HEIGHT:'.
+ */
+Item
+{
+ // The buildplate name
+ property var buildplate: null
+
+ // Height is one 18px label/icon
+ height: 18 * screenScaleFactor // TODO: Theme!
+ width: childrenRect.width
+
+ Row
+ {
+ height: parent.height
+ spacing: UM.Theme.getSize("print_setup_slider_handle").width // TODO: Theme! (Should be same as extruder spacing)
+
+ // This wrapper ensures that the buildplate icon is located centered
+ // below an extruder icon.
+ Item
+ {
+ height: parent.height
+ width: 32 * screenScaleFactor // Ensure the icon is centered under the extruder icon (same width)
+
+ Rectangle
+ {
+ anchors.centerIn: parent
+ height: parent.height
+ width: height
+ color: buildplateIcon.visible > 0 ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading")
+ radius: Math.floor(height / 2)
+ }
+
+ UM.RecolorImage
+ {
+ id: buildplateIcon
+ anchors.centerIn: parent
+ color: UM.Theme.getColor("monitor_icon_primary")
+ height: parent.height
+ source: "../svg/icons/buildplate.svg"
+ width: height
+ visible: buildplate
+ }
+ }
+
+ Label
+ {
+ id: buildplateLabel
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("default") // 12pt, regular
+ text: buildplate ? buildplate : ""
+ visible: text !== ""
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorCarousel.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorCarousel.qml
new file mode 100644
index 0000000000..0d7a177dd3
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorCarousel.qml
@@ -0,0 +1,258 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.3
+import QtQuick.Controls 2.0
+import QtGraphicalEffects 1.0
+import UM 1.3 as UM
+
+Item
+{
+ id: base
+
+ property var currentIndex: 0
+ property var tileWidth: 834 * screenScaleFactor // TODO: Theme!
+ property var tileHeight: 216 * screenScaleFactor // TODO: Theme!
+ property var tileSpacing: 60 * screenScaleFactor // TODO: Theme!
+
+ // Array/model of printers to populate the carousel with
+ property var printers: []
+
+ // Maximum distance the carousel can be shifted
+ property var maxOffset: (printers.length - 1) * (tileWidth + tileSpacing)
+
+ height: centerSection.height
+ width: maximumWidth
+
+ // Enable keyboard navigation
+ Keys.onLeftPressed: navigateTo(currentIndex - 1)
+ Keys.onRightPressed: navigateTo(currentIndex + 1)
+
+ Item
+ {
+ id: leftHint
+ anchors
+ {
+ right: leftButton.left
+ rightMargin: 12 * screenScaleFactor // TODO: Theme!
+ left: parent.left
+ }
+ height: parent.height
+ z: 10
+ LinearGradient
+ {
+ anchors.fill: parent
+ start: Qt.point(0, 0)
+ end: Qt.point(leftHint.width, 0)
+ gradient: Gradient
+ {
+ GradientStop
+ {
+ position: 0.0
+ color: UM.Theme.getColor("monitor_stage_background")
+ }
+ GradientStop
+ {
+ position: 1.0
+ color: UM.Theme.getColor("monitor_stage_background_fade")
+ }
+ }
+ }
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: navigateTo(currentIndex - 1)
+ }
+ }
+
+ Button
+ {
+ id: leftButton
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ right: centerSection.left
+ rightMargin: 12 * screenScaleFactor // TODO: Theme!
+ }
+ width: 36 * screenScaleFactor // TODO: Theme!
+ height: 72 * screenScaleFactor // TODO: Theme!
+ visible: currentIndex > 0
+ hoverEnabled: true
+ z: 10
+ onClicked: navigateTo(currentIndex - 1)
+ background: Rectangle
+ {
+ color: leftButton.hovered ? UM.Theme.getColor("monitor_card_hover") : UM.Theme.getColor("monitor_card_background")
+ border.width: 1 * screenScaleFactor // TODO: Theme!
+ border.color: UM.Theme.getColor("monitor_card_border")
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ }
+ contentItem: Item
+ {
+ anchors.fill: parent
+ UM.RecolorImage
+ {
+ anchors.centerIn: parent
+ width: 18 // TODO: Theme!
+ height: width // TODO: Theme!
+ sourceSize.width: width // TODO: Theme!
+ sourceSize.height: width // TODO: Theme!
+ color: UM.Theme.getColor("monitor_text_primary")
+ source: UM.Theme.getIcon("arrow_left")
+ }
+ }
+ }
+
+ Item
+ {
+ id: centerSection
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: tileWidth
+ height: tiles.height
+ z: 1
+
+ Row
+ {
+ id: tiles
+ height: childrenRect.height
+ width: 5 * tileWidth + 4 * tileSpacing // TODO: Theme!
+ x: 0
+ z: 0
+ Behavior on x
+ {
+ NumberAnimation
+ {
+ duration: 200
+ easing.type: Easing.InOutCubic
+ }
+ }
+ spacing: 60 * screenScaleFactor // TODO: Theme!
+
+ Repeater
+ {
+ model: printers
+ MonitorPrinterCard
+ {
+ printer: modelData
+ enabled: model.index == currentIndex
+ }
+ }
+ }
+ }
+
+ Button
+ {
+ id: rightButton
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ left: centerSection.right
+ leftMargin: 12 * screenScaleFactor // TODO: Theme!
+ }
+ width: 36 * screenScaleFactor // TODO: Theme!
+ height: 72 * screenScaleFactor // TODO: Theme!
+ z: 10
+ visible: currentIndex < printers.length - 1
+ onClicked: navigateTo(currentIndex + 1)
+ hoverEnabled: true
+ background: Rectangle
+ {
+ color: rightButton.hovered ? UM.Theme.getColor("monitor_card_hover") : UM.Theme.getColor("monitor_card_background")
+ border.width: 1 * screenScaleFactor // TODO: Theme!
+ border.color: UM.Theme.getColor("monitor_card_border")
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ }
+ contentItem: Item
+ {
+ anchors.fill: parent
+ UM.RecolorImage
+ {
+ anchors.centerIn: parent
+ width: 18 // TODO: Theme!
+ height: width // TODO: Theme!
+ sourceSize.width: width // TODO: Theme!
+ sourceSize.height: width // TODO: Theme!
+ color: UM.Theme.getColor("monitor_text_primary")
+ source: UM.Theme.getIcon("arrow_right")
+ }
+ }
+ }
+
+ Item
+ {
+ id: rightHint
+ anchors
+ {
+ left: rightButton.right
+ leftMargin: 12 * screenScaleFactor // TODO: Theme!
+ right: parent.right
+ }
+ height: centerSection.height
+ z: 10
+
+ LinearGradient
+ {
+ anchors.fill: parent
+ start: Qt.point(0, 0)
+ end: Qt.point(rightHint.width, 0)
+ gradient: Gradient
+ {
+ GradientStop
+ {
+ position: 0.0
+ color: UM.Theme.getColor("monitor_stage_background_fade")
+ }
+ GradientStop
+ {
+ position: 1.0
+ color: UM.Theme.getColor("monitor_stage_background")
+ }
+ }
+ }
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: navigateTo(currentIndex + 1)
+ }
+ }
+
+ Row
+ {
+ id: navigationDots
+ anchors
+ {
+ horizontalCenter: centerSection.horizontalCenter
+ top: centerSection.bottom
+ topMargin: 36 * screenScaleFactor // TODO: Theme!
+ }
+ spacing: 8 * screenScaleFactor // TODO: Theme!
+ visible: printers.length > 1
+ Repeater
+ {
+ model: printers
+ Button
+ {
+ background: Rectangle
+ {
+ color: model.index == currentIndex ? UM.Theme.getColor("monitor_carousel_dot_current") : UM.Theme.getColor("monitor_carousel_dot")
+ radius: Math.floor(width / 2)
+ width: 12 * screenScaleFactor // TODO: Theme!
+ height: width // TODO: Theme!
+ }
+ onClicked: navigateTo(model.index)
+ }
+ }
+ }
+
+ function navigateTo( i ) {
+ if (i >= 0 && i < printers.length)
+ {
+ tiles.x = -1 * i * (tileWidth + tileSpacing)
+ currentIndex = i
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorConfigOverrideDialog.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorConfigOverrideDialog.qml
new file mode 100644
index 0000000000..1718994d83
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorConfigOverrideDialog.qml
@@ -0,0 +1,142 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.3
+import QtQuick.Controls 1.4
+import QtQuick.Layouts 1.3
+import QtQuick.Dialogs 1.2
+import UM 1.3 as UM
+
+UM.Dialog
+{
+ id: overrideConfirmationDialog
+
+ property var printer: null
+
+ minimumWidth: screenScaleFactor * 640;
+ minimumHeight: screenScaleFactor * 320;
+ width: minimumWidth
+ height: minimumHeight
+ title: catalog.i18nc("@title:window", "Configuration Changes")
+ rightButtons:
+ [
+ Button
+ {
+ id: overrideButton
+ anchors.margins: UM.Theme.getSize("default_margin").width
+ text: catalog.i18nc("@action:button", "Override")
+ onClicked:
+ {
+ OutputDevice.forceSendJob(printer.activePrintJob.key)
+ overrideConfirmationDialog.close()
+ }
+ },
+ Button
+ {
+ id: cancelButton
+ anchors.margins: UM.Theme.getSize("default_margin").width
+ text: catalog.i18nc("@action:button", "Cancel")
+ onClicked:
+ {
+ overrideConfirmationDialog.reject()
+ }
+ }
+ ]
+
+ Label
+ {
+ anchors
+ {
+ fill: parent
+ margins: 36 * screenScaleFactor // TODO: Theme!
+ bottomMargin: 56 * screenScaleFactor // TODO: Theme!
+ }
+ wrapMode: Text.WordWrap
+ text:
+ {
+ if (!printer || !printer.activePrintJob)
+ {
+ return ""
+ }
+ var topLine
+ if (materialsAreKnown(printer.activePrintJob))
+ {
+ topLine = catalog.i18ncp("@label", "The assigned printer, %1, requires the following configuration change:", "The assigned printer, %1, requires the following configuration changes:", printer.activePrintJob.configurationChanges.length).arg(printer.name)
+ }
+ else
+ {
+ topLine = catalog.i18nc("@label", "The printer %1 is assigned, but the job contains an unknown material configuration.").arg(printer.name)
+ }
+ var result = "
" + topLine +"
\n\n"
+ for (var i = 0; i < printer.activePrintJob.configurationChanges.length; i++)
+ {
+ var change = printer.activePrintJob.configurationChanges[i]
+ var text
+ switch (change.typeOfChange)
+ {
+ case "material_change":
+ text = catalog.i18nc("@label", "Change material %1 from %2 to %3.").arg(change.index + 1).arg(change.originName).arg(change.targetName)
+ break
+ case "material_insert":
+ text = catalog.i18nc("@label", "Load %3 as material %1 (This cannot be overridden).").arg(change.index + 1).arg(change.targetName)
+ break
+ case "print_core_change":
+ text = catalog.i18nc("@label", "Change print core %1 from %2 to %3.").arg(change.index + 1).arg(change.originName).arg(change.targetName)
+ break
+ case "buildplate_change":
+ text = catalog.i18nc("@label", "Change build plate to %1 (This cannot be overridden).").arg(formatBuildPlateType(change.target_name))
+ break
+ default:
+ text = "unknown"
+ }
+ result += "
" + text + "
\n\n"
+ }
+ var bottomLine = catalog.i18nc("@label", "Override will use the specified settings with the existing printer configuration. This may result in a failed print.")
+ result += "
" + bottomLine + "
\n\n"
+ return result
+ }
+ }
+ // Utils
+ function formatPrintJobName(name)
+ {
+ var extensions = [ ".gcode.gz", ".gz", ".gcode", ".ufp" ]
+ for (var i = 0; i < extensions.length; i++)
+ {
+ var extension = extensions[i]
+ if (name.slice(-extension.length) === extension)
+ {
+ name = name.substring(0, name.length - extension.length)
+ }
+ }
+ return name;
+ }
+ function materialsAreKnown(job)
+ {
+ var conf0 = job.configuration[0]
+ if (conf0 && !conf0.material.material)
+ {
+ return false
+ }
+ var conf1 = job.configuration[1]
+ if (conf1 && !conf1.material.material)
+ {
+ return false
+ }
+ return true
+ }
+ function formatBuildPlateType(buildPlateType)
+ {
+ var translationText = ""
+ switch (buildPlateType) {
+ case "glass":
+ translationText = catalog.i18nc("@label", "Glass")
+ break
+ case "aluminum":
+ translationText = catalog.i18nc("@label", "Aluminum")
+ break
+ default:
+ translationText = null
+ }
+ return translationText
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenu.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenu.qml
new file mode 100644
index 0000000000..771bd4b8cf
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenu.qml
@@ -0,0 +1,182 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.3
+import QtQuick.Controls 2.0
+import QtQuick.Dialogs 1.1
+import UM 1.3 as UM
+
+/**
+ * A MonitorInfoBlurb is an extension of the GenericPopUp used to show static information (vs. interactive context
+ * menus). It accepts some text (text), an item to link to to (target), and a specification of which side of the target
+ * to appear on (direction). It also sets the GenericPopUp's color to black, to differentiate itself from a menu.
+ */
+Item
+{
+ property alias target: popUp.target
+
+ property var printJob: null
+
+ GenericPopUp
+ {
+ id: popUp
+
+ // Which way should the pop-up point? Default is up, but will flip when required
+ direction: "up"
+
+ // Use dark grey for info blurbs and white for context menus
+ color: UM.Theme.getColor("monitor_context_menu")
+
+ contentItem: Item
+ {
+ id: contentWrapper
+ implicitWidth: childrenRect.width
+ implicitHeight: menuItems.height + UM.Theme.getSize("default_margin").height
+
+ Column
+ {
+ id: menuItems
+ width: 144 * screenScaleFactor
+
+ anchors
+ {
+ top: parent.top
+ topMargin: Math.floor(UM.Theme.getSize("default_margin").height / 2)
+ }
+
+ spacing: Math.floor(UM.Theme.getSize("default_margin").height / 2)
+
+ PrintJobContextMenuItem {
+ onClicked: {
+ sendToTopConfirmationDialog.visible = true;
+ popUp.close();
+ }
+ text: catalog.i18nc("@label", "Move to top");
+ visible: {
+ if (printJob && (printJob.state == "queued" || printJob.state == "error") && !isAssigned(printJob)) {
+ if (OutputDevice && OutputDevice.queuedPrintJobs[0]) {
+ return OutputDevice.queuedPrintJobs[0].key != printJob.key;
+ }
+ }
+ return false;
+ }
+ }
+
+ PrintJobContextMenuItem {
+ onClicked: {
+ deleteConfirmationDialog.visible = true;
+ popUp.close();
+ }
+ text: catalog.i18nc("@label", "Delete");
+ visible: {
+ if (!printJob) {
+ return false;
+ }
+ var states = ["queued", "error", "sent_to_printer"];
+ return states.indexOf(printJob.state) !== -1;
+ }
+ }
+
+ PrintJobContextMenuItem {
+ enabled: visible && !(printJob.state == "pausing" || printJob.state == "resuming");
+ onClicked: {
+ if (printJob.state == "paused") {
+ printJob.setState("print");
+ popUp.close();
+ return;
+ }
+ if (printJob.state == "printing") {
+ printJob.setState("pause");
+ popUp.close();
+ return;
+ }
+ }
+ text: {
+ if (!printJob) {
+ return "";
+ }
+ switch(printJob.state) {
+ case "paused":
+ return catalog.i18nc("@label", "Resume");
+ case "pausing":
+ return catalog.i18nc("@label", "Pausing...");
+ case "resuming":
+ return catalog.i18nc("@label", "Resuming...");
+ default:
+ catalog.i18nc("@label", "Pause");
+ }
+ }
+ visible: {
+ if (!printJob) {
+ return false;
+ }
+ var states = ["printing", "pausing", "paused", "resuming"];
+ return states.indexOf(printJob.state) !== -1;
+ }
+ }
+
+ PrintJobContextMenuItem {
+ enabled: visible && printJob.state !== "aborting";
+ onClicked: {
+ abortConfirmationDialog.visible = true;
+ popUp.close();
+ }
+ text: printJob && printJob.state == "aborting" ? catalog.i18nc("@label", "Aborting...") : catalog.i18nc("@label", "Abort");
+ visible: {
+ if (!printJob) {
+ return false;
+ }
+ var states = ["pre_print", "printing", "pausing", "paused", "resuming"];
+ return states.indexOf(printJob.state) !== -1;
+ }
+ }
+ }
+ }
+ }
+
+ MessageDialog {
+ id: sendToTopConfirmationDialog
+ Component.onCompleted: visible = false
+ icon: StandardIcon.Warning
+ onYes: OutputDevice.sendJobToTop(printJob.key)
+ standardButtons: StandardButton.Yes | StandardButton.No
+ text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to move %1 to the top of the queue?").arg(printJob.name) : ""
+ title: catalog.i18nc("@window:title", "Move print job to top")
+ }
+
+ MessageDialog {
+ id: deleteConfirmationDialog
+ Component.onCompleted: visible = false
+ icon: StandardIcon.Warning
+ onYes: OutputDevice.deleteJobFromQueue(printJob.key)
+ standardButtons: StandardButton.Yes | StandardButton.No
+ text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to delete %1?").arg(printJob.name) : ""
+ title: catalog.i18nc("@window:title", "Delete print job")
+ }
+
+ MessageDialog {
+ id: abortConfirmationDialog
+ Component.onCompleted: visible = false
+ icon: StandardIcon.Warning
+ onYes: printJob.setState("abort")
+ standardButtons: StandardButton.Yes | StandardButton.No
+ text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to abort %1?").arg(printJob.name) : ""
+ title: catalog.i18nc("@window:title", "Abort print")
+ }
+
+ function switchPopupState() {
+ popUp.visible ? popUp.close() : popUp.open()
+ }
+ function open() {
+ popUp.open()
+ }
+ function close() {
+ popUp.close()
+ }
+ function isAssigned(job) {
+ if (!job) {
+ return false;
+ }
+ return job.assignedPrinter ? true : false;
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenuButton.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenuButton.qml
new file mode 100644
index 0000000000..ba85802809
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenuButton.qml
@@ -0,0 +1,31 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.3
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+Button
+{
+ id: base
+ background: Rectangle
+ {
+ color: enabled ? UM.Theme.getColor("viewport_background") : "transparent"
+ height: base.height
+ opacity: base.down || base.hovered ? 1 : 0
+ radius: Math.round(0.5 * width)
+ width: base.width
+ }
+ contentItem: Label {
+ color: enabled ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled")
+ font.pixelSize: 32 * screenScaleFactor
+ horizontalAlignment: Text.AlignHCenter
+ text: base.text
+ verticalAlignment: Text.AlignVCenter
+ }
+ height: width
+ hoverEnabled: enabled
+ text: "\u22EE" //Unicode Three stacked points.
+ width: 36 * screenScaleFactor // TODO: Theme!
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorExtruderConfiguration.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorExtruderConfiguration.qml
new file mode 100644
index 0000000000..4079f23b0a
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorExtruderConfiguration.qml
@@ -0,0 +1,100 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+
+/**
+ * This component comprises a colored extruder icon, the material name, and the
+ * print core name. It is used by the MonitorPrinterConfiguration component with
+ * a sibling instance as well as a MonitorBuildplateConfiguration instance.
+ *
+ * NOTE: For most labels, a fixed height with vertical alignment is used to make
+ * layouts more deterministic (like the fixed-size textboxes used in original
+ * mock-ups). This is also a stand-in for CSS's 'line-height' property. Denoted
+ * with '// FIXED-LINE-HEIGHT:'.
+ */
+Item
+{
+ // The material color
+ property alias color: extruderIcon.color
+
+ // The extruder position; NOTE: Decent human beings count from 0
+ property alias position: extruderIcon.position
+
+ // The material name
+ property alias material: materialLabel.text
+
+ // The print core name (referred to as hotendID in Python)
+ property alias printCore: printCoreLabel.text
+
+ // Height is 2 x 18px labels, plus 4px spacing between them
+ height: 40 * screenScaleFactor // TODO: Theme!
+ width: childrenRect.width
+
+ MonitorIconExtruder
+ {
+ id: extruderIcon
+ color: UM.Theme.getColor("monitor_skeleton_loading")
+ position: 0
+ }
+
+ Rectangle
+ {
+ id: materialLabelWrapper
+ anchors
+ {
+ left: extruderIcon.right
+ leftMargin: 12 * screenScaleFactor // TODO: Theme!
+ }
+ color: materialLabel.visible > 0 ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading")
+ height: 18 * screenScaleFactor // TODO: Theme!
+ width: Math.max(materialLabel.contentWidth, 60 * screenScaleFactor) // TODO: Theme!
+ radius: 2 * screenScaleFactor // TODO: Theme!
+
+ Label
+ {
+ id: materialLabel
+
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("default") // 12pt, regular
+ text: ""
+ visible: text !== ""
+
+ // FIXED-LINE-HEIGHT:
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ Rectangle
+ {
+ id: printCoreLabelWrapper
+ anchors
+ {
+ left: materialLabelWrapper.left
+ bottom: parent.bottom
+ }
+ color: printCoreLabel.visible > 0 ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading")
+ height: 18 * screenScaleFactor // TODO: Theme!
+ width: Math.max(printCoreLabel.contentWidth, 36 * screenScaleFactor) // TODO: Theme!
+ radius: 2 * screenScaleFactor // TODO: Theme!
+
+ Label
+ {
+ id: printCoreLabel
+
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("default_bold") // 12pt, bold
+ text: ""
+ visible: text !== ""
+
+ // FIXED-LINE-HEIGHT:
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorIconExtruder.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorIconExtruder.qml
new file mode 100644
index 0000000000..c3e78317c5
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorIconExtruder.qml
@@ -0,0 +1,52 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+
+/**
+ * This component is a sort of "super icon" which includes a colored SVG image
+ * as well as the extruder position number. It is used in the the
+ * MonitorExtruderConfiguration component.
+ */
+Item
+{
+ // The material color
+ property alias color: icon.color
+
+ // The extruder position; NOTE: Decent human beings count from 0
+ property int position: 0
+
+ // The extruder icon size; NOTE: This shouldn't need to be changed
+ property int size: 32 * screenScaleFactor // TODO: Theme!
+
+ // THe extruder icon source; NOTE: This shouldn't need to be changed
+ property string iconSource: "../svg/icons/extruder.svg"
+
+ height: size
+ width: size
+
+ UM.RecolorImage
+ {
+ id: icon
+ anchors.fill: parent
+ source: iconSource
+ width: size
+ }
+
+ Label
+ {
+ id: positionLabel
+ font: UM.Theme.getFont("small")
+ color: UM.Theme.getColor("monitor_text_primary")
+ height: Math.round(size / 2)
+ horizontalAlignment: Text.AlignHCenter
+ text: position + 1
+ verticalAlignment: Text.AlignVCenter
+ width: Math.round(size / 2)
+ x: Math.round(size * 0.25)
+ y: Math.round(size * 0.15625)
+ visible: position >= 0
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorInfoBlurb.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorInfoBlurb.qml
new file mode 100644
index 0000000000..21000b8bff
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorInfoBlurb.qml
@@ -0,0 +1,53 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.3
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+
+/**
+ * A MonitorInfoBlurb is an extension of the GenericPopUp used to show static information (vs. interactive context
+ * menus). It accepts some text (text), an item to link to to (target), and a specification of which side of the target
+ * to appear on (direction). It also sets the GenericPopUp's color to black, to differentiate itself from a menu.
+ */
+Item
+{
+ property alias text: innerLabel.text
+ property alias target: popUp.target
+ property alias direction: popUp.direction
+
+ GenericPopUp
+ {
+ id: popUp
+
+ // Which way should the pop-up point? Default is up, but will flip when required
+ direction: "up"
+
+ // Use dark grey for info blurbs and white for context menus
+ color: UM.Theme.getColor("monitor_tooltip")
+
+ contentItem: Item
+ {
+ id: contentWrapper
+ implicitWidth: childrenRect.width
+ implicitHeight: innerLabel.contentHeight + 2 * innerLabel.padding
+ Label
+ {
+ id: innerLabel
+ padding: 12 * screenScaleFactor // TODO: Theme!
+ text: ""
+ wrapMode: Text.WordWrap
+ width: 240 * screenScaleFactor // TODO: Theme!
+ color: UM.Theme.getColor("monitor_tooltip_text")
+ font: UM.Theme.getFont("default")
+ }
+ }
+ }
+
+ function open() {
+ popUp.open()
+ }
+ function close() {
+ popUp.close()
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorItem.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorItem.qml
new file mode 100644
index 0000000000..41b3a93a7b
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorItem.qml
@@ -0,0 +1,45 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+Component {
+ Item {
+ height: maximumHeight;
+ width: maximumWidth;
+
+ Cura.NetworkMJPGImage {
+ id: cameraImage;
+ anchors {
+ horizontalCenter: parent.horizontalCenter;
+ verticalCenter: parent.verticalCenter;
+ }
+ Component.onCompleted: {
+ if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
+ cameraImage.start();
+ }
+ }
+ height: Math.floor((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth);
+ onVisibleChanged: {
+ if (visible) {
+ if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
+ cameraImage.start();
+ }
+ } else {
+ if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
+ cameraImage.stop();
+ }
+ }
+ }
+ source: {
+ if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
+ return OutputDevice.activePrinter.cameraUrl;
+ }
+ }
+ width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
+ z: 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml
new file mode 100644
index 0000000000..a23b8ab0d3
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml
@@ -0,0 +1,257 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+/**
+ * A Print Job Card is essentially just a filled-in Expandable Card item. All
+ * data within it is derived from being passed a printJob property.
+ *
+ * NOTE: For most labels, a fixed height with vertical alignment is used to make
+ * layouts more deterministic (like the fixed-size textboxes used in original
+ * mock-ups). This is also a stand-in for CSS's 'line-height' property. Denoted
+ * with '// FIXED-LINE-HEIGHT:'.
+ */
+Item
+{
+ id: base
+
+ // The print job which all other data is derived from
+ property var printJob: null
+
+ // If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future
+ // they might not need to though.
+ property bool cloudConnection: Cura.MachineManager.activeMachineIsUsingCloudConnection
+
+ width: parent.width
+ height: childrenRect.height
+
+ ExpandableCard
+ {
+ enabled: printJob != null
+ borderColor: printJob && printJob.configurationChanges.length !== 0 ? UM.Theme.getColor("warning") : UM.Theme.getColor("monitor_card_border")
+ headerItem: Row
+ {
+ height: 48 * screenScaleFactor // TODO: Theme!
+ anchors.left: parent.left
+ anchors.leftMargin: 24 * screenScaleFactor // TODO: Theme!
+ spacing: 18 * screenScaleFactor // TODO: Theme!
+
+ MonitorPrintJobPreview
+ {
+ printJob: base.printJob
+ size: 32 * screenScaleFactor // TODO: Theme!
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Item
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ height: 18 * screenScaleFactor // TODO: Theme!
+ width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
+ Rectangle
+ {
+ color: UM.Theme.getColor("monitor_skeleton_loading")
+ width: Math.round(parent.width / 2)
+ height: parent.height
+ visible: !printJob
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ }
+ Label
+ {
+ text: printJob && printJob.name ? printJob.name : ""
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ visible: printJob
+
+ // FIXED-LINE-HEIGHT:
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ Item
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ height: 18 * screenScaleFactor // TODO: Theme!
+ width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
+ Rectangle
+ {
+ color: UM.Theme.getColor("monitor_skeleton_loading")
+ width: Math.round(parent.width / 3)
+ height: parent.height
+ visible: !printJob
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ }
+ Label
+ {
+ text: printJob ? OutputDevice.formatDuration(printJob.timeTotal) : ""
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ visible: printJob
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ Item
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ height: 18 * screenScaleFactor // TODO: This should be childrenRect.height but QML throws warnings
+ width: childrenRect.width
+
+ Rectangle
+ {
+ color: UM.Theme.getColor("monitor_skeleton_loading")
+ width: 72 * screenScaleFactor // TODO: Theme!
+ height: parent.height
+ visible: !printJob
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ }
+
+ Label
+ {
+ id: printerAssignmentLabel
+ anchors.verticalCenter: parent.verticalCenter
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ text: {
+ if (printJob !== null) {
+ if (printJob.assignedPrinter == null)
+ {
+ if (printJob.state == "error")
+ {
+ return catalog.i18nc("@label", "Unavailable printer")
+ }
+ return catalog.i18nc("@label", "First available")
+ }
+ return printJob.assignedPrinter.name
+ }
+ return ""
+ }
+ visible: printJob
+ width: 120 * screenScaleFactor // TODO: Theme!
+
+ // FIXED-LINE-HEIGHT:
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Row
+ {
+ id: printerFamilyPills
+ anchors
+ {
+ left: printerAssignmentLabel.right;
+ leftMargin: 12 // TODO: Theme!
+ verticalCenter: parent.verticalCenter
+ }
+ height: childrenRect.height
+ spacing: 6 // TODO: Theme!
+ visible: printJob
+
+ Repeater
+ {
+ id: compatiblePills
+ delegate: MonitorPrinterPill
+ {
+ text: modelData
+ }
+ model: printJob ? printJob.compatibleMachineFamilies : []
+ }
+ }
+ }
+ }
+ drawerItem: Row
+ {
+ anchors
+ {
+ left: parent.left
+ leftMargin: 74 * screenScaleFactor // TODO: Theme!
+ }
+ height: 108 * screenScaleFactor // TODO: Theme!
+ spacing: 18 * screenScaleFactor // TODO: Theme!
+
+ MonitorPrinterConfiguration
+ {
+ id: printerConfiguration
+ anchors.verticalCenter: parent.verticalCenter
+ buildplate: catalog.i18nc("@label", "Glass")
+ configurations:
+ [
+ base.printJob.configuration.extruderConfigurations[0],
+ base.printJob.configuration.extruderConfigurations[1]
+ ]
+ height: 72 * screenScaleFactor // TODO: Theme!
+ }
+ Label {
+ text: printJob && printJob.owner ? printJob.owner : ""
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ anchors.top: printerConfiguration.top
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+ }
+
+ MonitorContextMenuButton
+ {
+ id: contextMenuButton
+ anchors
+ {
+ right: parent.right
+ rightMargin: 8 * screenScaleFactor // TODO: Theme!
+ top: parent.top
+ topMargin: 8 * screenScaleFactor // TODO: Theme!
+ }
+ width: 32 * screenScaleFactor // TODO: Theme!
+ height: 32 * screenScaleFactor // TODO: Theme!
+ enabled: !cloudConnection
+ onClicked: enabled ? contextMenu.switchPopupState() : {}
+ visible:
+ {
+ if (!printJob) {
+ return false
+ }
+ var states = ["queued", "error", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"]
+ return states.indexOf(printJob.state) !== -1
+ }
+ }
+
+ MonitorContextMenu
+ {
+ id: contextMenu
+ printJob: base.printJob ? base.printJob : null
+ target: contextMenuButton
+ }
+
+ // For cloud printing, add this mouse area over the disabled contextButton to indicate that it's not available
+ MouseArea
+ {
+ id: contextMenuDisabledButtonArea
+ anchors.fill: contextMenuButton
+ hoverEnabled: contextMenuButton.visible && !contextMenuButton.enabled
+ onEntered: contextMenuDisabledInfo.open()
+ onExited: contextMenuDisabledInfo.close()
+ enabled: !contextMenuButton.enabled
+ }
+
+ MonitorInfoBlurb
+ {
+ id: contextMenuDisabledInfo
+ text: catalog.i18nc("@info", "These options are not available because you are monitoring a cloud printer.")
+ target: contextMenuButton
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml
new file mode 100644
index 0000000000..a392571757
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml
@@ -0,0 +1,101 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+
+// TODO: Documentation!
+Item
+{
+ id: printJobPreview
+
+ property var printJob: null
+ property var size: 256
+
+ width: size
+ height: size
+
+ Rectangle
+ {
+ anchors.fill: parent
+ color: printJob ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading")
+ radius: 8 // TODO: Theme!
+ Image
+ {
+ id: previewImage
+ anchors.fill: parent
+ opacity:
+ {
+ if (printJob && (printJob.state == "error" || printJob.configurationChanges.length > 0 || !printJob.isActive))
+ {
+ return 0.5
+ }
+ return 1.0
+ }
+ source: printJob ? printJob.previewImageUrl : ""
+ }
+ }
+
+
+ UM.RecolorImage
+ {
+ id: ultiBotImage
+
+ anchors.centerIn: printJobPreview
+ color: UM.Theme.getColor("monitor_placeholder_image")
+ height: printJobPreview.height
+ source: "../svg/ultibot.svg"
+ sourceSize
+ {
+ height: height
+ width: width
+ }
+ /* Since print jobs ALWAYS have an image url, we have to check if that image URL errors or
+ not in order to determine if we show the placeholder (ultibot) image instead. */
+ visible: printJob && previewImage.status == Image.Error
+ width: printJobPreview.width
+ }
+
+ UM.RecolorImage
+ {
+ id: overlayIcon
+ anchors.centerIn: printJobPreview
+ color: UM.Theme.getColor("monitor_image_overlay")
+ height: 0.5 * printJobPreview.height
+ source:
+ {
+ if (!printJob)
+ {
+ return ""
+ }
+ if (printJob.configurationChanges.length > 0)
+ {
+ return "../svg/warning-icon.svg"
+ }
+ switch(printJob.state)
+ {
+ case "error":
+ return "../svg/aborted-icon.svg"
+ case "wait_cleanup":
+ return printJob.timeTotal > printJob.timeElapsed ? "../svg/aborted-icon.svg" : ""
+ case "pausing":
+ return "../svg/paused-icon.svg"
+ case "paused":
+ return "../svg/paused-icon.svg"
+ case "resuming":
+ return "../svg/paused-icon.svg"
+ default:
+ return ""
+ }
+ return ""
+ }
+ sourceSize
+ {
+ height: height
+ width: width
+ }
+ visible: source != ""
+ width: 0.5 * printJobPreview.width
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml
new file mode 100644
index 0000000000..2ba70268b2
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml
@@ -0,0 +1,119 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.3
+import QtQuick.Controls.Styles 1.3
+import QtQuick.Controls 1.4
+import UM 1.3 as UM
+
+/**
+ * NOTE: For most labels, a fixed height with vertical alignment is used to make
+ * layouts more deterministic (like the fixed-size textboxes used in original
+ * mock-ups). This is also a stand-in for CSS's 'line-height' property. Denoted
+ * with '// FIXED-LINE-HEIGHT:'.
+ */
+Item
+{
+ id: base
+
+ // The print job which all other information is dervied from
+ property var printJob: null
+
+ width: childrenRect.width
+ height: 18 * screenScaleFactor // TODO: Theme!
+
+ ProgressBar
+ {
+ id: progressBar
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ }
+ value: printJob ? printJob.progress : 0
+ style: ProgressBarStyle
+ {
+ background: Rectangle
+ {
+ color: UM.Theme.getColor("monitor_progress_bar_empty")
+ implicitHeight: visible ? 12 * screenScaleFactor : 0 // TODO: Theme!
+ implicitWidth: 180 * screenScaleFactor // TODO: Theme!
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ }
+ progress: Rectangle
+ {
+ id: progressItem;
+ color: printJob && printJob.isActive ? UM.Theme.getColor("monitor_progress_bar_fill") : UM.Theme.getColor("monitor_progress_bar_deactive")
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ }
+ }
+ }
+ Label
+ {
+ id: percentLabel
+ anchors
+ {
+ left: progressBar.right
+ leftMargin: 18 * screenScaleFactor // TODO: Theme!
+ }
+ text: printJob ? Math.round(printJob.progress * 100) + "%" : "0%"
+ color: printJob && printJob.isActive ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled")
+ width: contentWidth
+ font: UM.Theme.getFont("medium") // 14pt, regular
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+ Label
+ {
+ id: statusLabel
+ anchors
+ {
+ left: percentLabel.right
+ leftMargin: 18 * screenScaleFactor // TODO: Theme!
+ }
+ color: UM.Theme.getColor("monitor_text_primary")
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ text:
+ {
+ if (!printJob)
+ {
+ return ""
+ }
+ switch (printJob.state)
+ {
+ case "wait_cleanup":
+ if (printJob.timeTotal > printJob.timeElapsed)
+ {
+ return catalog.i18nc("@label:status", "Aborted")
+ }
+ return catalog.i18nc("@label:status", "Finished")
+ case "finished":
+ return catalog.i18nc("@label:status", "Finished")
+ case "sent_to_printer":
+ return catalog.i18nc("@label:status", "Preparing...")
+ case "pre_print":
+ return catalog.i18nc("@label:status", "Preparing...")
+ case "aborting": // NOTE: Doesn't exist but maybe should someday
+ return catalog.i18nc("@label:status", "Aborting...")
+ case "aborted": // NOTE: Unused, see above
+ return catalog.i18nc("@label:status", "Aborted")
+ case "pausing":
+ return catalog.i18nc("@label:status", "Pausing...")
+ case "paused":
+ return catalog.i18nc("@label:status", "Paused")
+ case "resuming":
+ return catalog.i18nc("@label:status", "Resuming...")
+ case "queued":
+ return catalog.i18nc("@label:status", "Action required")
+ default:
+ return catalog.i18nc("@label:status", "Finishes %1 at %2".arg(OutputDevice.getDateCompleted( printJob.timeRemaining )).arg(OutputDevice.getTimeCompleted( printJob.timeRemaining )))
+ }
+ }
+ width: contentWidth
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml
new file mode 100644
index 0000000000..8c63e1ef1a
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml
@@ -0,0 +1,453 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.3
+import QtQuick.Controls 2.0
+import QtQuick.Dialogs 1.1
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+/**
+ * A Printer Card is has two main components: the printer portion and the print job portion, the latter being paired in
+ * the UI when a print job is paired a printer in-cluster.
+ *
+ * NOTE: For most labels, a fixed height with vertical alignment is used to make layouts more deterministic (like the
+ * fixed-size textboxes used in original mock-ups). This is also a stand-in for CSS's 'line-height' property. Denoted
+ * with '// FIXED-LINE-HEIGHT:'.
+ */
+Item
+{
+ id: base
+
+ // The printer which all printer data is derived from
+ property var printer: null
+
+ property var borderSize: 1 * screenScaleFactor // TODO: Theme, and remove from here
+
+ // If the printer card's controls are enabled. This is used by the carousel to prevent opening the context menu or
+ // camera while the printer card is not "in focus"
+ property var enabled: true
+
+ // If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future
+ // they might not need to though.
+ property bool cloudConnection: Cura.MachineManager.activeMachineIsUsingCloudConnection
+
+ width: 834 * screenScaleFactor // TODO: Theme!
+ height: childrenRect.height
+
+ Rectangle
+ {
+ id: background
+ anchors.fill: parent
+ color: UM.Theme.getColor("monitor_card_background")
+ border
+ {
+ color: UM.Theme.getColor("monitor_card_border")
+ width: borderSize // TODO: Remove once themed
+ }
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ }
+
+ // Printer portion
+ Item
+ {
+ id: printerInfo
+
+ width: parent.width
+ height: 144 * screenScaleFactor // TODO: Theme!
+
+ Row
+ {
+ anchors
+ {
+ left: parent.left
+ leftMargin: 36 * screenScaleFactor // TODO: Theme!
+ verticalCenter: parent.verticalCenter
+ }
+ spacing: 18 * screenScaleFactor // TODO: Theme!
+
+ Rectangle
+ {
+ id: printerImage
+ width: 108 * screenScaleFactor // TODO: Theme!
+ height: 108 * screenScaleFactor // TODO: Theme!
+ color: printer ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading")
+ radius: 8 // TODO: Theme!
+ Image
+ {
+ anchors.fill: parent
+ fillMode: Image.PreserveAspectFit
+ source: printer ? "../png/" + printer.type + ".png" : ""
+ mipmap: true
+ }
+ }
+
+
+ Item
+ {
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ }
+ width: 180 * screenScaleFactor // TODO: Theme!
+ height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
+
+ Rectangle
+ {
+ id: printerNameLabel
+ color: printer ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading")
+ height: 18 * screenScaleFactor // TODO: Theme!
+ width: parent.width
+ radius: 2 * screenScaleFactor // TODO: Theme!
+
+ Label
+ {
+ text: printer && printer.name ? printer.name : ""
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("large") // 16pt, bold
+ width: parent.width
+ visible: printer
+
+ // FIXED-LINE-HEIGHT:
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ Rectangle
+ {
+ color: UM.Theme.getColor("monitor_skeleton_loading")
+ height: 18 * screenScaleFactor // TODO: Theme!
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ visible: !printer
+ width: 48 * screenScaleFactor // TODO: Theme!
+ }
+ MonitorPrinterPill
+ {
+ id: printerFamilyPill
+ anchors
+ {
+ top: printerNameLabel.bottom
+ topMargin: 6 * screenScaleFactor // TODO: Theme!
+ left: printerNameLabel.left
+ }
+ text: printer ? printer.type : ""
+ }
+ }
+
+ MonitorPrinterConfiguration
+ {
+ id: printerConfiguration
+ anchors.verticalCenter: parent.verticalCenter
+ buildplate: printer ? "Glass" : null // 'Glass' as a default
+ configurations:
+ {
+ var configs = []
+ if (printer)
+ {
+ configs.push(printer.printerConfiguration.extruderConfigurations[0])
+ configs.push(printer.printerConfiguration.extruderConfigurations[1])
+ }
+ else
+ {
+ configs.push(null, null)
+ }
+ return configs
+ }
+ height: 72 * screenScaleFactor // TODO: Theme!te theRect's x property
+ }
+ }
+
+ MonitorContextMenuButton
+ {
+ id: contextMenuButton
+ anchors
+ {
+ right: parent.right
+ rightMargin: 12 * screenScaleFactor // TODO: Theme!
+ top: parent.top
+ topMargin: 12 * screenScaleFactor // TODO: Theme!
+ }
+ width: 36 * screenScaleFactor // TODO: Theme!
+ height: 36 * screenScaleFactor // TODO: Theme!
+ enabled: !cloudConnection
+
+ onClicked: enabled ? contextMenu.switchPopupState() : {}
+ visible:
+ {
+ if (!printer || !printer.activePrintJob) {
+ return false
+ }
+ var states = ["queued", "error", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"]
+ return states.indexOf(printer.activePrintJob.state) !== -1
+ }
+ }
+
+ MonitorContextMenu
+ {
+ id: contextMenu
+ printJob: printer ? printer.activePrintJob : null
+ target: contextMenuButton
+ }
+
+ // For cloud printing, add this mouse area over the disabled contextButton to indicate that it's not available
+ MouseArea
+ {
+ id: contextMenuDisabledButtonArea
+ anchors.fill: contextMenuButton
+ hoverEnabled: contextMenuButton.visible && !contextMenuButton.enabled
+ onEntered: contextMenuDisabledInfo.open()
+ onExited: contextMenuDisabledInfo.close()
+ enabled: !contextMenuButton.enabled
+ }
+
+ MonitorInfoBlurb
+ {
+ id: contextMenuDisabledInfo
+ text: catalog.i18nc("@info", "These options are not available because you are monitoring a cloud printer.")
+ target: contextMenuButton
+ }
+
+ CameraButton
+ {
+ id: cameraButton
+ anchors
+ {
+ right: parent.right
+ rightMargin: 20 * screenScaleFactor // TODO: Theme!
+ bottom: parent.bottom
+ bottomMargin: 20 * screenScaleFactor // TODO: Theme!
+ }
+ iconSource: "../svg/icons/camera.svg"
+ enabled: !cloudConnection
+ visible: printer
+ }
+
+ // For cloud printing, add this mouse area over the disabled cameraButton to indicate that it's not available
+ MouseArea
+ {
+ id: cameraDisabledButtonArea
+ anchors.fill: cameraButton
+ hoverEnabled: cameraButton.visible && !cameraButton.enabled
+ onEntered: cameraDisabledInfo.open()
+ onExited: cameraDisabledInfo.close()
+ enabled: !cameraButton.enabled
+ }
+
+ MonitorInfoBlurb
+ {
+ id: cameraDisabledInfo
+ text: catalog.i18nc("@info", "The webcam is not available because you are monitoring a cloud printer.")
+ target: cameraButton
+ }
+ }
+
+
+ // Divider
+ Rectangle
+ {
+ anchors
+ {
+ top: printJobInfo.top
+ left: printJobInfo.left
+ right: printJobInfo.right
+ }
+ height: borderSize // Remove once themed
+ color: background.border.color
+ }
+
+ // Print job portion
+ Rectangle
+ {
+ id: printJobInfo
+ anchors
+ {
+ top: printerInfo.bottom
+ topMargin: -borderSize * screenScaleFactor // TODO: Theme!
+ }
+ border
+ {
+ color: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 ? UM.Theme.getColor("warning") : "transparent" // TODO: Theme!
+ width: borderSize // TODO: Remove once themed
+ }
+ color: "transparent" // TODO: Theme!
+ height: 84 * screenScaleFactor + borderSize // TODO: Remove once themed
+ width: parent.width
+
+ Row
+ {
+ anchors
+ {
+ fill: parent
+ topMargin: 12 * screenScaleFactor + borderSize // TODO: Theme!
+ bottomMargin: 12 * screenScaleFactor // TODO: Theme!
+ leftMargin: 36 * screenScaleFactor // TODO: Theme!
+ }
+ height: childrenRect.height
+ spacing: 18 * screenScaleFactor // TODO: Theme!
+
+ Label
+ {
+ id: printerStatus
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ }
+ color: printer ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled")
+ font: UM.Theme.getFont("large_bold") // 16pt, bold
+ text: {
+ if (!printer) {
+ return catalog.i18nc("@label:status", "Loading...")
+ }
+ if (printer && printer.state == "disabled")
+ {
+ return catalog.i18nc("@label:status", "Unavailable")
+ }
+ if (printer && printer.state == "unreachable")
+ {
+ return catalog.i18nc("@label:status", "Unreachable")
+ }
+ if (printer && !printer.activePrintJob && printer.state == "idle")
+ {
+ return catalog.i18nc("@label:status", "Idle")
+ }
+ return ""
+ }
+ visible: text !== ""
+ }
+
+ Item
+ {
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ }
+ width: printerImage.width
+ height: 60 * screenScaleFactor // TODO: Theme!
+ MonitorPrintJobPreview
+ {
+ anchors.centerIn: parent
+ printJob: printer ? printer.activePrintJob : null
+ size: parent.height
+ }
+ visible: printer && printer.activePrintJob && !printerStatus.visible
+ }
+
+ Item
+ {
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ }
+ width: 180 * screenScaleFactor // TODO: Theme!
+ height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
+ visible: printer && printer.activePrintJob && !printerStatus.visible
+
+ Label
+ {
+ id: printerJobNameLabel
+ color: printer && printer.activePrintJob && printer.activePrintJob.isActive ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("large") // 16pt, bold
+ text: printer && printer.activePrintJob ? printer.activePrintJob.name : catalog.i18nc("@label", "Untitled")
+ width: parent.width
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Label
+ {
+ id: printerJobOwnerLabel
+ anchors
+ {
+ top: printerJobNameLabel.bottom
+ topMargin: 6 * screenScaleFactor // TODO: Theme!
+ left: printerJobNameLabel.left
+ }
+ color: printer && printer.activePrintJob && printer.activePrintJob.isActive ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("default") // 12pt, regular
+ text: printer && printer.activePrintJob ? printer.activePrintJob.owner : catalog.i18nc("@label", "Anonymous")
+ width: parent.width
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ MonitorPrintJobProgressBar
+ {
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ }
+ printJob: printer && printer.activePrintJob
+ visible: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length === 0 && !printerStatus.visible
+ }
+
+ Label
+ {
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ }
+ font: UM.Theme.getFont("default")
+ text: catalog.i18nc("@label:status", "Requires configuration changes")
+ visible: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 && !printerStatus.visible
+ color: UM.Theme.getColor("monitor_text_primary")
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ Button
+ {
+ id: detailsButton
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ right: parent.right
+ rightMargin: 18 * screenScaleFactor // TODO: Theme!
+ }
+ background: Rectangle
+ {
+ color: UM.Theme.getColor("monitor_secondary_button_shadow")
+ radius: 2 * screenScaleFactor // Todo: Theme!
+ Rectangle
+ {
+ anchors.fill: parent
+ anchors.bottomMargin: 2 * screenScaleFactor // TODO: Theme!
+ color: detailsButton.hovered ? UM.Theme.getColor("monitor_secondary_button_hover") : UM.Theme.getColor("monitor_secondary_button")
+ radius: 2 * screenScaleFactor // Todo: Theme!
+ }
+ }
+ contentItem: Label
+ {
+ anchors.fill: parent
+ anchors.bottomMargin: 2 * screenScaleFactor // TODO: Theme!
+ color: UM.Theme.getColor("monitor_secondary_button_text")
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ text: catalog.i18nc("@action:button","Details");
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ height: 18 * screenScaleFactor // TODO: Theme!
+ }
+ implicitHeight: 32 * screenScaleFactor // TODO: Theme!
+ implicitWidth: 96 * screenScaleFactor // TODO: Theme!
+ visible: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 && !printerStatus.visible
+ onClicked: base.enabled ? overrideConfirmationDialog.open() : {}
+ }
+ }
+
+ MonitorConfigOverrideDialog
+ {
+ id: overrideConfirmationDialog
+ printer: base.printer
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterConfiguration.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterConfiguration.qml
new file mode 100644
index 0000000000..dbe085e18e
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterConfiguration.qml
@@ -0,0 +1,58 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import UM 1.3 as UM
+
+/**
+ * The MonitorPrinterConfiguration accepts 2 configuration objects as input and
+ * applies them to a MonitorBuildplateConfiguration instance and two instances
+ * of MonitorExtruderConfiguration. It's used in both the MonitorPrintJobCard
+ * component as well as the MonitorPrinterCard component.
+ */
+Item
+{
+ id: base
+
+ // Extracted buildplate configuration
+ property alias buildplate: buildplateConfig.buildplate
+
+ // Array of extracted extruder configurations
+ property var configurations: [null,null]
+
+ // Default size, but should be stretched to fill parent
+ height: 72 * parent.height
+ width: 450 * screenScaleFactor // TODO: Theme!
+
+ Row
+ {
+ id: extruderConfigurationRow
+ spacing: 18 * screenScaleFactor // TODO: Theme!
+
+ Repeater
+ {
+ id: extruderConfigurationRepeater
+ model: configurations
+
+ MonitorExtruderConfiguration
+ {
+ color: modelData && modelData.activeMaterial ? modelData.activeMaterial.color : UM.Theme.getColor("monitor_skeleton_loading")
+ material: modelData && modelData.activeMaterial ? modelData.activeMaterial.name : ""
+ position: modelData && typeof(modelData.position) === "number" ? modelData.position : -1 // Use negative one to create empty extruder number
+ printCore: modelData ? modelData.hotendID : ""
+
+ // Keep things responsive!
+ width: Math.floor((base.width - (configurations.length - 1) * extruderConfigurationRow.spacing) / configurations.length)
+ }
+
+ }
+ }
+
+ MonitorBuildplateConfiguration
+ {
+ id: buildplateConfig
+ anchors.bottom: parent.bottom
+ buildplate: null
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterPill.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterPill.qml
new file mode 100644
index 0000000000..2aeecd5a92
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterPill.qml
@@ -0,0 +1,47 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+import UM 1.2 as UM
+
+/**
+ * A MonitorPrinterPill is a blue-colored tag indicating which printers a print
+ * job is compatible with. It is used by the MonitorPrintJobCard component.
+ */
+Item
+{
+ // The printer name
+ property var text: ""
+ property var tagText: {
+ switch(text) {
+ case "Ultimaker 3":
+ return "UM 3"
+ case "Ultimaker 3 Extended":
+ return "UM 3 EXT"
+ case "Ultimaker S5":
+ return "UM S5"
+ default:
+ return text
+ }
+ }
+
+ implicitHeight: 18 * screenScaleFactor // TODO: Theme!
+ implicitWidth: Math.max(printerNameLabel.contentWidth + 12 * screenScaleFactor, 36 * screenScaleFactor) // TODO: Theme!
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ color: printerNameLabel.visible ? UM.Theme.getColor("monitor_printer_family_tag") : UM.Theme.getColor("monitor_skeleton_loading")
+ radius: 2 * screenScaleFactor // TODO: Theme!
+ }
+
+ Label {
+ id: printerNameLabel
+ anchors.centerIn: parent
+ color: UM.Theme.getColor("monitor_text_primary")
+ text: tagText
+ font.pointSize: 10 // TODO: Theme!
+ visible: text !== ""
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorQueue.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorQueue.qml
new file mode 100644
index 0000000000..6eaff20f71
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorQueue.qml
@@ -0,0 +1,266 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+/**
+ * This component contains the print job queue, extracted from the primary
+ * MonitorStage.qml file not for reusability but simply to keep it lean and more
+ * readable.
+ */
+Item
+{
+ // If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future
+ // they might not need to though.
+ property bool cloudConnection: Cura.MachineManager.activeMachineIsUsingCloudConnection
+
+ Label
+ {
+ id: queuedLabel
+ anchors
+ {
+ left: queuedPrintJobs.left
+ top: parent.top
+ }
+ color: UM.Theme.getColor("monitor_text_primary")
+ font: UM.Theme.getFont("large")
+ text: catalog.i18nc("@label", "Queued")
+ }
+
+ Item
+ {
+ id: manageQueueLabel
+ anchors
+ {
+ right: queuedPrintJobs.right
+ verticalCenter: queuedLabel.verticalCenter
+ }
+ height: 18 * screenScaleFactor // TODO: Theme!
+ width: childrenRect.width
+ visible: !cloudConnection
+
+ UM.RecolorImage
+ {
+ id: externalLinkIcon
+ anchors.verticalCenter: manageQueueLabel.verticalCenter
+ color: UM.Theme.getColor("monitor_text_link")
+ source: UM.Theme.getIcon("external_link")
+ width: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
+ height: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
+ }
+ Label
+ {
+ id: manageQueueText
+ anchors
+ {
+ left: externalLinkIcon.right
+ leftMargin: 6 * screenScaleFactor // TODO: Theme!
+ verticalCenter: externalLinkIcon.verticalCenter
+ }
+ color: UM.Theme.getColor("monitor_text_link")
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ linkColor: UM.Theme.getColor("monitor_text_link")
+ text: catalog.i18nc("@label link to connect manager", "Go to Cura Connect")
+ renderType: Text.NativeRendering
+ }
+ }
+
+ MouseArea
+ {
+ anchors.fill: manageQueueLabel
+ enabled: !cloudConnection
+ hoverEnabled: !cloudConnection
+ onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
+ onEntered:
+ {
+ manageQueueText.font.underline = true
+ }
+ onExited:
+ {
+ manageQueueText.font.underline = false
+ }
+ }
+
+ Row
+ {
+ id: printJobQueueHeadings
+ anchors
+ {
+ left: queuedPrintJobs.left
+ leftMargin: 6 * screenScaleFactor // TODO: Theme!
+ top: queuedLabel.bottom
+ topMargin: 24 * screenScaleFactor // TODO: Theme!
+ }
+ spacing: 18 * screenScaleFactor // TODO: Theme!
+
+ Label
+ {
+ text: catalog.i18nc("@label", "Print jobs")
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ anchors.verticalCenter: parent.verticalCenter
+ width: 284 * screenScaleFactor // TODO: Theme! (Should match column size)
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Label
+ {
+ text: catalog.i18nc("@label", "Total print time")
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ anchors.verticalCenter: parent.verticalCenter
+ width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Label
+ {
+ text: catalog.i18nc("@label", "Waiting for")
+ color: UM.Theme.getColor("monitor_text_primary")
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ anchors.verticalCenter: parent.verticalCenter
+ width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
+
+ // FIXED-LINE-HEIGHT:
+ height: 18 * screenScaleFactor // TODO: Theme!
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ ScrollView
+ {
+ id: queuedPrintJobs
+ anchors
+ {
+ bottom: parent.bottom
+ horizontalCenter: parent.horizontalCenter
+ top: printJobQueueHeadings.bottom
+ topMargin: 12 * screenScaleFactor // TODO: Theme!
+ }
+ style: UM.Theme.styles.scrollview
+ width: parent.width
+
+ ListView
+ {
+ id: printJobList
+ anchors.fill: parent
+ delegate: MonitorPrintJobCard
+ {
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ }
+ printJob: modelData
+ }
+ model:
+ {
+ // When printing over the cloud we don't recieve print jobs until there is one, so
+ // unless there's at least one print job we'll be stuck with skeleton loading
+ // indefinitely.
+ if (Cura.MachineManager.activeMachineIsUsingCloudConnection || OutputDevice.receivedPrintJobs)
+ {
+ return OutputDevice.queuedPrintJobs
+ }
+ return [null, null]
+ }
+ spacing: 6 // TODO: Theme!
+ }
+ }
+
+ Rectangle
+ {
+ anchors
+ {
+ horizontalCenter: parent.horizontalCenter
+ top: printJobQueueHeadings.bottom
+ topMargin: 12 * screenScaleFactor // TODO: Theme!
+ }
+ height: 48 * screenScaleFactor // TODO: Theme!
+ width: parent.width
+ color: UM.Theme.getColor("monitor_card_background")
+ border.color: UM.Theme.getColor("monitor_card_border")
+ radius: 2 * screenScaleFactor // TODO: Theme!
+
+ visible: printJobList.model.length == 0
+
+ Row
+ {
+ anchors
+ {
+ left: parent.left
+ leftMargin: 18 * screenScaleFactor // TODO: Theme!
+ verticalCenter: parent.verticalCenter
+ }
+ spacing: 18 * screenScaleFactor // TODO: Theme!
+ height: 18 * screenScaleFactor // TODO: Theme!
+
+ Label
+ {
+ text: "All jobs are printed."
+ color: UM.Theme.getColor("monitor_text_primary")
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ }
+
+ Item
+ {
+ id: viewPrintHistoryLabel
+
+ height: 18 * screenScaleFactor // TODO: Theme!
+ width: childrenRect.width
+
+ UM.RecolorImage
+ {
+ id: printHistoryIcon
+ anchors.verticalCenter: parent.verticalCenter
+ color: UM.Theme.getColor("monitor_text_link")
+ source: UM.Theme.getIcon("external_link")
+ width: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
+ height: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
+ }
+ Label
+ {
+ id: viewPrintHistoryText
+ anchors
+ {
+ left: printHistoryIcon.right
+ leftMargin: 6 * screenScaleFactor // TODO: Theme!
+ verticalCenter: printHistoryIcon.verticalCenter
+ }
+ color: UM.Theme.getColor("monitor_text_link")
+ font: UM.Theme.getFont("medium") // 14pt, regular
+ linkColor: UM.Theme.getColor("monitor_text_link")
+ text: catalog.i18nc("@label link to connect manager", "View print history")
+ renderType: Text.NativeRendering
+ }
+ MouseArea
+ {
+ anchors.fill: parent
+ hoverEnabled: true
+ onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
+ onEntered:
+ {
+ viewPrintHistoryText.font.underline = true
+ }
+ onExited:
+ {
+ viewPrintHistoryText.font.underline = false
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorStage.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorStage.qml
new file mode 100644
index 0000000000..59cbda7172
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorStage.qml
@@ -0,0 +1,77 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+import QtGraphicalEffects 1.0
+
+// This is the root component for the monitor stage.
+Component
+{
+ Rectangle
+ {
+ id: monitorFrame
+
+ height: maximumHeight
+ onVisibleChanged:
+ {
+ if (monitorFrame != null && !monitorFrame.visible)
+ {
+ OutputDevice.setActiveCameraUrl("")
+ }
+ }
+ width: maximumWidth
+ color: UM.Theme.getColor("monitor_stage_background")
+
+ // Enable keyboard navigation. NOTE: This is done here so that we can also potentially
+ // forward to the queue items in the future. (Deleting selected print job, etc.)
+ Keys.forwardTo: carousel
+ Component.onCompleted: forceActiveFocus()
+
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
+
+ Item
+ {
+ id: printers
+ anchors
+ {
+ top: parent.top
+ topMargin: 48 * screenScaleFactor // TODO: Theme!
+ }
+ width: parent.width
+ height: 264 * screenScaleFactor // TODO: Theme!
+ MonitorCarousel
+ {
+ id: carousel
+ printers: OutputDevice.receivedPrintJobs ? OutputDevice.printers : [null]
+ }
+ }
+
+ MonitorQueue
+ {
+ id: queue
+ width: Math.min(834 * screenScaleFactor, maximumWidth)
+ anchors
+ {
+ bottom: parent.bottom
+ horizontalCenter: parent.horizontalCenter
+ top: printers.bottom
+ topMargin: 48 * screenScaleFactor // TODO: Theme!
+ }
+ }
+
+ PrinterVideoStream
+ {
+ anchors.fill: parent
+ cameraUrl: OutputDevice.activeCameraUrl
+ visible: OutputDevice.activeCameraUrl != ""
+ }
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenuItem.qml b/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenuItem.qml
new file mode 100644
index 0000000000..67c82db320
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenuItem.qml
@@ -0,0 +1,23 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import QtQuick.Controls.Styles 1.4
+import UM 1.3 as UM
+
+Button {
+ background: Rectangle {
+ opacity: parent.down || parent.hovered ? 1 : 0;
+ color: UM.Theme.getColor("monitor_context_menu_hover")
+ }
+ contentItem: Label {
+ color: enabled ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled");
+ text: parent.text
+ horizontalAlignment: Text.AlignLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ height: visible ? 39 * screenScaleFactor : 0; // TODO: Theme!
+ hoverEnabled: true;
+ width: parent.width;
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml b/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml
new file mode 100644
index 0000000000..c2590e99a8
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml
@@ -0,0 +1,103 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Window 2.2
+import QtQuick.Controls 1.2
+import UM 1.1 as UM
+
+UM.Dialog {
+ id: base;
+ height: minimumHeight;
+ leftButtons: [
+ Button {
+ enabled: true;
+ onClicked: {
+ base.visible = false;
+ printerSelectionCombobox.currentIndex = 0;
+ OutputDevice.cancelPrintSelection();
+ }
+ text: catalog.i18nc("@action:button","Cancel");
+ }
+ ]
+ maximumHeight: minimumHeight;
+ maximumWidth: minimumWidth;
+ minimumHeight: 140 * screenScaleFactor;
+ minimumWidth: 500 * screenScaleFactor;
+ modality: Qt.ApplicationModal;
+ onVisibleChanged: {
+ if (visible) {
+ resetPrintersModel();
+ } else {
+ OutputDevice.cancelPrintSelection();
+ }
+ }
+ rightButtons: [
+ Button {
+ enabled: true;
+ onClicked: {
+ base.visible = false;
+ OutputDevice.selectPrinter(printerSelectionCombobox.model.get(printerSelectionCombobox.currentIndex).key);
+ // reset to defaults
+ printerSelectionCombobox.currentIndex = 0;
+ }
+ text: catalog.i18nc("@action:button","Print");
+ }
+ ]
+ title: catalog.i18nc("@title:window", "Print over network");
+ visible: true;
+ width: minimumWidth;
+
+ Column {
+ id: printerSelection;
+ anchors {
+ fill: parent;
+ leftMargin: UM.Theme.getSize("default_margin").width;
+ rightMargin: UM.Theme.getSize("default_margin").width;
+ top: parent.top;
+ topMargin: UM.Theme.getSize("default_margin").height;
+ }
+ height: 50 * screenScaleFactor;
+
+ SystemPalette {
+ id: palette;
+ }
+
+ UM.I18nCatalog {
+ id: catalog;
+ name: "cura";
+ }
+
+ Label {
+ id: manualPrinterSelectionLabel;
+ anchors {
+ left: parent.left;
+ right: parent.right;
+ topMargin: UM.Theme.getSize("default_margin").height;
+ }
+ height: 20 * screenScaleFactor;
+ text: catalog.i18nc("@label", "Printer selection");
+ wrapMode: Text.Wrap;
+ }
+
+ ComboBox {
+ id: printerSelectionCombobox;
+ Behavior on height { NumberAnimation { duration: 100 } }
+ height: 40 * screenScaleFactor;
+ model: ListModel {
+ id: printersModel;
+ }
+ textRole: "name";
+ width: parent.width;
+ }
+ }
+
+ // Utils
+ function resetPrintersModel() {
+ printersModel.clear();
+ printersModel.append({ name: "Automatic", key: ""});
+ for (var index in OutputDevice.printers) {
+ printersModel.append({name: OutputDevice.printers[index].name, key: OutputDevice.printers[index].key});
+ }
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml b/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml
new file mode 100644
index 0000000000..77b481f6d8
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/PrinterVideoStream.qml
@@ -0,0 +1,65 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+Item {
+ property var cameraUrl: "";
+
+ Rectangle {
+ anchors.fill:parent;
+ color: UM.Theme.getColor("viewport_overlay");
+ opacity: 0.5;
+ }
+
+ MouseArea {
+ anchors.fill: parent;
+ onClicked: OutputDevice.setActiveCameraUrl("");
+ z: 0;
+ }
+
+ CameraButton {
+ id: closeCameraButton;
+ anchors {
+ right: cameraImage.right
+ rightMargin: UM.Theme.getSize("default_margin").width
+ top: cameraImage.top
+ topMargin: UM.Theme.getSize("default_margin").height
+ }
+ iconSource: UM.Theme.getIcon("cross1");
+ z: 999;
+ }
+
+ Cura.NetworkMJPGImage {
+ id: cameraImage
+ anchors.horizontalCenter: parent.horizontalCenter;
+ anchors.verticalCenter: parent.verticalCenter;
+ height: Math.round((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth);
+ onVisibleChanged: {
+ if (visible) {
+ if (cameraUrl != "") {
+ start();
+ }
+ } else {
+ if (cameraUrl != "") {
+ stop();
+ }
+ }
+ }
+ source: cameraUrl
+ width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
+ z: 1
+ }
+
+ MouseArea {
+ anchors.fill: cameraImage;
+ onClicked: {
+ OutputDevice.setActiveCameraUrl("");
+ }
+ z: 1;
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml b/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml
new file mode 100644
index 0000000000..c99ed1688e
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml
@@ -0,0 +1,93 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+import QtQuick.Layouts 1.1
+import QtQuick.Window 2.1
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Item {
+ id: base;
+ property string activeQualityDefinitionId: Cura.MachineManager.activeQualityDefinitionId;
+ property bool isUM3: activeQualityDefinitionId == "ultimaker3" || activeQualityDefinitionId.match("ultimaker_") != null;
+ property bool printerConnected: Cura.MachineManager.printerConnected;
+ property bool printerAcceptsCommands:
+ {
+ if (printerConnected && Cura.MachineManager.printerOutputDevices[0])
+ {
+ return Cura.MachineManager.printerOutputDevices[0].acceptsCommands
+ }
+ return false
+ }
+ property bool authenticationRequested:
+ {
+ if (printerConnected && Cura.MachineManager.printerOutputDevices[0])
+ {
+ var device = Cura.MachineManager.printerOutputDevices[0]
+ // AuthState.AuthenticationRequested or AuthState.AuthenticationReceived
+ return device.authenticationState == 2 || device.authenticationState == 5
+ }
+ return false
+ }
+ property var materialNames:
+ {
+ if (printerConnected && Cura.MachineManager.printerOutputDevices[0])
+ {
+ return Cura.MachineManager.printerOutputDevices[0].materialNames
+ }
+ return null
+ }
+ property var hotendIds:
+ {
+ if (printerConnected && Cura.MachineManager.printerOutputDevices[0])
+ {
+ return Cura.MachineManager.printerOutputDevices[0].hotendIds
+ }
+ return null
+ }
+
+ UM.I18nCatalog {
+ id: catalog;
+ name: "cura";
+ }
+
+ Row {
+ objectName: "networkPrinterConnectButton";
+ spacing: UM.Theme.getSize("default_margin").width;
+ visible: isUM3;
+
+ Button {
+ height: UM.Theme.getSize("save_button_save_to_button").height;
+ onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication();
+ style: UM.Theme.styles.print_setup_action_button;
+ text: catalog.i18nc("@action:button", "Request Access");
+ tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer");
+ visible: printerConnected && !printerAcceptsCommands && !authenticationRequested;
+ }
+
+ Button {
+ height: UM.Theme.getSize("save_button_save_to_button").height;
+ onClicked: connectActionDialog.show();
+ style: UM.Theme.styles.print_setup_action_button;
+ text: catalog.i18nc("@action:button", "Connect");
+ tooltip: catalog.i18nc("@info:tooltip", "Connect to a printer");
+ visible: !printerConnected;
+ }
+ }
+
+ UM.Dialog {
+ id: connectActionDialog;
+ rightButtons: Button {
+ iconName: "dialog-close";
+ onClicked: connectActionDialog.reject();
+ text: catalog.i18nc("@action:button", "Close");
+ }
+
+ Loader {
+ anchors.fill: parent;
+ source: "DiscoverUM3Action.qml";
+ }
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/resources/svg/UM3-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/UM3-icon.svg
new file mode 100644
index 0000000000..6b5d4e4895
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/UM3-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/UM3x-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/UM3x-icon.svg
new file mode 100644
index 0000000000..3708173dc5
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/UM3x-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/UMs5-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/UMs5-icon.svg
new file mode 100644
index 0000000000..78437465b3
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/UMs5-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/aborted-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/aborted-icon.svg
new file mode 100644
index 0000000000..7ef82c8911
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/aborted-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/action-required-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/action-required-icon.svg
similarity index 100%
rename from plugins/UM3NetworkPrinting/action-required-icon.svg
rename to plugins/UM3NetworkPrinting/resources/svg/action-required-icon.svg
diff --git a/plugins/UM3NetworkPrinting/resources/svg/approved-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/approved-icon.svg
new file mode 100644
index 0000000000..671957d709
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/approved-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/blocked-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/blocked-icon.svg
similarity index 100%
rename from plugins/UM3NetworkPrinting/blocked-icon.svg
rename to plugins/UM3NetworkPrinting/resources/svg/blocked-icon.svg
diff --git a/plugins/UM3NetworkPrinting/resources/svg/camera-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/camera-icon.svg
new file mode 100644
index 0000000000..66bed04508
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/camera-icon.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/checkmark-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/checkmark-icon.svg
similarity index 100%
rename from plugins/UM3NetworkPrinting/checkmark-icon.svg
rename to plugins/UM3NetworkPrinting/resources/svg/checkmark-icon.svg
diff --git a/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg b/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg
new file mode 100644
index 0000000000..8eba62ecc8
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg
@@ -0,0 +1,27 @@
+
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg b/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg
new file mode 100644
index 0000000000..746dc269fd
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/icons/buildplate.svg b/plugins/UM3NetworkPrinting/resources/svg/icons/buildplate.svg
new file mode 100644
index 0000000000..bcb278a8ca
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/icons/buildplate.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/icons/camera.svg b/plugins/UM3NetworkPrinting/resources/svg/icons/camera.svg
new file mode 100644
index 0000000000..2eaebb812d
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/icons/camera.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/icons/extruder.svg b/plugins/UM3NetworkPrinting/resources/svg/icons/extruder.svg
new file mode 100644
index 0000000000..235cb432e9
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/icons/extruder.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/paused-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/paused-icon.svg
new file mode 100644
index 0000000000..a66217d662
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/paused-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/ultibot.svg b/plugins/UM3NetworkPrinting/resources/svg/ultibot.svg
new file mode 100644
index 0000000000..be6ca64723
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/ultibot.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/svg/warning-icon.svg b/plugins/UM3NetworkPrinting/resources/svg/warning-icon.svg
new file mode 100644
index 0000000000..064d0783e0
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/svg/warning-icon.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py
new file mode 100644
index 0000000000..adff94bbbc
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py
@@ -0,0 +1,167 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+import json
+from json import JSONDecodeError
+from time import time
+from typing import Callable, List, Type, TypeVar, Union, Optional, Tuple, Dict, Any, cast
+
+from PyQt5.QtCore import QUrl
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
+
+from UM.Logger import Logger
+from cura import UltimakerCloudAuthentication
+from cura.API import Account
+from .ToolPathUploader import ToolPathUploader
+from ..Models import BaseModel
+from .Models.CloudClusterResponse import CloudClusterResponse
+from .Models.CloudError import CloudError
+from .Models.CloudClusterStatus import CloudClusterStatus
+from .Models.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest
+from .Models.CloudPrintResponse import CloudPrintResponse
+from .Models.CloudPrintJobResponse import CloudPrintJobResponse
+
+
+## The generic type variable used to document the methods below.
+CloudApiClientModel = TypeVar("CloudApiClientModel", bound = BaseModel)
+
+
+## The cloud API client is responsible for handling the requests and responses from the cloud.
+# Each method should only handle models instead of exposing Any HTTP details.
+class CloudApiClient:
+
+ # The cloud URL to use for this remote cluster.
+ ROOT_PATH = UltimakerCloudAuthentication.CuraCloudAPIRoot
+ CLUSTER_API_ROOT = "{}/connect/v1".format(ROOT_PATH)
+ CURA_API_ROOT = "{}/cura/v1".format(ROOT_PATH)
+
+ ## Initializes a new cloud API client.
+ # \param account: The user's account object
+ # \param on_error: The callback to be called whenever we receive errors from the server.
+ def __init__(self, account: Account, on_error: Callable[[List[CloudError]], None]) -> None:
+ super().__init__()
+ self._manager = QNetworkAccessManager()
+ self._account = account
+ self._on_error = on_error
+ self._upload = None # type: Optional[ToolPathUploader]
+ # In order to avoid garbage collection we keep the callbacks in this list.
+ self._anti_gc_callbacks = [] # type: List[Callable[[], None]]
+
+ ## Gets the account used for the API.
+ @property
+ def account(self) -> Account:
+ return self._account
+
+ ## Retrieves all the clusters for the user that is currently logged in.
+ # \param on_finished: The function to be called after the result is parsed.
+ def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], Any]) -> None:
+ url = "{}/clusters".format(self.CLUSTER_API_ROOT)
+ reply = self._manager.get(self._createEmptyRequest(url))
+ self._addCallback(reply, on_finished, CloudClusterResponse)
+
+ ## Retrieves the status of the given cluster.
+ # \param cluster_id: The ID of the cluster.
+ # \param on_finished: The function to be called after the result is parsed.
+ def getClusterStatus(self, cluster_id: str, on_finished: Callable[[CloudClusterStatus], Any]) -> None:
+ url = "{}/clusters/{}/status".format(self.CLUSTER_API_ROOT, cluster_id)
+ reply = self._manager.get(self._createEmptyRequest(url))
+ self._addCallback(reply, on_finished, CloudClusterStatus)
+
+ ## Requests the cloud to register the upload of a print job mesh.
+ # \param request: The request object.
+ # \param on_finished: The function to be called after the result is parsed.
+ def requestUpload(self, request: CloudPrintJobUploadRequest, on_finished: Callable[[CloudPrintJobResponse], Any]
+ ) -> None:
+ url = "{}/jobs/upload".format(self.CURA_API_ROOT)
+ body = json.dumps({"data": request.toDict()})
+ reply = self._manager.put(self._createEmptyRequest(url), body.encode())
+ self._addCallback(reply, on_finished, CloudPrintJobResponse)
+
+ ## Uploads a print job tool path to the cloud.
+ # \param print_job: The object received after requesting an upload with `self.requestUpload`.
+ # \param mesh: The tool path data to be uploaded.
+ # \param on_finished: The function to be called after the upload is successful.
+ # \param on_progress: A function to be called during upload progress. It receives a percentage (0-100).
+ # \param on_error: A function to be called if the upload fails.
+ def uploadToolPath(self, print_job: CloudPrintJobResponse, mesh: bytes, on_finished: Callable[[], Any],
+ on_progress: Callable[[int], Any], on_error: Callable[[], Any]):
+ self._upload = ToolPathUploader(self._manager, print_job, mesh, on_finished, on_progress, on_error)
+ self._upload.start()
+
+ # Requests a cluster to print the given print job.
+ # \param cluster_id: The ID of the cluster.
+ # \param job_id: The ID of the print job.
+ # \param on_finished: The function to be called after the result is parsed.
+ def requestPrint(self, cluster_id: str, job_id: str, on_finished: Callable[[CloudPrintResponse], Any]) -> None:
+ url = "{}/clusters/{}/print/{}".format(self.CLUSTER_API_ROOT, cluster_id, job_id)
+ reply = self._manager.post(self._createEmptyRequest(url), b"")
+ self._addCallback(reply, on_finished, CloudPrintResponse)
+
+ ## We override _createEmptyRequest in order to add the user credentials.
+ # \param url: The URL to request
+ # \param content_type: The type of the body contents.
+ def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
+ request = QNetworkRequest(QUrl(path))
+ if content_type:
+ request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
+ access_token = self._account.accessToken
+ if access_token:
+ request.setRawHeader(b"Authorization", "Bearer {}".format(access_token).encode())
+ return request
+
+ ## Parses the given JSON network reply into a status code and a dictionary, handling unexpected errors as well.
+ # \param reply: The reply from the server.
+ # \return A tuple with a status code and a dictionary.
+ @staticmethod
+ def _parseReply(reply: QNetworkReply) -> Tuple[int, Dict[str, Any]]:
+ status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
+ try:
+ response = bytes(reply.readAll()).decode()
+ return status_code, json.loads(response)
+ except (UnicodeDecodeError, JSONDecodeError, ValueError) as err:
+ error = CloudError(code=type(err).__name__, title=str(err), http_code=str(status_code),
+ id=str(time()), http_status="500")
+ Logger.logException("e", "Could not parse the stardust response: %s", error.toDict())
+ return status_code, {"errors": [error.toDict()]}
+
+ ## Parses the given models and calls the correct callback depending on the result.
+ # \param response: The response from the server, after being converted to a dict.
+ # \param on_finished: The callback in case the response is successful.
+ # \param model_class: The type of the model to convert the response to. It may either be a single record or a list.
+ def _parseModels(self, response: Dict[str, Any],
+ on_finished: Union[Callable[[CloudApiClientModel], Any],
+ Callable[[List[CloudApiClientModel]], Any]],
+ model_class: Type[CloudApiClientModel]) -> None:
+ if "data" in response:
+ data = response["data"]
+ if isinstance(data, list):
+ results = [model_class(**c) for c in data] # type: List[CloudApiClientModel]
+ on_finished_list = cast(Callable[[List[CloudApiClientModel]], Any], on_finished)
+ on_finished_list(results)
+ else:
+ result = model_class(**data) # type: CloudApiClientModel
+ on_finished_item = cast(Callable[[CloudApiClientModel], Any], on_finished)
+ on_finished_item(result)
+ elif "errors" in response:
+ self._on_error([CloudError(**error) for error in response["errors"]])
+ else:
+ Logger.log("e", "Cannot find data or errors in the cloud response: %s", response)
+
+ ## Creates a callback function so that it includes the parsing of the response into the correct model.
+ # The callback is added to the 'finished' signal of the reply.
+ # \param reply: The reply that should be listened to.
+ # \param on_finished: The callback in case the response is successful. Depending on the endpoint it will be either
+ # a list or a single item.
+ # \param model: The type of the model to convert the response to.
+ def _addCallback(self,
+ reply: QNetworkReply,
+ on_finished: Union[Callable[[CloudApiClientModel], Any],
+ Callable[[List[CloudApiClientModel]], Any]],
+ model: Type[CloudApiClientModel],
+ ) -> None:
+ def parse() -> None:
+ status_code, response = self._parseReply(reply)
+ self._anti_gc_callbacks.remove(parse)
+ return self._parseModels(response, on_finished, model)
+
+ self._anti_gc_callbacks.append(parse)
+ reply.finished.connect(parse)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputController.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputController.py
new file mode 100644
index 0000000000..bd56ef3185
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputController.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ from .CloudOutputDevice import CloudOutputDevice
+
+
+class CloudOutputController(PrinterOutputController):
+ def __init__(self, output_device: "CloudOutputDevice") -> None:
+ super().__init__(output_device)
+
+ # The cloud connection only supports fetching the printer and queue status and adding a job to the queue.
+ # To let the UI know this we mark all features below as False.
+ self.can_pause = False
+ self.can_abort = False
+ self.can_pre_heat_bed = False
+ self.can_pre_heat_hotends = False
+ self.can_send_raw_gcode = False
+ self.can_control_manually = False
+ self.can_update_firmware = False
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py
new file mode 100644
index 0000000000..7b5add276a
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py
@@ -0,0 +1,435 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+import os
+
+from time import time
+from typing import Dict, List, Optional, Set, cast
+
+from PyQt5.QtCore import QObject, QUrl, pyqtProperty, pyqtSignal, pyqtSlot
+
+from UM import i18nCatalog
+from UM.Backend.Backend import BackendState
+from UM.FileHandler.FileHandler import FileHandler
+from UM.Logger import Logger
+from UM.Message import Message
+from UM.Qt.Duration import Duration, DurationFormat
+from UM.Scene.SceneNode import SceneNode
+from cura.CuraApplication import CuraApplication
+from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
+from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+from cura.PrinterOutputDevice import ConnectionType
+
+from .CloudOutputController import CloudOutputController
+from ..MeshFormatHandler import MeshFormatHandler
+from ..UM3PrintJobOutputModel import UM3PrintJobOutputModel
+from .CloudProgressMessage import CloudProgressMessage
+from .CloudApiClient import CloudApiClient
+from .Models.CloudClusterResponse import CloudClusterResponse
+from .Models.CloudClusterStatus import CloudClusterStatus
+from .Models.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest
+from .Models.CloudPrintResponse import CloudPrintResponse
+from .Models.CloudPrintJobResponse import CloudPrintJobResponse
+from .Models.CloudClusterPrinterStatus import CloudClusterPrinterStatus
+from .Models.CloudClusterPrintJobStatus import CloudClusterPrintJobStatus
+from .Utils import findChanges, formatDateCompleted, formatTimeCompleted
+
+
+I18N_CATALOG = i18nCatalog("cura")
+
+
+## The cloud output device is a network output device that works remotely but has limited functionality.
+# Currently it only supports viewing the printer and print job status and adding a new job to the queue.
+# As such, those methods have been implemented here.
+# Note that this device represents a single remote cluster, not a list of multiple clusters.
+class CloudOutputDevice(NetworkedPrinterOutputDevice):
+
+ # The interval with which the remote clusters are checked
+ CHECK_CLUSTER_INTERVAL = 10.0 # seconds
+
+ # Signal triggered when the print jobs in the queue were changed.
+ printJobsChanged = pyqtSignal()
+
+ # Signal triggered when the selected printer in the UI should be changed.
+ activePrinterChanged = pyqtSignal()
+
+ # Notify can only use signals that are defined by the class that they are in, not inherited ones.
+ # Therefore we create a private signal used to trigger the printersChanged signal.
+ _clusterPrintersChanged = pyqtSignal()
+
+ ## Creates a new cloud output device
+ # \param api_client: The client that will run the API calls
+ # \param cluster: The device response received from the cloud API.
+ # \param parent: The optional parent of this output device.
+ def __init__(self, api_client: CloudApiClient, cluster: CloudClusterResponse, parent: QObject = None) -> None:
+
+ # The following properties are expected on each networked output device.
+ # Because the cloud connection does not off all of these, we manually construct this version here.
+ # An example of why this is needed is the selection of the compatible file type when exporting the tool path.
+ properties = {
+ b"address": b"",
+ b"name": cluster.host_name.encode() if cluster.host_name else b"",
+ b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
+ b"printer_type": b""
+ }
+
+ super().__init__(device_id = cluster.cluster_id, address = "",
+ connection_type = ConnectionType.CloudConnection, properties = properties, parent = parent)
+ self._api = api_client
+ self._cluster = cluster
+
+ self._setInterfaceElements()
+
+ self._account = api_client.account
+
+ # We use the Cura Connect monitor tab to get most functionality right away.
+ self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "../../resources/qml/MonitorStage.qml")
+
+ # Trigger the printersChanged signal when the private signal is triggered.
+ self.printersChanged.connect(self._clusterPrintersChanged)
+
+ # We keep track of which printer is visible in the monitor page.
+ self._active_printer = None # type: Optional[PrinterOutputModel]
+
+ # Properties to populate later on with received cloud data.
+ self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
+ self._number_of_extruders = 2 # All networked printers are dual-extrusion Ultimaker machines.
+
+ # We only allow a single upload at a time.
+ self._progress = CloudProgressMessage()
+
+ # Keep server string of the last generated time to avoid updating models more than once for the same response
+ self._received_printers = None # type: Optional[List[CloudClusterPrinterStatus]]
+ self._received_print_jobs = None # type: Optional[List[CloudClusterPrintJobStatus]]
+
+ # A set of the user's job IDs that have finished
+ self._finished_jobs = set() # type: Set[str]
+
+ # Reference to the uploaded print job / mesh
+ self._tool_path = None # type: Optional[bytes]
+ self._uploaded_print_job = None # type: Optional[CloudPrintJobResponse]
+
+ ## Connects this device.
+ def connect(self) -> None:
+ if self.isConnected():
+ return
+ super().connect()
+ Logger.log("i", "Connected to cluster %s", self.key)
+ CuraApplication.getInstance().getBackend().backendStateChange.connect(self._onBackendStateChange)
+
+ ## Disconnects the device
+ def disconnect(self) -> None:
+ super().disconnect()
+ Logger.log("i", "Disconnected from cluster %s", self.key)
+ CuraApplication.getInstance().getBackend().backendStateChange.disconnect(self._onBackendStateChange)
+
+ ## Resets the print job that was uploaded to force a new upload, runs whenever the user re-slices.
+ def _onBackendStateChange(self, _: BackendState) -> None:
+ self._tool_path = None
+ self._uploaded_print_job = None
+
+ ## Gets the cluster response from which this device was created.
+ @property
+ def clusterData(self) -> CloudClusterResponse:
+ return self._cluster
+
+ ## Updates the cluster data from the cloud.
+ @clusterData.setter
+ def clusterData(self, value: CloudClusterResponse) -> None:
+ self._cluster = value
+
+ ## Checks whether the given network key is found in the cloud's host name
+ def matchesNetworkKey(self, network_key: str) -> bool:
+ # A network key looks like "ultimakersystem-aabbccdd0011._ultimaker._tcp.local."
+ # the host name should then be "ultimakersystem-aabbccdd0011"
+ return network_key.startswith(self.clusterData.host_name)
+
+ ## Set all the interface elements and texts for this output device.
+ def _setInterfaceElements(self) -> None:
+ self.setPriority(2) # Make sure we end up below the local networking and above 'save to file'
+ self.setName(self._id)
+ self.setShortDescription(I18N_CATALOG.i18nc("@action:button", "Print via Cloud"))
+ self.setDescription(I18N_CATALOG.i18nc("@properties:tooltip", "Print via Cloud"))
+ self.setConnectionText(I18N_CATALOG.i18nc("@info:status", "Connected via Cloud"))
+
+ ## Called when Cura requests an output device to receive a (G-code) file.
+ def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False,
+ file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
+
+ # Show an error message if we're already sending a job.
+ if self._progress.visible:
+ message = Message(
+ text = I18N_CATALOG.i18nc("@info:status", "Sending new jobs (temporarily) blocked, still sending the previous print job."),
+ title = I18N_CATALOG.i18nc("@info:title", "Cloud error"),
+ lifetime = 10
+ )
+ message.show()
+ return
+
+ if self._uploaded_print_job:
+ # The mesh didn't change, let's not upload it again
+ self._api.requestPrint(self.key, self._uploaded_print_job.job_id, self._onPrintUploadCompleted)
+ return
+
+ # Indicate we have started sending a job.
+ self.writeStarted.emit(self)
+
+ mesh_format = MeshFormatHandler(file_handler, self.firmwareVersion)
+ if not mesh_format.is_valid:
+ Logger.log("e", "Missing file or mesh writer!")
+ return self._onUploadError(I18N_CATALOG.i18nc("@info:status", "Could not export print job."))
+
+ mesh = mesh_format.getBytes(nodes)
+
+ self._tool_path = mesh
+ request = CloudPrintJobUploadRequest(
+ job_name = file_name or mesh_format.file_extension,
+ file_size = len(mesh),
+ content_type = mesh_format.mime_type,
+ )
+ self._api.requestUpload(request, self._onPrintJobCreated)
+
+ ## Called when the network data should be updated.
+ def _update(self) -> None:
+ super()._update()
+ if self._last_request_time and time() - self._last_request_time < self.CHECK_CLUSTER_INTERVAL:
+ return # Avoid calling the cloud too often
+
+ Logger.log("d", "Updating: %s - %s >= %s", time(), self._last_request_time, self.CHECK_CLUSTER_INTERVAL)
+ if self._account.isLoggedIn:
+ self.setAuthenticationState(AuthState.Authenticated)
+ self._last_request_time = time()
+ self._api.getClusterStatus(self.key, self._onStatusCallFinished)
+ else:
+ self.setAuthenticationState(AuthState.NotAuthenticated)
+
+ ## Method called when HTTP request to status endpoint is finished.
+ # Contains both printers and print jobs statuses in a single response.
+ def _onStatusCallFinished(self, status: CloudClusterStatus) -> None:
+ # Update all data from the cluster.
+ self._last_response_time = time()
+ if self._received_printers != status.printers:
+ self._received_printers = status.printers
+ self._updatePrinters(status.printers)
+
+ if status.print_jobs != self._received_print_jobs:
+ self._received_print_jobs = status.print_jobs
+ self._updatePrintJobs(status.print_jobs)
+
+ ## Updates the local list of printers with the list received from the cloud.
+ # \param jobs: The printers received from the cloud.
+ def _updatePrinters(self, printers: List[CloudClusterPrinterStatus]) -> None:
+ previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel]
+ received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinterStatus]
+
+ removed_printers, added_printers, updated_printers = findChanges(previous, received)
+
+ for removed_printer in removed_printers:
+ if self._active_printer == removed_printer:
+ self.setActivePrinter(None)
+ self._printers.remove(removed_printer)
+
+ for added_printer in added_printers:
+ self._printers.append(added_printer.createOutputModel(CloudOutputController(self)))
+
+ for model, printer in updated_printers:
+ printer.updateOutputModel(model)
+
+ # Always have an active printer
+ if self._printers and not self._active_printer:
+ self.setActivePrinter(self._printers[0])
+
+ if added_printers or removed_printers:
+ self.printersChanged.emit()
+
+ ## Updates the local list of print jobs with the list received from the cloud.
+ # \param jobs: The print jobs received from the cloud.
+ def _updatePrintJobs(self, jobs: List[CloudClusterPrintJobStatus]) -> None:
+ received = {j.uuid: j for j in jobs} # type: Dict[str, CloudClusterPrintJobStatus]
+ previous = {j.key: j for j in self._print_jobs} # type: Dict[str, UM3PrintJobOutputModel]
+
+ removed_jobs, added_jobs, updated_jobs = findChanges(previous, received)
+
+ for removed_job in removed_jobs:
+ if removed_job.assignedPrinter:
+ removed_job.assignedPrinter.updateActivePrintJob(None)
+ removed_job.stateChanged.disconnect(self._onPrintJobStateChanged)
+ self._print_jobs.remove(removed_job)
+
+ for added_job in added_jobs:
+ self._addPrintJob(added_job)
+
+ for model, job in updated_jobs:
+ job.updateOutputModel(model)
+ if job.printer_uuid:
+ self._updateAssignedPrinter(model, job.printer_uuid)
+
+ # We only have to update when jobs are added or removed
+ # updated jobs push their changes via their output model
+ if added_jobs or removed_jobs:
+ self.printJobsChanged.emit()
+
+ ## Registers a new print job received via the cloud API.
+ # \param job: The print job received.
+ def _addPrintJob(self, job: CloudClusterPrintJobStatus) -> None:
+ model = job.createOutputModel(CloudOutputController(self))
+ model.stateChanged.connect(self._onPrintJobStateChanged)
+ if job.printer_uuid:
+ self._updateAssignedPrinter(model, job.printer_uuid)
+ self._print_jobs.append(model)
+
+ ## Handles the event of a change in a print job state
+ def _onPrintJobStateChanged(self) -> None:
+ user_name = self._getUserName()
+ # TODO: confirm that notifications in Cura are still required
+ for job in self._print_jobs:
+ if job.state == "wait_cleanup" and job.key not in self._finished_jobs and job.owner == user_name:
+ self._finished_jobs.add(job.key)
+ Message(
+ title = I18N_CATALOG.i18nc("@info:status", "Print finished"),
+ text = (I18N_CATALOG.i18nc("@info:status", "Printer '{printer_name}' has finished printing '{job_name}'.").format(
+ printer_name = job.assignedPrinter.name,
+ job_name = job.name
+ ) if job.assignedPrinter else
+ I18N_CATALOG.i18nc("@info:status", "The print job '{job_name}' was finished.").format(
+ job_name = job.name
+ )),
+ ).show()
+
+ ## Updates the printer assignment for the given print job model.
+ def _updateAssignedPrinter(self, model: UM3PrintJobOutputModel, printer_uuid: str) -> None:
+ printer = next((p for p in self._printers if printer_uuid == p.key), None)
+ if not printer:
+ Logger.log("w", "Missing printer %s for job %s in %s", model.assignedPrinter, model.key,
+ [p.key for p in self._printers])
+ return
+
+ printer.updateActivePrintJob(model)
+ model.updateAssignedPrinter(printer)
+
+ ## Uploads the mesh when the print job was registered with the cloud API.
+ # \param job_response: The response received from the cloud API.
+ def _onPrintJobCreated(self, job_response: CloudPrintJobResponse) -> None:
+ self._progress.show()
+ self._uploaded_print_job = job_response
+ tool_path = cast(bytes, self._tool_path)
+ self._api.uploadToolPath(job_response, tool_path, self._onPrintJobUploaded, self._progress.update, self._onUploadError)
+
+ ## Requests the print to be sent to the printer when we finished uploading the mesh.
+ def _onPrintJobUploaded(self) -> None:
+ self._progress.update(100)
+ print_job = cast(CloudPrintJobResponse, self._uploaded_print_job)
+ self._api.requestPrint(self.key, print_job.job_id, self._onPrintUploadCompleted)
+
+ ## Displays the given message if uploading the mesh has failed
+ # \param message: The message to display.
+ def _onUploadError(self, message: str = None) -> None:
+ self._progress.hide()
+ self._uploaded_print_job = None
+ Message(
+ text = message or I18N_CATALOG.i18nc("@info:text", "Could not upload the data to the printer."),
+ title = I18N_CATALOG.i18nc("@info:title", "Cloud error"),
+ lifetime = 10
+ ).show()
+ self.writeError.emit()
+
+ ## Shows a message when the upload has succeeded
+ # \param response: The response from the cloud API.
+ def _onPrintUploadCompleted(self, response: CloudPrintResponse) -> None:
+ Logger.log("d", "The cluster will be printing this print job with the ID %s", response.cluster_job_id)
+ self._progress.hide()
+ Message(
+ text = I18N_CATALOG.i18nc("@info:status", "Print job was successfully sent to the printer."),
+ title = I18N_CATALOG.i18nc("@info:title", "Data Sent"),
+ lifetime = 5
+ ).show()
+ self.writeFinished.emit()
+
+ ## Gets the remote printers.
+ @pyqtProperty("QVariantList", notify=_clusterPrintersChanged)
+ def printers(self) -> List[PrinterOutputModel]:
+ return self._printers
+
+ ## Get the active printer in the UI (monitor page).
+ @pyqtProperty(QObject, notify = activePrinterChanged)
+ def activePrinter(self) -> Optional[PrinterOutputModel]:
+ return self._active_printer
+
+ ## Set the active printer in the UI (monitor page).
+ @pyqtSlot(QObject)
+ def setActivePrinter(self, printer: Optional[PrinterOutputModel] = None) -> None:
+ if printer != self._active_printer:
+ self._active_printer = printer
+ self.activePrinterChanged.emit()
+
+ @pyqtProperty(int, notify = _clusterPrintersChanged)
+ def clusterSize(self) -> int:
+ return len(self._printers)
+
+ ## Get remote print jobs.
+ @pyqtProperty("QVariantList", notify = printJobsChanged)
+ def printJobs(self) -> List[UM3PrintJobOutputModel]:
+ return self._print_jobs
+
+ ## Get remote print jobs that are still in the print queue.
+ @pyqtProperty("QVariantList", notify = printJobsChanged)
+ def queuedPrintJobs(self) -> List[UM3PrintJobOutputModel]:
+ return [print_job for print_job in self._print_jobs
+ if print_job.state == "queued" or print_job.state == "error"]
+
+ ## Get remote print jobs that are assigned to a printer.
+ @pyqtProperty("QVariantList", notify = printJobsChanged)
+ def activePrintJobs(self) -> List[UM3PrintJobOutputModel]:
+ return [print_job for print_job in self._print_jobs if
+ print_job.assignedPrinter is not None and print_job.state != "queued"]
+
+ @pyqtSlot(int, result = str)
+ def formatDuration(self, seconds: int) -> str:
+ return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
+
+ @pyqtSlot(int, result = str)
+ def getTimeCompleted(self, time_remaining: int) -> str:
+ return formatTimeCompleted(time_remaining)
+
+ @pyqtSlot(int, result = str)
+ def getDateCompleted(self, time_remaining: int) -> str:
+ return formatDateCompleted(time_remaining)
+
+ ## TODO: The following methods are required by the monitor page QML, but are not actually available using cloud.
+ # TODO: We fake the methods here to not break the monitor page.
+
+ @pyqtProperty(QUrl, notify = _clusterPrintersChanged)
+ def activeCameraUrl(self) -> "QUrl":
+ return QUrl()
+
+ @pyqtSlot(QUrl)
+ def setActiveCameraUrl(self, camera_url: "QUrl") -> None:
+ pass
+
+ @pyqtProperty(bool, notify = printJobsChanged)
+ def receivedPrintJobs(self) -> bool:
+ return bool(self._print_jobs)
+
+ @pyqtSlot()
+ def openPrintJobControlPanel(self) -> None:
+ pass
+
+ @pyqtSlot()
+ def openPrinterControlPanel(self) -> None:
+ pass
+
+ @pyqtSlot(str)
+ def sendJobToTop(self, print_job_uuid: str) -> None:
+ pass
+
+ @pyqtSlot(str)
+ def deleteJobFromQueue(self, print_job_uuid: str) -> None:
+ pass
+
+ @pyqtSlot(str)
+ def forceSendJob(self, print_job_uuid: str) -> None:
+ pass
+
+ @pyqtProperty("QVariantList", notify = _clusterPrintersChanged)
+ def connectedPrintersTypeCount(self) -> List[Dict[str, str]]:
+ return []
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py
new file mode 100644
index 0000000000..e081beb99c
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Dict, List
+
+from PyQt5.QtCore import QTimer
+
+from UM import i18nCatalog
+from UM.Logger import Logger
+from UM.Message import Message
+from UM.Signal import Signal, signalemitter
+from cura.API import Account
+from cura.CuraApplication import CuraApplication
+from cura.Settings.GlobalStack import GlobalStack
+from .CloudApiClient import CloudApiClient
+from .CloudOutputDevice import CloudOutputDevice
+from .Models.CloudClusterResponse import CloudClusterResponse
+from .Models.CloudError import CloudError
+from .Utils import findChanges
+
+
+## The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters.
+# Keeping all cloud related logic in this class instead of the UM3OutputDevicePlugin results in more readable code.
+#
+# API spec is available on https://api.ultimaker.com/docs/connect/spec/.
+#
+class CloudOutputDeviceManager:
+ META_CLUSTER_ID = "um_cloud_cluster_id"
+
+ # The interval with which the remote clusters are checked
+ CHECK_CLUSTER_INTERVAL = 30.0 # seconds
+
+ # The translation catalog for this device.
+ I18N_CATALOG = i18nCatalog("cura")
+
+ addedCloudCluster = Signal()
+ removedCloudCluster = Signal()
+
+ def __init__(self) -> None:
+ # Persistent dict containing the remote clusters for the authenticated user.
+ self._remote_clusters = {} # type: Dict[str, CloudOutputDevice]
+
+ self._application = CuraApplication.getInstance()
+ self._output_device_manager = self._application.getOutputDeviceManager()
+
+ self._account = self._application.getCuraAPI().account # type: Account
+ self._api = CloudApiClient(self._account, self._onApiError)
+
+ # Create a timer to update the remote cluster list
+ self._update_timer = QTimer()
+ self._update_timer.setInterval(int(self.CHECK_CLUSTER_INTERVAL * 1000))
+ self._update_timer.setSingleShot(False)
+
+ self._running = False
+
+ # Called when the uses logs in or out
+ def _onLoginStateChanged(self, is_logged_in: bool) -> None:
+ Logger.log("d", "Log in state changed to %s", is_logged_in)
+ if is_logged_in:
+ if not self._update_timer.isActive():
+ self._update_timer.start()
+ self._getRemoteClusters()
+ else:
+ if self._update_timer.isActive():
+ self._update_timer.stop()
+
+ # Notify that all clusters have disappeared
+ self._onGetRemoteClustersFinished([])
+
+ ## Gets all remote clusters from the API.
+ def _getRemoteClusters(self) -> None:
+ Logger.log("d", "Retrieving remote clusters")
+ self._api.getClusters(self._onGetRemoteClustersFinished)
+
+ ## Callback for when the request for getting the clusters. is finished.
+ def _onGetRemoteClustersFinished(self, clusters: List[CloudClusterResponse]) -> None:
+ online_clusters = {c.cluster_id: c for c in clusters if c.is_online} # type: Dict[str, CloudClusterResponse]
+
+ removed_devices, added_clusters, updates = findChanges(self._remote_clusters, online_clusters)
+
+ Logger.log("d", "Parsed remote clusters to %s", [cluster.toDict() for cluster in online_clusters.values()])
+ Logger.log("d", "Removed: %s, added: %s, updates: %s", len(removed_devices), len(added_clusters), len(updates))
+
+ # Remove output devices that are gone
+ for removed_cluster in removed_devices:
+ if removed_cluster.isConnected():
+ removed_cluster.disconnect()
+ removed_cluster.close()
+ self._output_device_manager.removeOutputDevice(removed_cluster.key)
+ self.removedCloudCluster.emit()
+ del self._remote_clusters[removed_cluster.key]
+
+ # Add an output device for each new remote cluster.
+ # We only add when is_online as we don't want the option in the drop down if the cluster is not online.
+ for added_cluster in added_clusters:
+ device = CloudOutputDevice(self._api, added_cluster)
+ self._remote_clusters[added_cluster.cluster_id] = device
+ self.addedCloudCluster.emit()
+
+ for device, cluster in updates:
+ device.clusterData = cluster
+
+ self._connectToActiveMachine()
+
+ ## Callback for when the active machine was changed by the user or a new remote cluster was found.
+ def _connectToActiveMachine(self) -> None:
+ active_machine = CuraApplication.getInstance().getGlobalContainerStack()
+ if not active_machine:
+ return
+
+ # Remove all output devices that we have registered.
+ # This is needed because when we switch machines we can only leave
+ # output devices that are meant for that machine.
+ for stored_cluster_id in self._remote_clusters:
+ self._output_device_manager.removeOutputDevice(stored_cluster_id)
+
+ # Check if the stored cluster_id for the active machine is in our list of remote clusters.
+ stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID)
+ if stored_cluster_id in self._remote_clusters:
+ device = self._remote_clusters[stored_cluster_id]
+ self._connectToOutputDevice(device, active_machine)
+ Logger.log("d", "Device connected by metadata cluster ID %s", stored_cluster_id)
+ else:
+ self._connectByNetworkKey(active_machine)
+
+ ## Tries to match the local network key to the cloud cluster host name.
+ def _connectByNetworkKey(self, active_machine: GlobalStack) -> None:
+ # Check if the active printer has a local network connection and match this key to the remote cluster.
+ local_network_key = active_machine.getMetaDataEntry("um_network_key")
+ if not local_network_key:
+ return
+
+ device = next((c for c in self._remote_clusters.values() if c.matchesNetworkKey(local_network_key)), None)
+ if not device:
+ return
+
+ Logger.log("i", "Found cluster %s with network key %s", device, local_network_key)
+ active_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
+ self._connectToOutputDevice(device, active_machine)
+
+ ## Connects to an output device and makes sure it is registered in the output device manager.
+ def _connectToOutputDevice(self, device: CloudOutputDevice, active_machine: GlobalStack) -> None:
+ device.connect()
+ self._output_device_manager.addOutputDevice(device)
+ active_machine.addConfiguredConnectionType(device.connectionType.value)
+
+ ## Handles an API error received from the cloud.
+ # \param errors: The errors received
+ def _onApiError(self, errors: List[CloudError] = None) -> None:
+ Logger.log("w", str(errors))
+ message = Message(
+ text = self.I18N_CATALOG.i18nc("@info:description", "There was an error connecting to the cloud."),
+ title = self.I18N_CATALOG.i18nc("@info:title", "Error"),
+ lifetime = 10
+ )
+ message.show()
+
+ ## Starts running the cloud output device manager, thus periodically requesting cloud data.
+ def start(self):
+ if self._running:
+ return
+ self._account.loginStateChanged.connect(self._onLoginStateChanged)
+ # When switching machines we check if we have to activate a remote cluster.
+ self._application.globalContainerStackChanged.connect(self._connectToActiveMachine)
+ self._update_timer.timeout.connect(self._getRemoteClusters)
+ self._onLoginStateChanged(is_logged_in = self._account.isLoggedIn)
+
+ ## Stops running the cloud output device manager.
+ def stop(self):
+ if not self._running:
+ return
+ self._account.loginStateChanged.disconnect(self._onLoginStateChanged)
+ # When switching machines we check if we have to activate a remote cluster.
+ self._application.globalContainerStackChanged.disconnect(self._connectToActiveMachine)
+ self._update_timer.timeout.disconnect(self._getRemoteClusters)
+ self._onLoginStateChanged(is_logged_in = False)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudProgressMessage.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudProgressMessage.py
new file mode 100644
index 0000000000..d85f49c1a0
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudProgressMessage.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from UM import i18nCatalog
+from UM.Message import Message
+
+
+I18N_CATALOG = i18nCatalog("cura")
+
+
+## Class responsible for showing a progress message while a mesh is being uploaded to the cloud.
+class CloudProgressMessage(Message):
+ def __init__(self):
+ super().__init__(
+ text = I18N_CATALOG.i18nc("@info:status", "Sending data to remote cluster"),
+ title = I18N_CATALOG.i18nc("@info:status", "Sending data to remote cluster"),
+ progress = -1,
+ lifetime = 0,
+ dismissable = False,
+ use_inactivity_timer = False
+ )
+
+ ## Shows the progress message.
+ def show(self):
+ self.setProgress(0)
+ super().show()
+
+ ## Updates the percentage of the uploaded.
+ # \param percentage: The percentage amount (0-100).
+ def update(self, percentage: int) -> None:
+ if not self._visible:
+ super().show()
+ self.setProgress(percentage)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/BaseCloudModel.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/BaseCloudModel.py
new file mode 100644
index 0000000000..18a8cb5cba
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/BaseCloudModel.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from datetime import datetime, timezone
+from typing import Dict, Union, TypeVar, Type, List, Any
+
+from ...Models import BaseModel
+
+
+## Base class for the models used in the interface with the Ultimaker cloud APIs.
+class BaseCloudModel(BaseModel):
+ ## Checks whether the two models are equal.
+ # \param other: The other model.
+ # \return True if they are equal, False if they are different.
+ def __eq__(self, other):
+ return type(self) == type(other) and self.toDict() == other.toDict()
+
+ ## Checks whether the two models are different.
+ # \param other: The other model.
+ # \return True if they are different, False if they are the same.
+ def __ne__(self, other) -> bool:
+ return type(self) != type(other) or self.toDict() != other.toDict()
+
+ ## Converts the model into a serializable dictionary
+ def toDict(self) -> Dict[str, Any]:
+ return self.__dict__
+
+ # Type variable used in the parse methods below, which should be a subclass of BaseModel.
+ T = TypeVar("T", bound=BaseModel)
+
+ ## Parses a single model.
+ # \param model_class: The model class.
+ # \param values: The value of the model, which is usually a dictionary, but may also be already parsed.
+ # \return An instance of the model_class given.
+ @staticmethod
+ def parseModel(model_class: Type[T], values: Union[T, Dict[str, Any]]) -> T:
+ if isinstance(values, dict):
+ return model_class(**values)
+ return values
+
+ ## Parses a list of models.
+ # \param model_class: The model class.
+ # \param values: The value of the list. Each value is usually a dictionary, but may also be already parsed.
+ # \return A list of instances of the model_class given.
+ @classmethod
+ def parseModels(cls, model_class: Type[T], values: List[Union[T, Dict[str, Any]]]) -> List[T]:
+ return [cls.parseModel(model_class, value) for value in values]
+
+ ## Parses the given date string.
+ # \param date: The date to parse.
+ # \return The parsed date.
+ @staticmethod
+ def parseDate(date: Union[str, datetime]) -> datetime:
+ if isinstance(date, datetime):
+ return date
+ return datetime.strptime(date, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterBuildPlate.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterBuildPlate.py
new file mode 100644
index 0000000000..4386bbb435
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterBuildPlate.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from .BaseCloudModel import BaseCloudModel
+
+
+## Class representing a cluster printer
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterBuildPlate(BaseCloudModel):
+ ## Create a new build plate
+ # \param type: The type of buildplate glass or aluminium
+ def __init__(self, type: str = "glass", **kwargs) -> None:
+ self.type = type
+ super().__init__(**kwargs)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintCoreConfiguration.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintCoreConfiguration.py
new file mode 100644
index 0000000000..7454401d09
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintCoreConfiguration.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Union, Dict, Optional, Any
+
+from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
+from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
+from .CloudClusterPrinterConfigurationMaterial import CloudClusterPrinterConfigurationMaterial
+from .BaseCloudModel import BaseCloudModel
+
+
+## Class representing a cloud cluster printer configuration
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterPrintCoreConfiguration(BaseCloudModel):
+ ## Creates a new cloud cluster printer configuration object
+ # \param extruder_index: The position of the extruder on the machine as list index. Numbered from left to right.
+ # \param material: The material of a configuration object in a cluster printer. May be in a dict or an object.
+ # \param nozzle_diameter: The diameter of the print core at this position in millimeters, e.g. '0.4'.
+ # \param print_core_id: The type of print core inserted at this position, e.g. 'AA 0.4'.
+ def __init__(self, extruder_index: int,
+ material: Union[None, Dict[str, Any], CloudClusterPrinterConfigurationMaterial],
+ print_core_id: Optional[str] = None, **kwargs) -> None:
+ self.extruder_index = extruder_index
+ self.material = self.parseModel(CloudClusterPrinterConfigurationMaterial, material) if material else None
+ self.print_core_id = print_core_id
+ super().__init__(**kwargs)
+
+ ## Updates the given output model.
+ # \param model - The output model to update.
+ def updateOutputModel(self, model: ExtruderOutputModel) -> None:
+ if self.print_core_id is not None:
+ model.updateHotendID(self.print_core_id)
+
+ if self.material:
+ active_material = model.activeMaterial
+ if active_material is None or active_material.guid != self.material.guid:
+ material = self.material.createOutputModel()
+ model.updateActiveMaterial(material)
+ else:
+ model.updateActiveMaterial(None)
+
+ ## Creates a configuration model
+ def createConfigurationModel(self) -> ExtruderConfigurationModel:
+ model = ExtruderConfigurationModel(position = self.extruder_index)
+ self.updateConfigurationModel(model)
+ return model
+
+ ## Creates a configuration model
+ def updateConfigurationModel(self, model: ExtruderConfigurationModel) -> ExtruderConfigurationModel:
+ model.setHotendID(self.print_core_id)
+ if self.material:
+ model.setMaterial(self.material.createOutputModel())
+ return model
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobConfigurationChange.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobConfigurationChange.py
new file mode 100644
index 0000000000..9ff4154666
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobConfigurationChange.py
@@ -0,0 +1,27 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional
+
+from .BaseCloudModel import BaseCloudModel
+
+
+## Model for the types of changes that are needed before a print job can start
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterPrintJobConfigurationChange(BaseCloudModel):
+ ## Creates a new print job constraint.
+ # \param type_of_change: The type of configuration change, one of: "material", "print_core_change"
+ # \param index: The hotend slot or extruder index to change
+ # \param target_id: Target material guid or hotend id
+ # \param origin_id: Original/current material guid or hotend id
+ # \param target_name: Target material name or hotend id
+ # \param origin_name: Original/current material name or hotend id
+ def __init__(self, type_of_change: str, target_id: str, origin_id: str,
+ index: Optional[int] = None, target_name: Optional[str] = None, origin_name: Optional[str] = None,
+ **kwargs) -> None:
+ self.type_of_change = type_of_change
+ self.index = index
+ self.target_id = target_id
+ self.origin_id = origin_id
+ self.target_name = target_name
+ self.origin_name = origin_name
+ super().__init__(**kwargs)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobConstraint.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobConstraint.py
new file mode 100644
index 0000000000..8236ec06b9
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobConstraint.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional
+
+from .BaseCloudModel import BaseCloudModel
+
+
+## Class representing a cloud cluster print job constraint
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterPrintJobConstraints(BaseCloudModel):
+ ## Creates a new print job constraint.
+ # \param require_printer_name: Unique name of the printer that this job should be printed on.
+ # Should be one of the unique_name field values in the cluster, e.g. 'ultimakersystem-ccbdd30044ec'
+ def __init__(self, require_printer_name: Optional[str] = None, **kwargs) -> None:
+ self.require_printer_name = require_printer_name
+ super().__init__(**kwargs)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobImpediment.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobImpediment.py
new file mode 100644
index 0000000000..12b67996c1
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobImpediment.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from .BaseCloudModel import BaseCloudModel
+
+
+## Class representing the reasons that prevent this job from being printed on the associated printer
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterPrintJobImpediment(BaseCloudModel):
+ ## Creates a new print job constraint.
+ # \param translation_key: A string indicating a reason the print cannot be printed, such as 'does_not_fit_in_build_volume'
+ # \param severity: A number indicating the severity of the problem, with higher being more severe
+ def __init__(self, translation_key: str, severity: int, **kwargs) -> None:
+ self.translation_key = translation_key
+ self.severity = severity
+ super().__init__(**kwargs)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobStatus.py
new file mode 100644
index 0000000000..45b7d838a5
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrintJobStatus.py
@@ -0,0 +1,134 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import List, Optional, Union, Dict, Any
+
+from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
+from ...UM3PrintJobOutputModel import UM3PrintJobOutputModel
+from ...ConfigurationChangeModel import ConfigurationChangeModel
+from ..CloudOutputController import CloudOutputController
+from .BaseCloudModel import BaseCloudModel
+from .CloudClusterBuildPlate import CloudClusterBuildPlate
+from .CloudClusterPrintJobConfigurationChange import CloudClusterPrintJobConfigurationChange
+from .CloudClusterPrintJobImpediment import CloudClusterPrintJobImpediment
+from .CloudClusterPrintCoreConfiguration import CloudClusterPrintCoreConfiguration
+from .CloudClusterPrintJobConstraint import CloudClusterPrintJobConstraints
+
+
+## Model for the status of a single print job in a cluster.
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterPrintJobStatus(BaseCloudModel):
+ ## Creates a new cloud print job status model.
+ # \param assigned_to: The name of the printer this job is assigned to while being queued.
+ # \param configuration: The required print core configurations of this print job.
+ # \param constraints: Print job constraints object.
+ # \param created_at: The timestamp when the job was created in Cura Connect.
+ # \param force: Allow this job to be printed despite of mismatching configurations.
+ # \param last_seen: The number of seconds since this job was checked.
+ # \param machine_variant: The machine type that this job should be printed on.Coincides with the machine_type field
+ # of the printer object.
+ # \param name: The name of the print job. Usually the name of the .gcode file.
+ # \param network_error_count: The number of errors encountered when requesting data for this print job.
+ # \param owner: The name of the user who added the print job to Cura Connect.
+ # \param printer_uuid: UUID of the printer that the job is currently printing on or assigned to.
+ # \param started: Whether the job has started printing or not.
+ # \param status: The status of the print job.
+ # \param time_elapsed: The remaining printing time in seconds.
+ # \param time_total: The total printing time in seconds.
+ # \param uuid: UUID of this print job. Should be used for identification purposes.
+ # \param deleted_at: The time when this print job was deleted.
+ # \param printed_on_uuid: UUID of the printer used to print this job.
+ # \param configuration_changes_required: List of configuration changes the printer this job is associated with
+ # needs to make in order to be able to print this job
+ # \param build_plate: The build plate (type) this job needs to be printed on.
+ # \param compatible_machine_families: Family names of machines suitable for this print job
+ # \param impediments_to_printing: A list of reasons that prevent this job from being printed on the associated
+ # printer
+ def __init__(self, created_at: str, force: bool, machine_variant: str, name: str, started: bool, status: str,
+ time_total: int, uuid: str,
+ configuration: List[Union[Dict[str, Any], CloudClusterPrintCoreConfiguration]],
+ constraints: List[Union[Dict[str, Any], CloudClusterPrintJobConstraints]],
+ last_seen: Optional[float] = None, network_error_count: Optional[int] = None,
+ owner: Optional[str] = None, printer_uuid: Optional[str] = None, time_elapsed: Optional[int] = None,
+ assigned_to: Optional[str] = None, deleted_at: Optional[str] = None,
+ printed_on_uuid: Optional[str] = None,
+ configuration_changes_required: List[
+ Union[Dict[str, Any], CloudClusterPrintJobConfigurationChange]] = None,
+ build_plate: Union[Dict[str, Any], CloudClusterBuildPlate] = None,
+ compatible_machine_families: List[str] = None,
+ impediments_to_printing: List[Union[Dict[str, Any], CloudClusterPrintJobImpediment]] = None,
+ **kwargs) -> None:
+ self.assigned_to = assigned_to
+ self.configuration = self.parseModels(CloudClusterPrintCoreConfiguration, configuration)
+ self.constraints = self.parseModels(CloudClusterPrintJobConstraints, constraints)
+ self.created_at = created_at
+ self.force = force
+ self.last_seen = last_seen
+ self.machine_variant = machine_variant
+ self.name = name
+ self.network_error_count = network_error_count
+ self.owner = owner
+ self.printer_uuid = printer_uuid
+ self.started = started
+ self.status = status
+ self.time_elapsed = time_elapsed
+ self.time_total = time_total
+ self.uuid = uuid
+ self.deleted_at = deleted_at
+ self.printed_on_uuid = printed_on_uuid
+
+ self.configuration_changes_required = self.parseModels(CloudClusterPrintJobConfigurationChange,
+ configuration_changes_required) \
+ if configuration_changes_required else []
+ self.build_plate = self.parseModel(CloudClusterBuildPlate, build_plate) if build_plate else None
+ self.compatible_machine_families = compatible_machine_families if compatible_machine_families else []
+ self.impediments_to_printing = self.parseModels(CloudClusterPrintJobImpediment, impediments_to_printing) \
+ if impediments_to_printing else []
+
+ super().__init__(**kwargs)
+
+ ## Creates an UM3 print job output model based on this cloud cluster print job.
+ # \param printer: The output model of the printer
+ def createOutputModel(self, controller: CloudOutputController) -> UM3PrintJobOutputModel:
+ model = UM3PrintJobOutputModel(controller, self.uuid, self.name)
+ self.updateOutputModel(model)
+
+ return model
+
+ ## Creates a new configuration model
+ def _createConfigurationModel(self) -> ConfigurationModel:
+ extruders = [extruder.createConfigurationModel() for extruder in self.configuration or ()]
+ configuration = ConfigurationModel()
+ configuration.setExtruderConfigurations(extruders)
+ return configuration
+
+ ## Updates an UM3 print job output model based on this cloud cluster print job.
+ # \param model: The model to update.
+ def updateOutputModel(self, model: UM3PrintJobOutputModel) -> None:
+ model.updateConfiguration(self._createConfigurationModel())
+ model.updateTimeTotal(self.time_total)
+ model.updateTimeElapsed(self.time_elapsed)
+ model.updateOwner(self.owner)
+ model.updateState(self.status)
+ model.setCompatibleMachineFamilies(self.compatible_machine_families)
+ model.updateTimeTotal(self.time_total)
+ model.updateTimeElapsed(self.time_elapsed)
+ model.updateOwner(self.owner)
+
+ status_set_by_impediment = False
+ for impediment in self.impediments_to_printing:
+ # TODO: impediment.severity is defined as int, this will not work, is there a translation?
+ if impediment.severity == "UNFIXABLE":
+ status_set_by_impediment = True
+ model.updateState("error")
+ break
+
+ if not status_set_by_impediment:
+ model.updateState(self.status)
+
+ model.updateConfigurationChanges(
+ [ConfigurationChangeModel(
+ type_of_change = change.type_of_change,
+ index = change.index if change.index else 0,
+ target_name = change.target_name if change.target_name else "",
+ origin_name = change.origin_name if change.origin_name else "")
+ for change in self.configuration_changes_required])
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterConfigurationMaterial.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterConfigurationMaterial.py
new file mode 100644
index 0000000000..652cbdabda
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterConfigurationMaterial.py
@@ -0,0 +1,55 @@
+from typing import Optional
+
+from UM.Logger import Logger
+from cura.CuraApplication import CuraApplication
+from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
+from .BaseCloudModel import BaseCloudModel
+
+
+## Class representing a cloud cluster printer configuration
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterPrinterConfigurationMaterial(BaseCloudModel):
+ ## Creates a new material configuration model.
+ # \param brand: The brand of material in this print core, e.g. 'Ultimaker'.
+ # \param color: The color of material in this print core, e.g. 'Blue'.
+ # \param guid: he GUID of the material in this print core, e.g. '506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9'.
+ # \param material: The type of material in this print core, e.g. 'PLA'.
+ def __init__(self, brand: Optional[str] = None, color: Optional[str] = None, guid: Optional[str] = None,
+ material: Optional[str] = None, **kwargs) -> None:
+ self.guid = guid
+ self.brand = brand
+ self.color = color
+ self.material = material
+ super().__init__(**kwargs)
+
+ ## Creates a material output model based on this cloud printer material.
+ def createOutputModel(self) -> MaterialOutputModel:
+ material_manager = CuraApplication.getInstance().getMaterialManager()
+ material_group_list = material_manager.getMaterialGroupListByGUID(self.guid) or []
+
+ # Sort the material groups by "is_read_only = True" first, and then the name alphabetically.
+ read_only_material_group_list = list(filter(lambda x: x.is_read_only, material_group_list))
+ non_read_only_material_group_list = list(filter(lambda x: not x.is_read_only, material_group_list))
+ material_group = None
+ if read_only_material_group_list:
+ read_only_material_group_list = sorted(read_only_material_group_list, key = lambda x: x.name)
+ material_group = read_only_material_group_list[0]
+ elif non_read_only_material_group_list:
+ non_read_only_material_group_list = sorted(non_read_only_material_group_list, key = lambda x: x.name)
+ material_group = non_read_only_material_group_list[0]
+
+ if material_group:
+ container = material_group.root_material_node.getContainer()
+ color = container.getMetaDataEntry("color_code")
+ brand = container.getMetaDataEntry("brand")
+ material_type = container.getMetaDataEntry("material")
+ name = container.getName()
+ else:
+ Logger.log("w", "Unable to find material with guid {guid}. Using data as provided by cluster"
+ .format(guid = self.guid))
+ color = self.color
+ brand = self.brand
+ material_type = self.material
+ name = "Empty" if self.material == "empty" else "Unknown"
+
+ return MaterialOutputModel(guid = self.guid, type = material_type, brand = brand, color = color, name = name)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py
new file mode 100644
index 0000000000..bd3e482bde
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterPrinterStatus.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import List, Union, Dict, Optional, Any
+
+from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+from .CloudClusterBuildPlate import CloudClusterBuildPlate
+from .CloudClusterPrintCoreConfiguration import CloudClusterPrintCoreConfiguration
+from .BaseCloudModel import BaseCloudModel
+
+
+## Class representing a cluster printer
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterPrinterStatus(BaseCloudModel):
+ ## Creates a new cluster printer status
+ # \param enabled: A printer can be disabled if it should not receive new jobs. By default every printer is enabled.
+ # \param firmware_version: Firmware version installed on the printer. Can differ for each printer in a cluster.
+ # \param friendly_name: Human readable name of the printer. Can be used for identification purposes.
+ # \param ip_address: The IP address of the printer in the local network.
+ # \param machine_variant: The type of printer. Can be 'Ultimaker 3' or 'Ultimaker 3ext'.
+ # \param status: The status of the printer.
+ # \param unique_name: The unique name of the printer in the network.
+ # \param uuid: The unique ID of the printer, also known as GUID.
+ # \param configuration: The active print core configurations of this printer.
+ # \param reserved_by: A printer can be claimed by a specific print job.
+ # \param maintenance_required: Indicates if maintenance is necessary
+ # \param firmware_update_status: Whether the printer's firmware is up-to-date, value is one of: "up_to_date",
+ # "pending_update", "update_available", "update_in_progress", "update_failed", "update_impossible"
+ # \param latest_available_firmware: The version of the latest firmware that is available
+ # \param build_plate: The build plate that is on the printer
+ def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str,
+ status: str, unique_name: str, uuid: str,
+ configuration: List[Union[Dict[str, Any], CloudClusterPrintCoreConfiguration]],
+ reserved_by: Optional[str] = None, maintenance_required: Optional[bool] = None,
+ firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None,
+ build_plate: Union[Dict[str, Any], CloudClusterBuildPlate] = None, **kwargs) -> None:
+
+ self.configuration = self.parseModels(CloudClusterPrintCoreConfiguration, configuration)
+ self.enabled = enabled
+ self.firmware_version = firmware_version
+ self.friendly_name = friendly_name
+ self.ip_address = ip_address
+ self.machine_variant = machine_variant
+ self.status = status
+ self.unique_name = unique_name
+ self.uuid = uuid
+ self.reserved_by = reserved_by
+ self.maintenance_required = maintenance_required
+ self.firmware_update_status = firmware_update_status
+ self.latest_available_firmware = latest_available_firmware
+ self.build_plate = self.parseModel(CloudClusterBuildPlate, build_plate) if build_plate else None
+ super().__init__(**kwargs)
+
+ ## Creates a new output model.
+ # \param controller - The controller of the model.
+ def createOutputModel(self, controller: PrinterOutputController) -> PrinterOutputModel:
+ model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version)
+ self.updateOutputModel(model)
+ return model
+
+ ## Updates the given output model.
+ # \param model - The output model to update.
+ def updateOutputModel(self, model: PrinterOutputModel) -> None:
+ model.updateKey(self.uuid)
+ model.updateName(self.friendly_name)
+ model.updateType(self.machine_variant)
+ model.updateState(self.status if self.enabled else "disabled")
+ model.updateBuildplate(self.build_plate.type if self.build_plate else "glass")
+
+ for configuration, extruder_output, extruder_config in \
+ zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations):
+ configuration.updateOutputModel(extruder_output)
+ configuration.updateConfigurationModel(extruder_config)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py
new file mode 100644
index 0000000000..9c0853e7c9
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional
+
+from .BaseCloudModel import BaseCloudModel
+
+
+## Class representing a cloud connected cluster.
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterResponse(BaseCloudModel):
+ ## Creates a new cluster response object.
+ # \param cluster_id: The secret unique ID, e.g. 'kBEeZWEifXbrXviO8mRYLx45P8k5lHVGs43XKvRniPg='.
+ # \param host_guid: The unique identifier of the print cluster host, e.g. 'e90ae0ac-1257-4403-91ee-a44c9b7e8050'.
+ # \param host_name: The name of the printer as configured during the Wi-Fi setup. Used as identifier for end users.
+ # \param is_online: Whether this cluster is currently connected to the cloud.
+ # \param status: The status of the cluster authentication (active or inactive).
+ # \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on.
+ def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
+ host_version: Optional[str] = None, **kwargs) -> None:
+ self.cluster_id = cluster_id
+ self.host_guid = host_guid
+ self.host_name = host_name
+ self.status = status
+ self.is_online = is_online
+ self.host_version = host_version
+ super().__init__(**kwargs)
+
+ # Validates the model, raising an exception if the model is invalid.
+ def validate(self) -> None:
+ super().validate()
+ if not self.cluster_id:
+ raise ValueError("cluster_id is required on CloudCluster")
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterStatus.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterStatus.py
new file mode 100644
index 0000000000..b0250c2ebb
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterStatus.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from datetime import datetime
+from typing import List, Dict, Union, Any
+
+from .CloudClusterPrinterStatus import CloudClusterPrinterStatus
+from .CloudClusterPrintJobStatus import CloudClusterPrintJobStatus
+from .BaseCloudModel import BaseCloudModel
+
+
+# Model that represents the status of the cluster for the cloud
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudClusterStatus(BaseCloudModel):
+ ## Creates a new cluster status model object.
+ # \param printers: The latest status of each printer in the cluster.
+ # \param print_jobs: The latest status of each print job in the cluster.
+ # \param generated_time: The datetime when the object was generated on the server-side.
+ def __init__(self,
+ printers: List[Union[CloudClusterPrinterStatus, Dict[str, Any]]],
+ print_jobs: List[Union[CloudClusterPrintJobStatus, Dict[str, Any]]],
+ generated_time: Union[str, datetime],
+ **kwargs) -> None:
+ self.generated_time = self.parseDate(generated_time)
+ self.printers = self.parseModels(CloudClusterPrinterStatus, printers)
+ self.print_jobs = self.parseModels(CloudClusterPrintJobStatus, print_jobs)
+ super().__init__(**kwargs)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudError.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudError.py
new file mode 100644
index 0000000000..b53361022e
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudError.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Dict, Optional, Any
+
+from .BaseCloudModel import BaseCloudModel
+
+
+## Class representing errors generated by the cloud servers, according to the JSON-API standard.
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudError(BaseCloudModel):
+ ## Creates a new error object.
+ # \param id: Unique identifier for this particular occurrence of the problem.
+ # \param title: A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence
+ # of the problem, except for purposes of localization.
+ # \param code: An application-specific error code, expressed as a string value.
+ # \param detail: A human-readable explanation specific to this occurrence of the problem. Like title, this field's
+ # value can be localized.
+ # \param http_status: The HTTP status code applicable to this problem, converted to string.
+ # \param meta: Non-standard meta-information about the error, depending on the error code.
+ def __init__(self, id: str, code: str, title: str, http_status: str, detail: Optional[str] = None,
+ meta: Optional[Dict[str, Any]] = None, **kwargs) -> None:
+ self.id = id
+ self.code = code
+ self.http_status = http_status
+ self.title = title
+ self.detail = detail
+ self.meta = meta
+ super().__init__(**kwargs)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudPrintJobResponse.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudPrintJobResponse.py
new file mode 100644
index 0000000000..79196ee38c
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudPrintJobResponse.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional
+
+from .BaseCloudModel import BaseCloudModel
+
+
+# Model that represents the response received from the cloud after requesting to upload a print job
+# Spec: https://api-staging.ultimaker.com/cura/v1/spec
+class CloudPrintJobResponse(BaseCloudModel):
+ ## Creates a new print job response model.
+ # \param job_id: The job unique ID, e.g. 'kBEeZWEifXbrXviO8mRYLx45P8k5lHVGs43XKvRniPg='.
+ # \param status: The status of the print job.
+ # \param status_description: Contains more details about the status, e.g. the cause of failures.
+ # \param download_url: A signed URL to download the resulting status. Only available when the job is finished.
+ # \param job_name: The name of the print job.
+ # \param slicing_details: Model for slice information.
+ # \param upload_url: The one-time use URL where the toolpath must be uploaded to (only if status is uploading).
+ # \param content_type: The content type of the print job (e.g. text/plain or application/gzip)
+ # \param generated_time: The datetime when the object was generated on the server-side.
+ def __init__(self, job_id: str, status: str, download_url: Optional[str] = None, job_name: Optional[str] = None,
+ upload_url: Optional[str] = None, content_type: Optional[str] = None,
+ status_description: Optional[str] = None, slicing_details: Optional[dict] = None, **kwargs) -> None:
+ self.job_id = job_id
+ self.status = status
+ self.download_url = download_url
+ self.job_name = job_name
+ self.upload_url = upload_url
+ self.content_type = content_type
+ self.status_description = status_description
+ # TODO: Implement slicing details
+ self.slicing_details = slicing_details
+ super().__init__(**kwargs)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudPrintJobUploadRequest.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudPrintJobUploadRequest.py
new file mode 100644
index 0000000000..e59c571558
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudPrintJobUploadRequest.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from .BaseCloudModel import BaseCloudModel
+
+
+# Model that represents the request to upload a print job to the cloud
+# Spec: https://api-staging.ultimaker.com/cura/v1/spec
+class CloudPrintJobUploadRequest(BaseCloudModel):
+ ## Creates a new print job upload request.
+ # \param job_name: The name of the print job.
+ # \param file_size: The size of the file in bytes.
+ # \param content_type: The content type of the print job (e.g. text/plain or application/gzip)
+ def __init__(self, job_name: str, file_size: int, content_type: str, **kwargs) -> None:
+ self.job_name = job_name
+ self.file_size = file_size
+ self.content_type = content_type
+ super().__init__(**kwargs)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudPrintResponse.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudPrintResponse.py
new file mode 100644
index 0000000000..919d1b3c3a
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudPrintResponse.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from datetime import datetime
+from typing import Optional, Union
+
+from .BaseCloudModel import BaseCloudModel
+
+
+# Model that represents the responses received from the cloud after requesting a job to be printed.
+# Spec: https://api-staging.ultimaker.com/connect/v1/spec
+class CloudPrintResponse(BaseCloudModel):
+ ## Creates a new print response object.
+ # \param job_id: The unique ID of a print job inside of the cluster. This ID is generated by Cura Connect.
+ # \param status: The status of the print request (queued or failed).
+ # \param generated_time: The datetime when the object was generated on the server-side.
+ # \param cluster_job_id: The unique ID of a print job inside of the cluster. This ID is generated by Cura Connect.
+ def __init__(self, job_id: str, status: str, generated_time: Union[str, datetime],
+ cluster_job_id: Optional[str] = None, **kwargs) -> None:
+ self.job_id = job_id
+ self.status = status
+ self.cluster_job_id = cluster_job_id
+ self.generated_time = self.parseDate(generated_time)
+ super().__init__(**kwargs)
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/__init__.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/__init__.py
new file mode 100644
index 0000000000..f3f6970c54
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/ToolPathUploader.py b/plugins/UM3NetworkPrinting/src/Cloud/ToolPathUploader.py
new file mode 100644
index 0000000000..176b7e6ab7
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/ToolPathUploader.py
@@ -0,0 +1,148 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# !/usr/bin/env python
+# -*- coding: utf-8 -*-
+from PyQt5.QtCore import QUrl
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
+from typing import Optional, Callable, Any, Tuple, cast
+
+from UM.Logger import Logger
+from .Models.CloudPrintJobResponse import CloudPrintJobResponse
+
+
+## Class responsible for uploading meshes to the cloud in separate requests.
+class ToolPathUploader:
+
+ # The maximum amount of times to retry if the server returns one of the RETRY_HTTP_CODES
+ MAX_RETRIES = 10
+
+ # The HTTP codes that should trigger a retry.
+ RETRY_HTTP_CODES = {500, 502, 503, 504}
+
+ # The amount of bytes to send per request
+ BYTES_PER_REQUEST = 256 * 1024
+
+ ## Creates a mesh upload object.
+ # \param manager: The network access manager that will handle the HTTP requests.
+ # \param print_job: The print job response that was returned by the cloud after registering the upload.
+ # \param data: The mesh bytes to be uploaded.
+ # \param on_finished: The method to be called when done.
+ # \param on_progress: The method to be called when the progress changes (receives a percentage 0-100).
+ # \param on_error: The method to be called when an error occurs.
+ def __init__(self, manager: QNetworkAccessManager, print_job: CloudPrintJobResponse, data: bytes,
+ on_finished: Callable[[], Any], on_progress: Callable[[int], Any], on_error: Callable[[], Any]
+ ) -> None:
+ self._manager = manager
+ self._print_job = print_job
+ self._data = data
+
+ self._on_finished = on_finished
+ self._on_progress = on_progress
+ self._on_error = on_error
+
+ self._sent_bytes = 0
+ self._retries = 0
+ self._finished = False
+ self._reply = None # type: Optional[QNetworkReply]
+
+ ## Returns the print job for which this object was created.
+ @property
+ def printJob(self):
+ return self._print_job
+
+ ## Creates a network request to the print job upload URL, adding the needed content range header.
+ def _createRequest(self) -> QNetworkRequest:
+ request = QNetworkRequest(QUrl(self._print_job.upload_url))
+ request.setHeader(QNetworkRequest.ContentTypeHeader, self._print_job.content_type)
+
+ first_byte, last_byte = self._chunkRange()
+ content_range = "bytes {}-{}/{}".format(first_byte, last_byte - 1, len(self._data))
+ request.setRawHeader(b"Content-Range", content_range.encode())
+ Logger.log("i", "Uploading %s to %s", content_range, self._print_job.upload_url)
+
+ return request
+
+ ## Determines the bytes that should be uploaded next.
+ # \return: A tuple with the first and the last byte to upload.
+ def _chunkRange(self) -> Tuple[int, int]:
+ last_byte = min(len(self._data), self._sent_bytes + self.BYTES_PER_REQUEST)
+ return self._sent_bytes, last_byte
+
+ ## Starts uploading the mesh.
+ def start(self) -> None:
+ if self._finished:
+ # reset state.
+ self._sent_bytes = 0
+ self._retries = 0
+ self._finished = False
+ self._uploadChunk()
+
+ ## Stops uploading the mesh, marking it as finished.
+ def stop(self):
+ Logger.log("i", "Stopped uploading")
+ self._finished = True
+
+ ## Uploads a chunk of the mesh to the cloud.
+ def _uploadChunk(self) -> None:
+ if self._finished:
+ raise ValueError("The upload is already finished")
+
+ first_byte, last_byte = self._chunkRange()
+ request = self._createRequest()
+
+ # now send the reply and subscribe to the results
+ self._reply = self._manager.put(request, self._data[first_byte:last_byte])
+ self._reply.finished.connect(self._finishedCallback)
+ self._reply.uploadProgress.connect(self._progressCallback)
+ self._reply.error.connect(self._errorCallback)
+
+ ## Handles an update to the upload progress
+ # \param bytes_sent: The amount of bytes sent in the current request.
+ # \param bytes_total: The amount of bytes to send in the current request.
+ def _progressCallback(self, bytes_sent: int, bytes_total: int) -> None:
+ Logger.log("i", "Progress callback %s / %s", bytes_sent, bytes_total)
+ if bytes_total:
+ total_sent = self._sent_bytes + bytes_sent
+ self._on_progress(int(total_sent / len(self._data) * 100))
+
+ ## Handles an error uploading.
+ def _errorCallback(self) -> None:
+ reply = cast(QNetworkReply, self._reply)
+ body = bytes(reply.readAll()).decode()
+ Logger.log("e", "Received error while uploading: %s", body)
+ self.stop()
+ self._on_error()
+
+ ## Checks whether a chunk of data was uploaded successfully, starting the next chunk if needed.
+ def _finishedCallback(self) -> None:
+ reply = cast(QNetworkReply, self._reply)
+ Logger.log("i", "Finished callback %s %s",
+ reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url().toString())
+
+ status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) # type: int
+
+ # check if we should retry the last chunk
+ if self._retries < self.MAX_RETRIES and status_code in self.RETRY_HTTP_CODES:
+ self._retries += 1
+ Logger.log("i", "Retrying %s/%s request %s", self._retries, self.MAX_RETRIES, reply.url().toString())
+ self._uploadChunk()
+ return
+
+ # Http codes that are not to be retried are assumed to be errors.
+ if status_code > 308:
+ self._errorCallback()
+ return
+
+ Logger.log("d", "status_code: %s, Headers: %s, body: %s", status_code,
+ [bytes(header).decode() for header in reply.rawHeaderList()], bytes(reply.readAll()).decode())
+ self._chunkUploaded()
+
+ ## Handles a chunk of data being uploaded, starting the next chunk if needed.
+ def _chunkUploaded(self) -> None:
+ # We got a successful response. Let's start the next chunk or report the upload is finished.
+ first_byte, last_byte = self._chunkRange()
+ self._sent_bytes += last_byte - first_byte
+ if self._sent_bytes >= len(self._data):
+ self.stop()
+ self._on_finished()
+ else:
+ self._uploadChunk()
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Utils.py b/plugins/UM3NetworkPrinting/src/Cloud/Utils.py
new file mode 100644
index 0000000000..5136e0e7db
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Cloud/Utils.py
@@ -0,0 +1,54 @@
+from datetime import datetime, timedelta
+from typing import TypeVar, Dict, Tuple, List
+
+from UM import i18nCatalog
+
+T = TypeVar("T")
+U = TypeVar("U")
+
+
+## Splits the given dictionaries into three lists (in a tuple):
+# - `removed`: Items that were in the first argument but removed in the second one.
+# - `added`: Items that were not in the first argument but were included in the second one.
+# - `updated`: Items that were in both dictionaries. Both values are given in a tuple.
+# \param previous: The previous items
+# \param received: The received items
+# \return: The tuple (removed, added, updated) as explained above.
+def findChanges(previous: Dict[str, T], received: Dict[str, U]) -> Tuple[List[T], List[U], List[Tuple[T, U]]]:
+ previous_ids = set(previous)
+ received_ids = set(received)
+
+ removed_ids = previous_ids.difference(received_ids)
+ new_ids = received_ids.difference(previous_ids)
+ updated_ids = received_ids.intersection(previous_ids)
+
+ removed = [previous[removed_id] for removed_id in removed_ids]
+ added = [received[new_id] for new_id in new_ids]
+ updated = [(previous[updated_id], received[updated_id]) for updated_id in updated_ids]
+
+ return removed, added, updated
+
+
+def formatTimeCompleted(seconds_remaining: int) -> str:
+ completed = datetime.now() + timedelta(seconds=seconds_remaining)
+ return "{hour:02d}:{minute:02d}".format(hour = completed.hour, minute = completed.minute)
+
+
+def formatDateCompleted(seconds_remaining: int) -> str:
+ now = datetime.now()
+ completed = now + timedelta(seconds=seconds_remaining)
+ days = (completed.date() - now.date()).days
+ i18n = i18nCatalog("cura")
+
+ # If finishing date is more than 7 days out, using "Mon Dec 3 at HH:MM" format
+ if days >= 7:
+ return completed.strftime("%a %b ") + "{day}".format(day = completed.day)
+ # If finishing date is within the next week, use "Monday at HH:MM" format
+ elif days >= 2:
+ return completed.strftime("%a")
+ # If finishing tomorrow, use "tomorrow at HH:MM" format
+ elif days >= 1:
+ return i18n.i18nc("@info:status", "tomorrow")
+ # If finishing today, use "today at HH:MM" format
+ else:
+ return i18n.i18nc("@info:status", "today")
diff --git a/plugins/UM3NetworkPrinting/src/Cloud/__init__.py b/plugins/UM3NetworkPrinting/src/Cloud/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py
similarity index 54%
rename from plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
rename to plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py
index e85961f619..c1a6362455 100644
--- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
+++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py
@@ -1,82 +1,87 @@
-# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import Any, cast, Optional, Set, Tuple, Union
+from typing import Any, cast, Tuple, Union, Optional, Dict, List
+from time import time
+
+import io # To create the correct buffers for sending data to the printer.
+import json
+import os
from UM.FileHandler.FileHandler import FileHandler
-from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary).
-from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously.
+from UM.FileHandler.WriteFileJob import WriteFileJob # To call the file writer asynchronously.
from UM.Logger import Logger
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
-from UM.Mesh.MeshWriter import MeshWriter # For typing
-from UM.Message import Message
from UM.Qt.Duration import Duration, DurationFormat
-from UM.OutputDevice import OutputDeviceError #To show that something went wrong when writing.
-from UM.Scene.SceneNode import SceneNode #For typing.
-from UM.Version import Version #To check against firmware versions for support.
+
+from UM.Message import Message
+from UM.Scene.SceneNode import SceneNode # For typing.
from cura.CuraApplication import CuraApplication
-from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
+from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
+from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
+from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
-from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
-from cura.PrinterOutput.NetworkCamera import NetworkCamera
+from cura.PrinterOutputDevice import ConnectionType
+from .Cloud.Utils import formatTimeCompleted, formatDateCompleted
from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
+from .ConfigurationChangeModel import ConfigurationChangeModel
+from .MeshFormatHandler import MeshFormatHandler
from .SendMaterialJob import SendMaterialJob
+from .UM3PrintJobOutputModel import UM3PrintJobOutputModel
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
-from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtGui import QDesktopServices, QImage
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
-from time import time
-from datetime import datetime
-from typing import Optional, Dict, List, Set
-
-import io #To create the correct buffers for sending data to the printer.
-import json
-import os
-
i18n_catalog = i18nCatalog("cura")
class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
printJobsChanged = pyqtSignal()
activePrinterChanged = pyqtSignal()
+ activeCameraUrlChanged = pyqtSignal()
+ receivedPrintJobsChanged = pyqtSignal()
- # This is a bit of a hack, as the notify can only use signals that are defined by the class that they are in.
- # Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions.
- clusterPrintersChanged = pyqtSignal()
+ # Notify can only use signals that are defined by the class that they are in, not inherited ones.
+ # Therefore we create a private signal used to trigger the printersChanged signal.
+ _clusterPrintersChanged = pyqtSignal()
def __init__(self, device_id, address, properties, parent = None) -> None:
- super().__init__(device_id = device_id, address = address, properties=properties, parent = parent)
+ super().__init__(device_id = device_id, address = address, properties=properties, connection_type = ConnectionType.NetworkConnection, parent = parent)
self._api_prefix = "/cluster-api/v1/"
+ self._application = CuraApplication.getInstance()
+
self._number_of_extruders = 2
- self._dummy_lambdas = ("", {}, io.BytesIO()) #type: Tuple[str, Dict, Union[io.StringIO, io.BytesIO]]
+ self._dummy_lambdas = (
+ "", {}, io.BytesIO()
+ ) # type: Tuple[Optional[str], Dict[str, Union[str, int, bool]], Union[io.StringIO, io.BytesIO]]
- self._print_jobs = [] # type: List[PrintJobOutputModel]
+ self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
+ self._received_print_jobs = False # type: bool
- self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
- self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
+ self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/MonitorStage.qml")
- # See comments about this hack with the clusterPrintersChanged signal
- self.printersChanged.connect(self.clusterPrintersChanged)
+ # Trigger the printersChanged signal when the private signal is triggered
+ self.printersChanged.connect(self._clusterPrintersChanged)
- self._accepts_commands = True #type: bool
+ self._accepts_commands = True # type: bool
# Cluster does not have authentication, so default to authenticated
self._authentication_state = AuthState.Authenticated
- self._error_message = None #type: Optional[Message]
- self._write_job_progress_message = None #type: Optional[Message]
- self._progress_message = None #type: Optional[Message]
+ self._error_message = None # type: Optional[Message]
+ self._write_job_progress_message = None # type: Optional[Message]
+ self._progress_message = None # type: Optional[Message]
self._active_printer = None # type: Optional[PrinterOutputModel]
- self._printer_selection_dialog = None #type: QObject
+ self._printer_selection_dialog = None # type: QObject
self.setPriority(3) # Make sure the output device gets selected above local file output
self.setName(self._id)
@@ -87,77 +92,47 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._printer_uuid_to_unique_name_mapping = {} # type: Dict[str, str]
- self._finished_jobs = [] # type: List[PrintJobOutputModel]
+ self._finished_jobs = [] # type: List[UM3PrintJobOutputModel]
- self._cluster_size = int(properties.get(b"cluster_size", 0))
+ self._cluster_size = int(properties.get(b"cluster_size", 0)) # type: int
- self._latest_reply_handler = None #type: Optional[QNetworkReply]
+ self._latest_reply_handler = None # type: Optional[QNetworkReply]
+ self._sending_job = None
- def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
+ self._active_camera_url = QUrl() # type: QUrl
+
+ def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False,
+ file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
self.writeStarted.emit(self)
self.sendMaterialProfiles()
- #Formats supported by this application (file types that we can actually write).
- if file_handler:
- file_formats = file_handler.getSupportedFileTypesWrite()
- else:
- file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
+ mesh_format = MeshFormatHandler(file_handler, self.firmwareVersion)
- global_stack = CuraApplication.getInstance().getGlobalContainerStack()
- #Create a list from the supported file formats string.
- if not global_stack:
- Logger.log("e", "Missing global stack!")
- return
-
- machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
- machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
- #Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
- if "application/x-ufp" not in machine_file_formats and Version(self.firmwareVersion) >= Version("4.4"):
- machine_file_formats = ["application/x-ufp"] + machine_file_formats
-
- # Take the intersection between file_formats and machine_file_formats.
- format_by_mimetype = {format["mime_type"]: format for format in file_formats}
- file_formats = [format_by_mimetype[mimetype] for mimetype in machine_file_formats] #Keep them ordered according to the preference in machine_file_formats.
-
- if len(file_formats) == 0:
- Logger.log("e", "There are no file formats available to write with!")
- raise OutputDeviceError.WriteRequestFailedError(i18n_catalog.i18nc("@info:status", "There are no file formats available to write with!"))
- preferred_format = file_formats[0]
-
- #Just take the first file format available.
- if file_handler is not None:
- writer = file_handler.getWriterByMimeType(cast(str, preferred_format["mime_type"]))
- else:
- writer = CuraApplication.getInstance().getMeshFileHandler().getWriterByMimeType(cast(str, preferred_format["mime_type"]))
-
- if not writer:
- Logger.log("e", "Unexpected error when trying to get the FileWriter")
- return
-
- #This function pauses with the yield, waiting on instructions on which printer it needs to print with.
- if not writer:
+ # This function pauses with the yield, waiting on instructions on which printer it needs to print with.
+ if not mesh_format.is_valid:
Logger.log("e", "Missing file or mesh writer!")
return
- self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
- self._sending_job.send(None) #Start the generator.
+ self._sending_job = self._sendPrintJob(mesh_format, nodes)
+ if self._sending_job is not None:
+ self._sending_job.send(None) # Start the generator.
- if len(self._printers) > 1: #We need to ask the user.
- self._spawnPrinterSelectionDialog()
- is_job_sent = True
- else: #Just immediately continue.
- self._sending_job.send("") #No specifically selected printer.
- is_job_sent = self._sending_job.send(None)
+ if len(self._printers) > 1: # We need to ask the user.
+ self._spawnPrinterSelectionDialog()
+ is_job_sent = True
+ else: # Just immediately continue.
+ self._sending_job.send("") # No specifically selected printer.
+ is_job_sent = self._sending_job.send(None)
def _spawnPrinterSelectionDialog(self):
if self._printer_selection_dialog is None:
- path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml")
- self._printer_selection_dialog = CuraApplication.getInstance().createQmlComponent(path, {"OutputDevice": self})
+ path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/PrintWindow.qml")
+ self._printer_selection_dialog = self._application.createQmlComponent(path, {"OutputDevice": self})
if self._printer_selection_dialog is not None:
self._printer_selection_dialog.show()
@pyqtProperty(int, constant=True)
- def clusterSize(self):
+ def clusterSize(self) -> int:
return self._cluster_size
## Allows the user to choose a printer to print with from the printer
@@ -165,7 +140,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# \param target_printer The name of the printer to target.
@pyqtSlot(str)
def selectPrinter(self, target_printer: str = "") -> None:
- self._sending_job.send(target_printer)
+ if self._sending_job is not None:
+ self._sending_job.send(target_printer)
@pyqtSlot()
def cancelPrintSelection(self) -> None:
@@ -177,11 +153,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# greenlet in order to optionally wait for selectPrinter() to select a
# printer.
# The greenlet yields exactly three times: First time None,
- # \param writer The file writer to use to create the data.
- # \param preferred_format A dictionary containing some information about
- # what format to write to. This is necessary to create the correct buffer
- # types and file extension and such.
- def _sendPrintJob(self, writer: FileWriter, preferred_format: Dict, nodes: List[SceneNode]):
+ # \param mesh_format Object responsible for choosing the right kind of format to write with.
+ def _sendPrintJob(self, mesh_format: MeshFormatHandler, nodes: List[SceneNode]):
Logger.log("i", "Sending print job to printer.")
if self._sending_gcode:
self._error_message = Message(
@@ -195,35 +168,37 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._sending_gcode = True
- target_printer = yield #Potentially wait on the user to select a target printer.
+ # Potentially wait on the user to select a target printer.
+ target_printer = yield # type: Optional[str]
# Using buffering greatly reduces the write time for many lines of gcode
- stream = io.BytesIO() # type: Union[io.BytesIO, io.StringIO]# Binary mode.
- if preferred_format["mode"] == FileWriter.OutputMode.TextMode:
- stream = io.StringIO()
+ stream = mesh_format.createStream()
- job = WriteFileJob(writer, stream, nodes, preferred_format["mode"])
+ job = WriteFileJob(mesh_format.writer, stream, nodes, mesh_format.file_mode)
- self._write_job_progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1,
- title = i18n_catalog.i18nc("@info:title", "Sending Data"), use_inactivity_timer = False)
+ self._write_job_progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"),
+ lifetime = 0, dismissable = False, progress = -1,
+ title = i18n_catalog.i18nc("@info:title", "Sending Data"),
+ use_inactivity_timer = False)
self._write_job_progress_message.show()
- self._dummy_lambdas = (target_printer, preferred_format, stream)
- job.finished.connect(self._sendPrintJobWaitOnWriteJobFinished)
-
- job.start()
-
- yield True #Return that we had success!
- yield #To prevent having to catch the StopIteration exception.
+ if mesh_format.preferred_format is not None:
+ self._dummy_lambdas = (target_printer, mesh_format.preferred_format, stream)
+ job.finished.connect(self._sendPrintJobWaitOnWriteJobFinished)
+ job.start()
+ yield True # Return that we had success!
+ yield # To prevent having to catch the StopIteration exception.
def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None:
if self._write_job_progress_message:
self._write_job_progress_message.hide()
- self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1,
+ self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0,
+ dismissable = False, progress = -1,
title = i18n_catalog.i18nc("@info:title", "Sending Data"))
- self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = None, description = "")
+ self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = "",
+ description = "")
self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered)
self._progress_message.show()
parts = []
@@ -238,16 +213,18 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# Add user name to the print_job
parts.append(self._createFormPart("name=owner", bytes(self._getUserName(), "utf-8"), "text/plain"))
- file_name = CuraApplication.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
+ file_name = self._application.getPrintInformation().jobName + "." + preferred_format["extension"]
- output = stream.getvalue() #Either str or bytes depending on the output mode.
+ output = stream.getvalue() # Either str or bytes depending on the output mode.
if isinstance(stream, io.StringIO):
output = cast(str, output).encode("utf-8")
output = cast(bytes, output)
parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, output))
- self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, on_finished = self._onPostPrintJobFinished, on_progress = self._onUploadPrintJobProgress)
+ self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts,
+ on_finished = self._onPostPrintJobFinished,
+ on_progress = self._onUploadPrintJobProgress)
@pyqtProperty(QObject, notify = activePrinterChanged)
def activePrinter(self) -> Optional[PrinterOutputModel]:
@@ -256,36 +233,49 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
@pyqtSlot(QObject)
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
if self._active_printer != printer:
- if self._active_printer and self._active_printer.camera:
- self._active_printer.camera.stop()
self._active_printer = printer
self.activePrinterChanged.emit()
+ @pyqtProperty(QUrl, notify = activeCameraUrlChanged)
+ def activeCameraUrl(self) -> "QUrl":
+ return self._active_camera_url
+
+ @pyqtSlot(QUrl)
+ def setActiveCameraUrl(self, camera_url: "QUrl") -> None:
+ if self._active_camera_url != camera_url:
+ self._active_camera_url = camera_url
+ self.activeCameraUrlChanged.emit()
+
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
if self._progress_message:
self._progress_message.hide()
self._compressing_gcode = False
self._sending_gcode = False
+ ## The IP address of the printer.
+ @pyqtProperty(str, constant = True)
+ def address(self) -> str:
+ return self._address
+
def _onUploadPrintJobProgress(self, bytes_sent: int, bytes_total: int) -> None:
if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100
# Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
# timeout responses if this happens.
self._last_response_time = time()
- if self._progress_message and new_progress > self._progress_message.getProgress():
+ if self._progress_message is not None and new_progress != self._progress_message.getProgress():
self._progress_message.show() # Ensure that the message is visible.
self._progress_message.setProgress(bytes_sent / bytes_total * 100)
# If successfully sent:
if bytes_sent == bytes_total:
- # Show a confirmation to the user so they know the job was sucessful and provide the option to switch to the
- # monitor tab.
+ # Show a confirmation to the user so they know the job was sucessful and provide the option to switch to
+ # the monitor tab.
self._success_message = Message(
i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."),
lifetime=5, dismissable=True,
title=i18n_catalog.i18nc("@info:title", "Data Sent"))
- self._success_message.addAction("View", i18n_catalog.i18nc("@action:button", "View in Monitor"), icon=None,
+ self._success_message.addAction("View", i18n_catalog.i18nc("@action:button", "View in Monitor"), icon = "",
description="")
self._success_message.actionTriggered.connect(self._successMessageActionTriggered)
self._success_message.show()
@@ -301,7 +291,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._progress_message.hide()
self._compressing_gcode = False
self._sending_gcode = False
- CuraApplication.getInstance().getController().setActiveStage("PrepareStage")
+ self._application.getController().setActiveStage("PrepareStage")
# After compressing the sliced model Cura sends data to printer, to stop receiving updates from the request
# the "reply" should be disconnected
@@ -311,7 +301,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
def _successMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None:
if action_id == "View":
- CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
+ self._application.getController().setActiveStage("MonitorStage")
@pyqtSlot()
def openPrintJobControlPanel(self) -> None:
@@ -324,18 +314,22 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
@pyqtProperty("QVariantList", notify = printJobsChanged)
- def printJobs(self)-> List[PrintJobOutputModel]:
+ def printJobs(self)-> List[UM3PrintJobOutputModel]:
return self._print_jobs
- @pyqtProperty("QVariantList", notify = printJobsChanged)
- def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
- return [print_job for print_job in self._print_jobs if print_job.state == "queued"]
+ @pyqtProperty(bool, notify = receivedPrintJobsChanged)
+ def receivedPrintJobs(self) -> bool:
+ return self._received_print_jobs
@pyqtProperty("QVariantList", notify = printJobsChanged)
- def activePrintJobs(self) -> List[PrintJobOutputModel]:
+ def queuedPrintJobs(self) -> List[UM3PrintJobOutputModel]:
+ return [print_job for print_job in self._print_jobs if print_job.state == "queued" or print_job.state == "error"]
+
+ @pyqtProperty("QVariantList", notify = printJobsChanged)
+ def activePrintJobs(self) -> List[UM3PrintJobOutputModel]:
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"]
- @pyqtProperty("QVariantList", notify = clusterPrintersChanged)
+ @pyqtProperty("QVariantList", notify = _clusterPrintersChanged)
def connectedPrintersTypeCount(self) -> List[Dict[str, str]]:
printer_count = {} # type: Dict[str, int]
for printer in self._printers:
@@ -348,21 +342,39 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])})
return result
+ @pyqtProperty("QVariantList", notify=_clusterPrintersChanged)
+ def printers(self):
+ return self._printers
+
+ @pyqtSlot(int, result = str)
+ def getTimeCompleted(self, time_remaining: int) -> str:
+ return formatTimeCompleted(time_remaining)
+
+ @pyqtSlot(int, result = str)
+ def getDateCompleted(self, time_remaining: int) -> str:
+ return formatDateCompleted(time_remaining)
+
@pyqtSlot(int, result = str)
def formatDuration(self, seconds: int) -> str:
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
- @pyqtSlot(int, result = str)
- def getTimeCompleted(self, time_remaining: int) -> str:
- current_time = time()
- datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
- return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
+ @pyqtSlot(str)
+ def sendJobToTop(self, print_job_uuid: str) -> None:
+ # This function is part of the output device (and not of the printjob output model) as this type of operation
+ # is a modification of the cluster queue and not of the actual job.
+ data = "{\"to_position\": 0}"
+ self.put("print_jobs/{uuid}/move_to_position".format(uuid = print_job_uuid), data, on_finished=None)
- @pyqtSlot(int, result = str)
- def getDateCompleted(self, time_remaining: int) -> str:
- current_time = time()
- datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
- return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
+ @pyqtSlot(str)
+ def deleteJobFromQueue(self, print_job_uuid: str) -> None:
+ # This function is part of the output device (and not of the printjob output model) as this type of operation
+ # is a modification of the cluster queue and not of the actual job.
+ self.delete("print_jobs/{uuid}".format(uuid = print_job_uuid), on_finished=None)
+
+ @pyqtSlot(str)
+ def forceSendJob(self, print_job_uuid: str) -> None:
+ data = "{\"force\": true}"
+ self.put("print_jobs/{uuid}".format(uuid=print_job_uuid), data, on_finished=None)
def _printJobStateChanged(self) -> None:
username = self._getUserName()
@@ -392,12 +404,30 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
super().connect()
self.sendMaterialProfiles()
+ def _onGetPreviewImageFinished(self, reply: QNetworkReply) -> None:
+ reply_url = reply.url().toString()
+
+ uuid = reply_url[reply_url.find("print_jobs/")+len("print_jobs/"):reply_url.rfind("/preview_image")]
+
+ print_job = findByKey(self._print_jobs, uuid)
+ if print_job:
+ image = QImage()
+ image.loadFromData(reply.readAll())
+ print_job.updatePreviewImage(image)
+
def _update(self) -> None:
super()._update()
self.get("printers/", on_finished = self._onGetPrintersDataFinished)
self.get("print_jobs/", on_finished = self._onGetPrintJobsFinished)
+ for print_job in self._print_jobs:
+ if print_job.getPreviewImage() is None:
+ self.get("print_jobs/{uuid}/preview_image".format(uuid=print_job.key), on_finished=self._onGetPreviewImageFinished)
+
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
+ self._received_print_jobs = True
+ self.receivedPrintJobsChanged.emit()
+
if not checkValidGetReply(reply):
return
@@ -407,16 +437,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
print_jobs_seen = []
job_list_changed = False
- for print_job_data in result:
+ for idx, print_job_data in enumerate(result):
print_job = findByKey(self._print_jobs, print_job_data["uuid"])
-
if print_job is None:
print_job = self._createPrintJobModel(print_job_data)
job_list_changed = True
+ elif not job_list_changed:
+ # Check if the order of the jobs has changed since the last check
+ if self._print_jobs.index(print_job) != idx:
+ job_list_changed = True
self._updatePrintJob(print_job, print_job_data)
- if print_job.state != "queued": # Print job should be assigned to a printer.
+ if print_job.state != "queued" and print_job.state != "error": # Print job should be assigned to a printer.
if print_job.state in ["failed", "finished", "aborted", "none"]:
# Print job was already completed, so don't attach it to a printer.
printer = None
@@ -437,6 +470,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
job_list_changed = job_list_changed or self._removeJob(removed_job)
if job_list_changed:
+ # Override the old list with the new list (either because jobs were removed / added or order changed)
+ self._print_jobs = print_jobs_seen
self.printJobsChanged.emit() # Do a single emit for all print job changes.
def _onGetPrintersDataFinished(self, reply: QNetworkReply) -> None:
@@ -471,23 +506,109 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
def _createPrinterModel(self, data: Dict[str, Any]) -> PrinterOutputModel:
printer = PrinterOutputModel(output_controller = ClusterUM3PrinterOutputController(self),
number_of_extruders = self._number_of_extruders)
- printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
+ printer.setCameraUrl(QUrl("http://" + data["ip_address"] + ":8080/?action=stream"))
self._printers.append(printer)
return printer
- def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel:
- print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
+ def _createPrintJobModel(self, data: Dict[str, Any]) -> UM3PrintJobOutputModel:
+ print_job = UM3PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
key=data["uuid"], name= data["name"])
+
+ configuration = ConfigurationModel()
+ extruders = [ExtruderConfigurationModel(position = idx) for idx in range(0, self._number_of_extruders)]
+ for index in range(0, self._number_of_extruders):
+ try:
+ extruder_data = data["configuration"][index]
+ except IndexError:
+ continue
+ extruder = extruders[int(data["configuration"][index]["extruder_index"])]
+ extruder.setHotendID(extruder_data.get("print_core_id", ""))
+ extruder.setMaterial(self._createMaterialOutputModel(extruder_data.get("material", {})))
+
+ configuration.setExtruderConfigurations(extruders)
+ print_job.updateConfiguration(configuration)
+ print_job.setCompatibleMachineFamilies(data.get("compatible_machine_families", []))
print_job.stateChanged.connect(self._printJobStateChanged)
- self._print_jobs.append(print_job)
return print_job
- def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None:
+ def _updatePrintJob(self, print_job: UM3PrintJobOutputModel, data: Dict[str, Any]) -> None:
print_job.updateTimeTotal(data["time_total"])
print_job.updateTimeElapsed(data["time_elapsed"])
- print_job.updateState(data["status"])
+ impediments_to_printing = data.get("impediments_to_printing", [])
print_job.updateOwner(data["owner"])
+ status_set_by_impediment = False
+ for impediment in impediments_to_printing:
+ if impediment["severity"] == "UNFIXABLE":
+ status_set_by_impediment = True
+ print_job.updateState("error")
+ break
+
+ if not status_set_by_impediment:
+ print_job.updateState(data["status"])
+
+ print_job.updateConfigurationChanges(self._createConfigurationChanges(data["configuration_changes_required"]))
+
+ def _createConfigurationChanges(self, data: List[Dict[str, Any]]) -> List[ConfigurationChangeModel]:
+ result = []
+ for change in data:
+ result.append(ConfigurationChangeModel(type_of_change=change["type_of_change"],
+ index=change["index"],
+ target_name=change["target_name"],
+ origin_name=change["origin_name"]))
+ return result
+
+ def _createMaterialOutputModel(self, material_data: Dict[str, Any]) -> "MaterialOutputModel":
+ material_manager = self._application.getMaterialManager()
+ material_group_list = None
+
+ # Avoid crashing if there is no "guid" field in the metadata
+ material_guid = material_data.get("guid")
+ if material_guid:
+ material_group_list = material_manager.getMaterialGroupListByGUID(material_guid)
+
+ # This can happen if the connected machine has no material in one or more extruders (if GUID is empty), or the
+ # material is unknown to Cura, so we should return an "empty" or "unknown" material model.
+ if material_group_list is None:
+ material_name = i18n_catalog.i18nc("@label:material", "Empty") if len(material_data.get("guid", "")) == 0 \
+ else i18n_catalog.i18nc("@label:material", "Unknown")
+
+ return MaterialOutputModel(guid = material_data.get("guid", ""),
+ type = material_data.get("material", ""),
+ color = material_data.get("color", ""),
+ brand = material_data.get("brand", ""),
+ name = material_data.get("name", material_name)
+ )
+
+ # Sort the material groups by "is_read_only = True" first, and then the name alphabetically.
+ read_only_material_group_list = list(filter(lambda x: x.is_read_only, material_group_list))
+ non_read_only_material_group_list = list(filter(lambda x: not x.is_read_only, material_group_list))
+ material_group = None
+ if read_only_material_group_list:
+ read_only_material_group_list = sorted(read_only_material_group_list, key = lambda x: x.name)
+ material_group = read_only_material_group_list[0]
+ elif non_read_only_material_group_list:
+ non_read_only_material_group_list = sorted(non_read_only_material_group_list, key = lambda x: x.name)
+ material_group = non_read_only_material_group_list[0]
+
+ if material_group:
+ container = material_group.root_material_node.getContainer()
+ color = container.getMetaDataEntry("color_code")
+ brand = container.getMetaDataEntry("brand")
+ material_type = container.getMetaDataEntry("material")
+ name = container.getName()
+ else:
+ Logger.log("w",
+ "Unable to find material with guid {guid}. Using data as provided by cluster".format(
+ guid=material_data["guid"]))
+ color = material_data["color"]
+ brand = material_data["brand"]
+ material_type = material_data["material"]
+ name = i18n_catalog.i18nc("@label:material", "Empty") if material_data["material"] == "empty" \
+ else i18n_catalog.i18nc("@label:material", "Unknown")
+ return MaterialOutputModel(guid = material_data["guid"], type = material_type,
+ brand = brand, color = color, name = name)
+
def _updatePrinter(self, printer: PrinterOutputModel, data: Dict[str, Any]) -> None:
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
@@ -506,7 +627,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# Do not store the build plate information that comes from connect if the current printer has not build plate information
if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False):
- printer.updateBuildplateName(data["build_plate"]["type"])
+ printer.updateBuildplate(data["build_plate"]["type"])
if not data["enabled"]:
printer.updateState("disabled")
else:
@@ -523,27 +644,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
material_data = extruder_data["material"]
if extruder.activeMaterial is None or extruder.activeMaterial.guid != material_data["guid"]:
- containers = ContainerRegistry.getInstance().findInstanceContainers(type="material",
- GUID=material_data["guid"])
- if containers:
- color = containers[0].getMetaDataEntry("color_code")
- brand = containers[0].getMetaDataEntry("brand")
- material_type = containers[0].getMetaDataEntry("material")
- name = containers[0].getName()
- else:
- Logger.log("w",
- "Unable to find material with guid {guid}. Using data as provided by cluster".format(
- guid=material_data["guid"]))
- color = material_data["color"]
- brand = material_data["brand"]
- material_type = material_data["material"]
- name = "Empty" if material_data["material"] == "empty" else "Unknown"
-
- material = MaterialOutputModel(guid=material_data["guid"], type=material_type,
- brand=brand, color=color, name=name)
+ material = self._createMaterialOutputModel(material_data)
extruder.updateActiveMaterial(material)
- def _removeJob(self, job: PrintJobOutputModel) -> bool:
+ def _removeJob(self, job: UM3PrintJobOutputModel) -> bool:
if job not in self._print_jobs:
return False
@@ -586,8 +690,8 @@ def checkValidGetReply(reply: QNetworkReply) -> bool:
return True
-def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
- for item in list:
+def findByKey(lst: List[Union[UM3PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[UM3PrintJobOutputModel]:
+ for item in lst:
if item.key == key:
return item
return None
diff --git a/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py b/plugins/UM3NetworkPrinting/src/ClusterUM3PrinterOutputController.py
similarity index 91%
rename from plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
rename to plugins/UM3NetworkPrinting/src/ClusterUM3PrinterOutputController.py
index 4a0319cafc..fc6798386a 100644
--- a/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
+++ b/plugins/UM3NetworkPrinting/src/ClusterUM3PrinterOutputController.py
@@ -6,8 +6,6 @@ from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
- from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
-
class ClusterUM3PrinterOutputController(PrinterOutputController):
def __init__(self, output_device):
@@ -20,4 +18,3 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
def setJobState(self, job: "PrintJobOutputModel", state: str):
data = "{\"action\": \"%s\"}" % state
self._output_device.put("print_jobs/%s/action" % job.key, data, on_finished=None)
-
diff --git a/plugins/UM3NetworkPrinting/src/ConfigurationChangeModel.py b/plugins/UM3NetworkPrinting/src/ConfigurationChangeModel.py
new file mode 100644
index 0000000000..ef8a212b76
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/ConfigurationChangeModel.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
+
+class ConfigurationChangeModel(QObject):
+ def __init__(self, type_of_change: str, index: int, target_name: str, origin_name: str) -> None:
+ super().__init__()
+ self._type_of_change = type_of_change
+ # enum = ["material", "print_core_change"]
+ self._index = index
+ self._target_name = target_name
+ self._origin_name = origin_name
+
+ @pyqtProperty(int, constant = True)
+ def index(self) -> int:
+ return self._index
+
+ @pyqtProperty(str, constant = True)
+ def typeOfChange(self) -> str:
+ return self._type_of_change
+
+ @pyqtProperty(str, constant = True)
+ def targetName(self) -> str:
+ return self._target_name
+
+ @pyqtProperty(str, constant = True)
+ def originName(self) -> str:
+ return self._origin_name
diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py
similarity index 73%
rename from plugins/UM3NetworkPrinting/DiscoverUM3Action.py
rename to plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py
index c0a828ece9..ecc89b3948 100644
--- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py
+++ b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py
@@ -3,7 +3,7 @@
import os.path
import time
-from typing import cast, Optional
+from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject
@@ -13,9 +13,13 @@ from UM.i18n import i18nCatalog
from cura.CuraApplication import CuraApplication
from cura.MachineAction import MachineAction
+from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from .UM3OutputDevicePlugin import UM3OutputDevicePlugin
+if TYPE_CHECKING:
+ from cura.PrinterOutputDevice import PrinterOutputDevice
+
catalog = i18nCatalog("cura")
@@ -24,7 +28,7 @@ class DiscoverUM3Action(MachineAction):
def __init__(self) -> None:
super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network"))
- self._qml_url = "DiscoverUM3Action.qml"
+ self._qml_url = "resources/qml/DiscoverUM3Action.qml"
self._network_plugin = None #type: Optional[UM3OutputDevicePlugin]
@@ -102,13 +106,13 @@ class DiscoverUM3Action(MachineAction):
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
if global_container_stack:
meta_data = global_container_stack.getMetaData()
- if "connect_group_name" in meta_data:
- previous_connect_group_name = meta_data["connect_group_name"]
- global_container_stack.setMetaDataEntry("connect_group_name", group_name)
+ if "group_name" in meta_data:
+ previous_connect_group_name = meta_data["group_name"]
+ global_container_stack.setMetaDataEntry("group_name", group_name)
# Find all the places where there is the same group name and change it accordingly
- CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name)
+ CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "group_name", value = previous_connect_group_name, new_value = group_name)
else:
- global_container_stack.setMetaDataEntry("connect_group_name", group_name)
+ global_container_stack.setMetaDataEntry("group_name", group_name)
# Set the default value for "hidden", which is used when you have a group with multiple types of printers
global_container_stack.setMetaDataEntry("hidden", False)
@@ -116,22 +120,43 @@ class DiscoverUM3Action(MachineAction):
# Ensure that the connection states are refreshed.
self._network_plugin.reCheckConnections()
- @pyqtSlot(str)
- def setKey(self, key: str) -> None:
- Logger.log("d", "Attempting to set the network key of the active machine to %s", key)
+ # Associates the currently active machine with the given printer device. The network connection information will be
+ # stored into the metadata of the currently active machine.
+ @pyqtSlot(QObject)
+ def associateActiveMachineWithPrinterDevice(self, printer_device: Optional["PrinterOutputDevice"]) -> None:
+ if not printer_device:
+ return
+
+ Logger.log("d", "Attempting to set the network key of the active machine to %s", printer_device.key)
+
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
- if global_container_stack:
- meta_data = global_container_stack.getMetaData()
- if "um_network_key" in meta_data:
- previous_network_key= meta_data["um_network_key"]
- global_container_stack.setMetaDataEntry("um_network_key", key)
+ if not global_container_stack:
+ return
+
+ meta_data = global_container_stack.getMetaData()
+
+ if "um_network_key" in meta_data: # Global stack already had a connection, but it's changed.
+ old_network_key = meta_data["um_network_key"]
+ # Since we might have a bunch of hidden stacks, we also need to change it there.
+ metadata_filter = {"um_network_key": old_network_key}
+ containers = CuraContainerRegistry.getInstance().findContainerStacks(type="machine", **metadata_filter)
+
+ for container in containers:
+ container.setMetaDataEntry("um_network_key", printer_device.key)
+
# Delete old authentication data.
- Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key)
- global_container_stack.removeMetaDataEntry("network_authentication_id")
- global_container_stack.removeMetaDataEntry("network_authentication_key")
- CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
- else:
- global_container_stack.setMetaDataEntry("um_network_key", key)
+ Logger.log("d", "Removing old authentication id %s for device %s",
+ global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key)
+
+ container.removeMetaDataEntry("network_authentication_id")
+ container.removeMetaDataEntry("network_authentication_key")
+
+ # Ensure that these containers do know that they are configured for network connection
+ container.addConfiguredConnectionType(printer_device.connectionType.value)
+
+ else: # Global stack didn't have a connection yet, configure it.
+ global_container_stack.setMetaDataEntry("um_network_key", printer_device.key)
+ global_container_stack.addConfiguredConnectionType(printer_device.connectionType.value)
if self._network_plugin:
# Ensure that the connection states are refreshed.
@@ -174,7 +199,7 @@ class DiscoverUM3Action(MachineAction):
plugin_path = PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting")
if not plugin_path:
return
- path = os.path.join(plugin_path, "UM3InfoComponents.qml")
+ path = os.path.join(plugin_path, "resources/qml/UM3InfoComponents.qml")
self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
if not self.__additional_components_view:
Logger.log("w", "Could not create ui components for UM3.")
@@ -182,4 +207,3 @@ class DiscoverUM3Action(MachineAction):
# Create extra components
CuraApplication.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))
- CuraApplication.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo"))
diff --git a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py
similarity index 97%
rename from plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py
rename to plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py
index 8617b5b2ff..3ce0460d6b 100644
--- a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py
+++ b/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py
@@ -7,7 +7,7 @@ from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutp
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
-from cura.PrinterOutput.NetworkCamera import NetworkCamera
+from cura.PrinterOutputDevice import ConnectionType
from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.ExtruderManager import ExtruderManager
@@ -18,7 +18,7 @@ from UM.i18n import i18nCatalog
from UM.Message import Message
from PyQt5.QtNetwork import QNetworkRequest
-from PyQt5.QtCore import QTimer
+from PyQt5.QtCore import QTimer, QUrl
from PyQt5.QtWidgets import QMessageBox
from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController
@@ -44,7 +44,7 @@ i18n_catalog = i18nCatalog("cura")
# 5. As a final step, we verify the authentication, as this forces the QT manager to setup the authenticator.
class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
def __init__(self, device_id, address: str, properties, parent = None) -> None:
- super().__init__(device_id = device_id, address = address, properties = properties, parent = parent)
+ super().__init__(device_id = device_id, address = address, properties = properties, connection_type = ConnectionType.NetworkConnection, parent = parent)
self._api_prefix = "/api/v1/"
self._number_of_extruders = 2
@@ -76,7 +76,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self.setIconName("print")
- self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")
+ self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/MonitorItem.qml")
self._output_controller = LegacyUM3PrinterOutputController(self)
@@ -100,8 +100,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
title=i18n_catalog.i18nc("@info:title",
"Authentication status"))
- self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", ""),
- title=i18n_catalog.i18nc("@info:title", "Authentication Status"))
+ self._authentication_failed_message = Message("", title=i18n_catalog.i18nc("@info:title", "Authentication Status"))
self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry"), None,
i18n_catalog.i18nc("@info:tooltip", "Re-send the access request"))
self._authentication_failed_message.actionTriggered.connect(self._messageCallback)
@@ -500,8 +499,8 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self._authentication_id = None
self.post("auth/request",
- json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(),
- "user": self._getUserName()}).encode(),
+ json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(),
+ "user": self._getUserName()}),
on_finished=self._onRequestAuthenticationFinished)
self.setAuthenticationState(AuthState.AuthenticationRequested)
@@ -569,7 +568,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
# Quickest way to get the firmware version is to grab it from the zeroconf.
firmware_version = self._properties.get(b"firmware_version", b"").decode("utf-8")
self._printers = [PrinterOutputModel(output_controller=self._output_controller, number_of_extruders=self._number_of_extruders, firmware_version=firmware_version)]
- self._printers[0].setCamera(NetworkCamera("http://" + self._address + ":8080/?action=stream"))
+ self._printers[0].setCameraUrl(QUrl("http://" + self._address + ":8080/?action=stream"))
for extruder in self._printers[0].extruders:
extruder.activeMaterialChanged.connect(self.materialIdChanged)
extruder.hotendIDChanged.connect(self.hotendIdChanged)
diff --git a/plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py b/plugins/UM3NetworkPrinting/src/LegacyUM3PrinterOutputController.py
similarity index 97%
rename from plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py
rename to plugins/UM3NetworkPrinting/src/LegacyUM3PrinterOutputController.py
index 702b48ce15..63167b4ffb 100644
--- a/plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py
+++ b/plugins/UM3NetworkPrinting/src/LegacyUM3PrinterOutputController.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
@@ -33,9 +33,9 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
data = "{\"target\": \"%s\"}" % state
self._output_device.put("print_job/state", data, on_finished=None)
- def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
+ def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float):
data = str(temperature)
- self._output_device.put("printer/bed/temperature/target", data, on_finished=self._onPutBedTemperatureCompleted)
+ self._output_device.put("printer/bed/temperature/target", data, on_finished = self._onPutBedTemperatureCompleted)
def _onPutBedTemperatureCompleted(self, reply):
if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"):
diff --git a/plugins/UM3NetworkPrinting/src/MeshFormatHandler.py b/plugins/UM3NetworkPrinting/src/MeshFormatHandler.py
new file mode 100644
index 0000000000..c3cd82a86d
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/MeshFormatHandler.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+import io
+from typing import Optional, Dict, Union, List, cast
+
+from UM.FileHandler.FileHandler import FileHandler
+from UM.FileHandler.FileWriter import FileWriter
+from UM.Logger import Logger
+from UM.OutputDevice import OutputDeviceError # To show that something went wrong when writing.
+from UM.Scene.SceneNode import SceneNode
+from UM.Version import Version # To check against firmware versions for support.
+from UM.i18n import i18nCatalog
+from cura.CuraApplication import CuraApplication
+
+
+I18N_CATALOG = i18nCatalog("cura")
+
+
+## This class is responsible for choosing the formats used by the connected clusters.
+class MeshFormatHandler:
+
+ def __init__(self, file_handler: Optional[FileHandler], firmware_version: str) -> None:
+ self._file_handler = file_handler or CuraApplication.getInstance().getMeshFileHandler()
+ self._preferred_format = self._getPreferredFormat(firmware_version)
+ self._writer = self._getWriter(self.mime_type) if self._preferred_format else None
+
+ @property
+ def is_valid(self) -> bool:
+ return bool(self._writer)
+
+ ## Chooses the preferred file format.
+ # \return A dict with the file format details, with the following keys:
+ # {id: str, extension: str, description: str, mime_type: str, mode: int, hide_in_file_dialog: bool}
+ @property
+ def preferred_format(self) -> Optional[Dict[str, Union[str, int, bool]]]:
+ return self._preferred_format
+
+ ## Gets the file writer for the given file handler and mime type.
+ # \return A file writer.
+ @property
+ def writer(self) -> Optional[FileWriter]:
+ return self._writer
+
+ @property
+ def mime_type(self) -> str:
+ return cast(str, self._preferred_format["mime_type"])
+
+ ## Gets the file mode (FileWriter.OutputMode.TextMode or FileWriter.OutputMode.BinaryMode)
+ @property
+ def file_mode(self) -> int:
+ return cast(int, self._preferred_format["mode"])
+
+ ## Gets the file extension
+ @property
+ def file_extension(self) -> str:
+ return cast(str, self._preferred_format["extension"])
+
+ ## Creates the right kind of stream based on the preferred format.
+ def createStream(self) -> Union[io.BytesIO, io.StringIO]:
+ if self.file_mode == FileWriter.OutputMode.TextMode:
+ return io.StringIO()
+ else:
+ return io.BytesIO()
+
+ ## Writes the mesh and returns its value.
+ def getBytes(self, nodes: List[SceneNode]) -> bytes:
+ if self.writer is None:
+ raise ValueError("There is no writer for the mesh format handler.")
+ stream = self.createStream()
+ self.writer.write(stream, nodes)
+ value = stream.getvalue()
+ if isinstance(value, str):
+ value = value.encode()
+ return value
+
+ ## Chooses the preferred file format for the given file handler.
+ # \param firmware_version: The version of the firmware.
+ # \return A dict with the file format details.
+ def _getPreferredFormat(self, firmware_version: str) -> Dict[str, Union[str, int, bool]]:
+ # Formats supported by this application (file types that we can actually write).
+ application = CuraApplication.getInstance()
+
+ file_formats = self._file_handler.getSupportedFileTypesWrite()
+
+ global_stack = application.getGlobalContainerStack()
+ # Create a list from the supported file formats string.
+ if not global_stack:
+ Logger.log("e", "Missing global stack!")
+ return {}
+
+ machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
+ machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
+ # Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
+ if "application/x-ufp" not in machine_file_formats and Version(firmware_version) >= Version("4.4"):
+ machine_file_formats = ["application/x-ufp"] + machine_file_formats
+
+ # Take the intersection between file_formats and machine_file_formats.
+ format_by_mimetype = {f["mime_type"]: f for f in file_formats}
+
+ # Keep them ordered according to the preference in machine_file_formats.
+ file_formats = [format_by_mimetype[mimetype] for mimetype in machine_file_formats]
+
+ if len(file_formats) == 0:
+ Logger.log("e", "There are no file formats available to write with!")
+ raise OutputDeviceError.WriteRequestFailedError(
+ I18N_CATALOG.i18nc("@info:status", "There are no file formats available to write with!")
+ )
+ return file_formats[0]
+
+ ## Gets the file writer for the given file handler and mime type.
+ # \param mime_type: The mine type.
+ # \return A file writer.
+ def _getWriter(self, mime_type: str) -> Optional[FileWriter]:
+ # Just take the first file format available.
+ return self._file_handler.getWriterByMimeType(mime_type)
diff --git a/plugins/UM3NetworkPrinting/src/Models.py b/plugins/UM3NetworkPrinting/src/Models.py
new file mode 100644
index 0000000000..c5b9b16665
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/Models.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+
+## Base model that maps kwargs to instance attributes.
+class BaseModel:
+ def __init__(self, **kwargs) -> None:
+ self.__dict__.update(kwargs)
+ self.validate()
+
+ # Validates the model, raising an exception if the model is invalid.
+ def validate(self) -> None:
+ pass
+
+
+## Class representing a material that was fetched from the cluster API.
+class ClusterMaterial(BaseModel):
+ def __init__(self, guid: str, version: int, **kwargs) -> None:
+ self.guid = guid # type: str
+ self.version = version # type: int
+ super().__init__(**kwargs)
+
+ def validate(self) -> None:
+ if not self.guid:
+ raise ValueError("guid is required on ClusterMaterial")
+ if not self.version:
+ raise ValueError("version is required on ClusterMaterial")
+
+
+## Class representing a local material that was fetched from the container registry.
+class LocalMaterial(BaseModel):
+ def __init__(self, GUID: str, id: str, version: int, **kwargs) -> None:
+ self.GUID = GUID # type: str
+ self.id = id # type: str
+ self.version = version # type: int
+ super().__init__(**kwargs)
+
+ #
+ def validate(self) -> None:
+ super().validate()
+ if not self.GUID:
+ raise ValueError("guid is required on LocalMaterial")
+ if not self.version:
+ raise ValueError("version is required on LocalMaterial")
+ if not self.id:
+ raise ValueError("id is required on LocalMaterial")
diff --git a/plugins/UM3NetworkPrinting/src/SendMaterialJob.py b/plugins/UM3NetworkPrinting/src/SendMaterialJob.py
new file mode 100644
index 0000000000..f0fde818c4
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/SendMaterialJob.py
@@ -0,0 +1,197 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import json
+import os
+from typing import Dict, TYPE_CHECKING, Set, Optional
+from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
+
+from UM.Job import Job
+from UM.Logger import Logger
+from cura.CuraApplication import CuraApplication
+
+# Absolute imports don't work in plugins
+from .Models import ClusterMaterial, LocalMaterial
+
+if TYPE_CHECKING:
+ from .ClusterUM3OutputDevice import ClusterUM3OutputDevice
+
+
+## Asynchronous job to send material profiles to the printer.
+#
+# This way it won't freeze up the interface while sending those materials.
+class SendMaterialJob(Job):
+
+ def __init__(self, device: "ClusterUM3OutputDevice") -> None:
+ super().__init__()
+ self.device = device # type: ClusterUM3OutputDevice
+
+ ## Send the request to the printer and register a callback
+ def run(self) -> None:
+ self.device.get("materials/", on_finished = self._onGetRemoteMaterials)
+
+ ## Process the materials reply from the printer.
+ #
+ # \param reply The reply from the printer, a json file.
+ def _onGetRemoteMaterials(self, reply: QNetworkReply) -> None:
+ # Got an error from the HTTP request. If we did not receive a 200 something happened.
+ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
+ Logger.log("e", "Error fetching materials from printer: %s", reply.errorString())
+ return
+
+ # Collect materials from the printer's reply and send the missing ones if needed.
+ remote_materials_by_guid = self._parseReply(reply)
+ if remote_materials_by_guid:
+ self._sendMissingMaterials(remote_materials_by_guid)
+
+ ## Determine which materials should be updated and send them to the printer.
+ #
+ # \param remote_materials_by_guid The remote materials by GUID.
+ def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None:
+ # Collect local materials
+ local_materials_by_guid = self._getLocalMaterials()
+ if len(local_materials_by_guid) == 0:
+ Logger.log("d", "There are no local materials to synchronize with the printer.")
+ return
+
+ # Find out what materials are new or updated and must be sent to the printer
+ material_ids_to_send = self._determineMaterialsToSend(local_materials_by_guid, remote_materials_by_guid)
+ if len(material_ids_to_send) == 0:
+ Logger.log("d", "There are no remote materials to update.")
+ return
+
+ # Send materials to the printer
+ self._sendMaterials(material_ids_to_send)
+
+ ## From the local and remote materials, determine which ones should be synchronized.
+ #
+ # Makes a Set of id's containing only the id's of the materials that are not on the printer yet or the ones that
+ # are newer in Cura.
+ #
+ # \param local_materials The local materials by GUID.
+ # \param remote_materials The remote materials by GUID.
+ @staticmethod
+ def _determineMaterialsToSend(local_materials: Dict[str, LocalMaterial],
+ remote_materials: Dict[str, ClusterMaterial]) -> Set[str]:
+ return {
+ material.id
+ for guid, material in local_materials.items()
+ if guid not in remote_materials or material.version > remote_materials[guid].version
+ }
+
+ ## Send the materials to the printer.
+ #
+ # The given materials will be loaded from disk en sent to to printer.
+ # The given id's will be matched with filenames of the locally stored materials.
+ #
+ # \param materials_to_send A set with id's of materials that must be sent.
+ def _sendMaterials(self, materials_to_send: Set[str]) -> None:
+ container_registry = CuraApplication.getInstance().getContainerRegistry()
+ material_manager = CuraApplication.getInstance().getMaterialManager()
+ material_group_dict = material_manager.getAllMaterialGroups()
+
+ for root_material_id in material_group_dict:
+ if root_material_id not in materials_to_send:
+ # If the material does not have to be sent we skip it.
+ continue
+
+ file_path = container_registry.getContainerFilePathById(root_material_id)
+ if not file_path:
+ Logger.log("w", "Cannot get file path for material container [%s]", root_material_id)
+ continue
+
+ file_name = os.path.basename(file_path)
+ self._sendMaterialFile(file_path, file_name, root_material_id)
+
+ ## Send a single material file to the printer.
+ #
+ # Also add the material signature file if that is available.
+ #
+ # \param file_path The path of the material file.
+ # \param file_name The name of the material file.
+ # \param material_id The ID of the material in the file.
+ def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None:
+ parts = []
+
+ # Add the material file.
+ with open(file_path, "rb") as f:
+ parts.append(self.device.createFormPart("name=\"file\"; filename=\"{file_name}\""
+ .format(file_name = file_name), f.read()))
+
+ # Add the material signature file if needed.
+ signature_file_path = "{}.sig".format(file_path)
+ if os.path.exists(signature_file_path):
+ signature_file_name = os.path.basename(signature_file_path)
+ with open(signature_file_path, "rb") as f:
+ parts.append(self.device.createFormPart("name=\"signature_file\"; filename=\"{file_name}\""
+ .format(file_name = signature_file_name), f.read()))
+
+ Logger.log("d", "Syncing material {material_id} with cluster.".format(material_id = material_id))
+ self.device.postFormWithParts(target = "materials/", parts = parts, on_finished = self.sendingFinished)
+
+ ## Check a reply from an upload to the printer and log an error when the call failed
+ @staticmethod
+ def sendingFinished(reply: QNetworkReply) -> None:
+ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
+ Logger.log("e", "Received error code from printer when syncing material: {code}, {text}".format(
+ code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute),
+ text = reply.errorString()
+ ))
+
+ ## Parse the reply from the printer
+ #
+ # Parses the reply to a "/materials" request to the printer
+ #
+ # \return a dictionary of ClusterMaterial objects by GUID
+ # \throw KeyError Raised when on of the materials does not include a valid guid
+ @classmethod
+ def _parseReply(cls, reply: QNetworkReply) -> Optional[Dict[str, ClusterMaterial]]:
+ try:
+ remote_materials = json.loads(reply.readAll().data().decode("utf-8"))
+ return {material["guid"]: ClusterMaterial(**material) for material in remote_materials}
+ except UnicodeDecodeError:
+ Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.")
+ except json.JSONDecodeError:
+ Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.")
+ except ValueError:
+ Logger.log("e", "Request material storage on printer: Printer's answer had an incorrect value.")
+ except TypeError:
+ Logger.log("e", "Request material storage on printer: Printer's answer was missing a required value.")
+ return None
+
+ ## Retrieves a list of local materials
+ #
+ # Only the new newest version of the local materials is returned
+ #
+ # \return a dictionary of LocalMaterial objects by GUID
+ def _getLocalMaterials(self) -> Dict[str, LocalMaterial]:
+ result = {} # type: Dict[str, LocalMaterial]
+ material_manager = CuraApplication.getInstance().getMaterialManager()
+
+ material_group_dict = material_manager.getAllMaterialGroups()
+
+ # Find the latest version of all material containers in the registry.
+ for root_material_id, material_group in material_group_dict.items():
+ material_metadata = material_group.root_material_node.getMetadata()
+
+ try:
+ # material version must be an int
+ material_metadata["version"] = int(material_metadata["version"])
+
+ # Create a new local material
+ local_material = LocalMaterial(**material_metadata)
+ local_material.id = root_material_id
+
+ if local_material.GUID not in result or \
+ local_material.GUID not in result or \
+ local_material.version > result[local_material.GUID].version:
+ result[local_material.GUID] = local_material
+
+ except KeyError:
+ Logger.logException("w", "Local material {} has missing values.".format(material_metadata["id"]))
+ except ValueError:
+ Logger.logException("w", "Local material {} has invalid values.".format(material_metadata["id"]))
+ except TypeError:
+ Logger.logException("w", "Local material {} has invalid values.".format(material_metadata["id"]))
+
+ return result
diff --git a/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py
similarity index 59%
rename from plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py
rename to plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py
index f4749a6747..723bcf2b7c 100644
--- a/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py
+++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py
@@ -1,24 +1,32 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-
-from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
-from UM.Logger import Logger
-from UM.Application import Application
-from UM.Signal import Signal, signalemitter
-from UM.Version import Version
-
-from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
-
-from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
-from PyQt5.QtCore import QUrl
-
-from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
+import json
from queue import Queue
from threading import Event, Thread
from time import time
+import os
-import json
+from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
+from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
+from PyQt5.QtGui import QDesktopServices
+from cura.CuraApplication import CuraApplication
+from cura.PrinterOutputDevice import ConnectionType
+from cura.Settings.GlobalStack import GlobalStack # typing
+from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
+from UM.Logger import Logger
+from UM.Signal import Signal, signalemitter
+from UM.Version import Version
+from UM.Message import Message
+from UM.i18n import i18nCatalog
+
+from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
+from .Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager
+
+from typing import Optional
+
+i18n_catalog = i18nCatalog("cura")
## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
# Zero-Conf is used to detect printers, which are saved in a dict.
@@ -28,17 +36,24 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
addDeviceSignal = Signal()
removeDeviceSignal = Signal()
discoveredDevicesChanged = Signal()
+ cloudFlowIsPossible = Signal()
def __init__(self):
super().__init__()
+
self._zero_conf = None
self._zero_conf_browser = None
+ self._application = CuraApplication.getInstance()
+
+ # Create a cloud output device manager that abstracts all cloud connection logic away.
+ self._cloud_output_device_manager = CloudOutputDeviceManager()
+
# Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
self.addDeviceSignal.connect(self._onAddDevice)
self.removeDeviceSignal.connect(self._onRemoveDevice)
- Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections)
+ self._application.globalContainerStackChanged.connect(self.reCheckConnections)
self._discovered_devices = {}
@@ -46,6 +61,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._network_manager.finished.connect(self._onNetworkRequestFinished)
self._min_cluster_version = Version("4.0.0")
+ self._min_cloud_version = Version("5.2.0")
self._api_version = "1"
self._api_prefix = "/api/v" + self._api_version + "/"
@@ -53,7 +69,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
# Get list of manual instances from preferences
- self._preferences = Application.getInstance().getPreferences()
+ self._preferences = CuraApplication.getInstance().getPreferences()
self._preferences.addPreference("um3networkprinting/manual_instances",
"") # A comma-separated list of ip adresses or hostnames
@@ -71,6 +87,26 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True)
self._service_changed_request_thread.start()
+ self._account = self._application.getCuraAPI().account
+
+ # Check if cloud flow is possible when user logs in
+ self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible)
+
+ # Check if cloud flow is possible when user switches machines
+ self._application.globalContainerStackChanged.connect(self._onMachineSwitched)
+
+ # Listen for when cloud flow is possible
+ self.cloudFlowIsPossible.connect(self._onCloudFlowPossible)
+
+ # Listen if cloud cluster was added
+ self._cloud_output_device_manager.addedCloudCluster.connect(self._onCloudPrintingConfigured)
+
+ # Listen if cloud cluster was removed
+ self._cloud_output_device_manager.removedCloudCluster.connect(self.checkCloudFlowIsPossible)
+
+ self._start_cloud_flow_message = None # type: Optional[Message]
+ self._cloud_flow_complete_message = None # type: Optional[Message]
+
def getDiscoveredDevices(self):
return self._discovered_devices
@@ -83,6 +119,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
## Start looking for devices on network.
def start(self):
self.startDiscovery()
+ self._cloud_output_device_manager.start()
def startDiscovery(self):
self.stop()
@@ -104,7 +141,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self.resetLastManualDevice()
def reCheckConnections(self):
- active_machine = Application.getInstance().getGlobalContainerStack()
+ active_machine = CuraApplication.getInstance().getGlobalContainerStack()
if not active_machine:
return
@@ -114,6 +151,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
if key == um_network_key:
if not self._discovered_devices[key].isConnected():
Logger.log("d", "Attempting to connect with [%s]" % key)
+ # It should already be set, but if it actually connects we know for sure it's supported!
+ active_machine.addConfiguredConnectionType(self._discovered_devices[key].connectionType.value)
self._discovered_devices[key].connect()
self._discovered_devices[key].connectionStateChanged.connect(self._onDeviceConnectionStateChanged)
else:
@@ -129,9 +168,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
return
if self._discovered_devices[key].isConnected():
# Sometimes the status changes after changing the global container and maybe the device doesn't belong to this machine
- um_network_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key")
+ um_network_key = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key")
if key == um_network_key:
self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key])
+ self.checkCloudFlowIsPossible()
else:
self.getOutputDeviceManager().removeOutputDevice(key)
@@ -139,6 +179,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
if self._zero_conf is not None:
Logger.log("d", "zeroconf close...")
self._zero_conf.close()
+ self._cloud_output_device_manager.stop()
def removeManualDevice(self, key, address = None):
if key in self._discovered_devices:
@@ -238,7 +279,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
properties = device.getProperties().copy()
if b"incomplete" in properties:
del properties[b"incomplete"]
- properties[b'cluster_size'] = len(cluster_printers_list)
+ properties[b"cluster_size"] = len(cluster_printers_list)
self._onRemoveDevice(instance_name)
self._onAddDevice(instance_name, address, properties)
@@ -260,6 +301,19 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
# or "Legacy" UM3 device.
cluster_size = int(properties.get(b"cluster_size", -1))
+ printer_type = properties.get(b"machine", b"").decode("utf-8")
+ printer_type_identifiers = {
+ "9066": "ultimaker3",
+ "9511": "ultimaker3_extended",
+ "9051": "ultimaker_s5"
+ }
+
+ for key, value in printer_type_identifiers.items():
+ if printer_type.startswith(key):
+ properties[b"printer_type"] = bytes(value, encoding="utf8")
+ break
+ else:
+ properties[b"printer_type"] = b"Unknown"
if cluster_size >= 0:
device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties)
else:
@@ -268,8 +322,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._discovered_devices[device.getId()] = device
self.discoveredDevicesChanged.emit()
- global_container_stack = Application.getInstance().getGlobalContainerStack()
+ global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"):
+ # Ensure that the configured connection type is set.
+ global_container_stack.addConfiguredConnectionType(device.connectionType.value)
device.connect()
device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged)
@@ -286,7 +342,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._service_changed_request_event.wait(timeout = 5.0)
# Stop if the application is shutting down
- if Application.getInstance().isShuttingDown():
+ if CuraApplication.getInstance().isShuttingDown():
return
self._service_changed_request_event.clear()
@@ -312,13 +368,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
## Handler for zeroConf detection.
# Return True or False indicating if the process succeeded.
- # Note that this function can take over 3 seconds to complete. Be carefull calling it from the main thread.
+ # Note that this function can take over 3 seconds to complete. Be careful
+ # calling it from the main thread.
def _onServiceChanged(self, zero_conf, service_type, name, state_change):
if state_change == ServiceStateChange.Added:
- Logger.log("d", "Bonjour service added: %s" % name)
-
# First try getting info from zero-conf cache
- info = ServiceInfo(service_type, name, properties={})
+ info = ServiceInfo(service_type, name, properties = {})
for record in zero_conf.cache.entries_with_name(name.lower()):
info.update_record(zero_conf, time(), record)
@@ -329,7 +384,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
# Request more data if info is not complete
if not info.address:
- Logger.log("d", "Trying to get address of %s", name)
info = zero_conf.get_service_info(service_type, name)
if info:
@@ -349,4 +403,125 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
Logger.log("d", "Bonjour service removed: %s" % name)
self.removeDeviceSignal.emit(str(name))
- return True
\ No newline at end of file
+ return True
+
+ ## Check if the prerequsites are in place to start the cloud flow
+ def checkCloudFlowIsPossible(self) -> None:
+ Logger.log("d", "Checking if cloud connection is possible...")
+
+ # Pre-Check: Skip if active machine already has been cloud connected or you said don't ask again
+ active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"]
+ if active_machine:
+
+ # Check 1A: Printer isn't already configured for cloud
+ if ConnectionType.CloudConnection.value in active_machine.configuredConnectionTypes:
+ Logger.log("d", "Active machine was already configured for cloud.")
+ return
+
+ # Check 1B: Printer isn't already configured for cloud
+ if active_machine.getMetaDataEntry("cloud_flow_complete", False):
+ Logger.log("d", "Active machine was already configured for cloud.")
+ return
+
+ # Check 2: User did not already say "Don't ask me again"
+ if active_machine.getMetaDataEntry("do_not_show_cloud_message", False):
+ Logger.log("d", "Active machine shouldn't ask about cloud anymore.")
+ return
+
+ # Check 3: User is logged in with an Ultimaker account
+ if not self._account.isLoggedIn:
+ Logger.log("d", "Cloud Flow not possible: User not logged in!")
+ return
+
+ # Check 4: Machine is configured for network connectivity
+ if not self._application.getMachineManager().activeMachineHasNetworkConnection:
+ Logger.log("d", "Cloud Flow not possible: Machine is not connected!")
+ return
+
+ # Check 5: Machine has correct firmware version
+ firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion # type: str
+ if not Version(firmware_version) > self._min_cloud_version:
+ Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)",
+ firmware_version,
+ self._min_cloud_version)
+ return
+
+ Logger.log("d", "Cloud flow is possible!")
+ self.cloudFlowIsPossible.emit()
+
+ def _onCloudFlowPossible(self) -> None:
+ # Cloud flow is possible, so show the message
+ if not self._start_cloud_flow_message:
+ self._start_cloud_flow_message = Message(
+ text = i18n_catalog.i18nc("@info:status", "Send and monitor print jobs from anywhere using your Ultimaker account."),
+ lifetime = 0,
+ image_source = QUrl.fromLocalFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..",
+ "resources", "svg", "cloud-flow-start.svg")),
+ image_caption = i18n_catalog.i18nc("@info:status", "Connect to Ultimaker Cloud"),
+ option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."),
+ option_state = False
+ )
+ self._start_cloud_flow_message.addAction("", i18n_catalog.i18nc("@action", "Get started"), "", "")
+ self._start_cloud_flow_message.optionToggled.connect(self._onDontAskMeAgain)
+ self._start_cloud_flow_message.actionTriggered.connect(self._onCloudFlowStarted)
+ self._start_cloud_flow_message.show()
+ return
+
+ def _onCloudPrintingConfigured(self) -> None:
+ if self._start_cloud_flow_message:
+ self._start_cloud_flow_message.hide()
+ self._start_cloud_flow_message = None
+
+ # Show the successful pop-up
+ if not self._start_cloud_flow_message:
+ self._cloud_flow_complete_message = Message(
+ text = i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."),
+ lifetime = 30,
+ image_source = QUrl.fromLocalFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..",
+ "resources", "svg", "cloud-flow-completed.svg")),
+ image_caption = i18n_catalog.i18nc("@info:status", "Connected!")
+ )
+ # Don't show the review connection link if we're not on the local network
+ if self._application.getMachineManager().activeMachineHasNetworkConnection:
+ self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon
+ self._cloud_flow_complete_message.actionTriggered.connect(self._onReviewCloudConnection)
+ self._cloud_flow_complete_message.show()
+
+ # Set the machine's cloud flow as complete so we don't ask the user again and again for cloud connected printers
+ active_machine = self._application.getMachineManager().activeMachine
+ if active_machine:
+ active_machine.setMetaDataEntry("do_not_show_cloud_message", True)
+ return
+
+ def _onDontAskMeAgain(self, checked: bool) -> None:
+ active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"]
+ if active_machine:
+ active_machine.setMetaDataEntry("do_not_show_cloud_message", checked)
+ if checked:
+ Logger.log("d", "Will not ask the user again to cloud connect for current printer.")
+ return
+
+ def _onCloudFlowStarted(self, messageId: str, actionId: str) -> None:
+ address = self._application.getMachineManager().activeMachineAddress # type: str
+ if address:
+ QDesktopServices.openUrl(QUrl("http://" + address + "/cloud_connect"))
+ if self._start_cloud_flow_message:
+ self._start_cloud_flow_message.hide()
+ self._start_cloud_flow_message = None
+ return
+
+ def _onReviewCloudConnection(self, messageId: str, actionId: str) -> None:
+ address = self._application.getMachineManager().activeMachineAddress # type: str
+ if address:
+ QDesktopServices.openUrl(QUrl("http://" + address + "/settings"))
+ return
+
+ def _onMachineSwitched(self) -> None:
+ if self._start_cloud_flow_message is not None:
+ self._start_cloud_flow_message.hide()
+ self._start_cloud_flow_message = None
+ if self._cloud_flow_complete_message is not None:
+ self._cloud_flow_complete_message.hide()
+ self._cloud_flow_complete_message = None
+
+ self.checkCloudFlowIsPossible()
diff --git a/plugins/UM3NetworkPrinting/src/UM3PrintJobOutputModel.py b/plugins/UM3NetworkPrinting/src/UM3PrintJobOutputModel.py
new file mode 100644
index 0000000000..4f44ca4af8
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/UM3PrintJobOutputModel.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import List
+
+from PyQt5.QtCore import pyqtProperty, pyqtSignal
+
+from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+from .ConfigurationChangeModel import ConfigurationChangeModel
+
+
+class UM3PrintJobOutputModel(PrintJobOutputModel):
+ configurationChangesChanged = pyqtSignal()
+
+ def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
+ super().__init__(output_controller, key, name, parent)
+ self._configuration_changes = [] # type: List[ConfigurationChangeModel]
+
+ @pyqtProperty("QVariantList", notify=configurationChangesChanged)
+ def configurationChanges(self) -> List[ConfigurationChangeModel]:
+ return self._configuration_changes
+
+ def updateConfigurationChanges(self, changes: List[ConfigurationChangeModel]) -> None:
+ if len(self._configuration_changes) == 0 and len(changes) == 0:
+ return
+ self._configuration_changes = changes
+ self.configurationChangesChanged.emit()
diff --git a/plugins/UM3NetworkPrinting/src/__init__.py b/plugins/UM3NetworkPrinting/src/__init__.py
new file mode 100644
index 0000000000..659263c0d6
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/src/__init__.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2019 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+# Workaround for a race condition on certain systems where there
+# is a race condition between Arcus and PyQt. Importing Arcus
+# first seems to prevent Sip from going into a state where it
+# tries to create PyQt objects on a non-main thread.
+import Arcus #@UnusedImport
+import Savitar #@UnusedImport
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/__init__.py b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/__init__.py
new file mode 100644
index 0000000000..777afc92c2
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+import json
+import os
+
+
+def readFixture(fixture_name: str) -> bytes:
+ with open("{}/{}.json".format(os.path.dirname(__file__), fixture_name), "rb") as f:
+ return f.read()
+
+def parseFixture(fixture_name: str) -> dict:
+ return json.loads(readFixture(fixture_name).decode())
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/getClusterStatusResponse.json b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/getClusterStatusResponse.json
new file mode 100644
index 0000000000..4f9f47fc75
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/getClusterStatusResponse.json
@@ -0,0 +1,95 @@
+{
+ "data": {
+ "generated_time": "2018-12-10T08:23:55.110Z",
+ "printers": [
+ {
+ "configuration": [
+ {
+ "extruder_index": 0,
+ "material": {
+ "material": "empty"
+ },
+ "print_core_id": "AA 0.4"
+ },
+ {
+ "extruder_index": 1,
+ "material": {
+ "material": "empty"
+ },
+ "print_core_id": "AA 0.4"
+ }
+ ],
+ "enabled": true,
+ "firmware_version": "5.1.2.20180807",
+ "friendly_name": "Master-Luke",
+ "ip_address": "10.183.1.140",
+ "machine_variant": "Ultimaker 3",
+ "status": "maintenance",
+ "unique_name": "ultimakersystem-ccbdd30044ec",
+ "uuid": "b3a47ea3-1eeb-4323-9626-6f9c3c888f9e"
+ },
+ {
+ "configuration": [
+ {
+ "extruder_index": 0,
+ "material": {
+ "brand": "Generic",
+ "color": "Generic",
+ "guid": "506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9",
+ "material": "PLA"
+ },
+ "print_core_id": "AA 0.4"
+ },
+ {
+ "extruder_index": 1,
+ "material": {
+ "brand": "Ultimaker",
+ "color": "Red",
+ "guid": "9cfe5bf1-bdc5-4beb-871a-52c70777842d",
+ "material": "PLA"
+ },
+ "print_core_id": "AA 0.4"
+ }
+ ],
+ "enabled": true,
+ "firmware_version": "4.3.3.20180529",
+ "friendly_name": "UM-Marijn",
+ "ip_address": "10.183.1.166",
+ "machine_variant": "Ultimaker 3",
+ "status": "idle",
+ "unique_name": "ultimakersystem-ccbdd30058ab",
+ "uuid": "6e62c40a-4601-4b0e-9fec-c7c02c59c30a"
+ }
+ ],
+ "print_jobs": [
+ {
+ "assigned_to": "6e62c40a-4601-4b0e-9fec-c7c02c59c30a",
+ "configuration": [
+ {
+ "extruder_index": 0,
+ "material": {
+ "brand": "Ultimaker",
+ "color": "Black",
+ "guid": "3ee70a86-77d8-4b87-8005-e4a1bc57d2ce",
+ "material": "PLA"
+ },
+ "print_core_id": "AA 0.4"
+ }
+ ],
+ "constraints": {},
+ "created_at": "2018-12-10T08:28:04.108Z",
+ "force": false,
+ "last_seen": 500165.109491861,
+ "machine_variant": "Ultimaker 3",
+ "name": "UM3_dragon",
+ "network_error_count": 0,
+ "owner": "Daniel Testing",
+ "started": false,
+ "status": "queued",
+ "time_elapsed": 0,
+ "time_total": 14145,
+ "uuid": "d1c8bd52-5e9f-486a-8c25-a123cc8c7702"
+ }
+ ]
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/getClusters.json b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/getClusters.json
new file mode 100644
index 0000000000..5200e3b971
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/getClusters.json
@@ -0,0 +1,17 @@
+{
+ "data": [{
+ "cluster_id": "RIZ6cZbWA_Ua7RZVJhrdVfVpf0z-MqaSHQE4v8aRTtYq",
+ "host_guid": "e90ae0ac-1257-4403-91ee-a44c9b7e8050",
+ "host_name": "ultimakersystem-ccbdd30044ec",
+ "host_version": "5.0.0.20170101",
+ "is_online": true,
+ "status": "active"
+ }, {
+ "cluster_id": "NWKV6vJP_LdYsXgXqAcaNCR0YcLJwar1ugh0ikEZsZs8",
+ "host_guid": "e0ace90a-91ee-1257-4403-e8050a44c9b7",
+ "host_name": "ultimakersystem-30044ecccbdd",
+ "host_version": "5.1.2.20180807",
+ "is_online": true,
+ "status": "active"
+ }]
+}
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/postJobPrintResponse.json b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/postJobPrintResponse.json
new file mode 100644
index 0000000000..caedcd8732
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/postJobPrintResponse.json
@@ -0,0 +1,8 @@
+{
+ "data": {
+ "cluster_job_id": "9a59d8e9-91d3-4ff6-b4cb-9db91c4094dd",
+ "job_id": "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=",
+ "status": "queued",
+ "generated_time": "2018-12-10T08:23:55.110Z"
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/putJobUploadResponse.json b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/putJobUploadResponse.json
new file mode 100644
index 0000000000..1304f3a9f6
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/Fixtures/putJobUploadResponse.json
@@ -0,0 +1,9 @@
+{
+ "data": {
+ "content_type": "text/plain",
+ "job_id": "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=",
+ "job_name": "Ultimaker Robot v3.0",
+ "status": "uploading",
+ "upload_url": "https://api.ultimaker.com/print-job-upload"
+ }
+}
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/Models/__init__.py b/plugins/UM3NetworkPrinting/tests/Cloud/Models/__init__.py
new file mode 100644
index 0000000000..f3f6970c54
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/Models/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/NetworkManagerMock.py b/plugins/UM3NetworkPrinting/tests/Cloud/NetworkManagerMock.py
new file mode 100644
index 0000000000..e504509d67
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/NetworkManagerMock.py
@@ -0,0 +1,105 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+import json
+from typing import Dict, Tuple, Union, Optional, Any
+from unittest.mock import MagicMock
+
+from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
+
+from UM.Logger import Logger
+from UM.Signal import Signal
+
+
+class FakeSignal:
+ def __init__(self):
+ self._callbacks = []
+
+ def connect(self, callback):
+ self._callbacks.append(callback)
+
+ def disconnect(self, callback):
+ self._callbacks.remove(callback)
+
+ def emit(self, *args, **kwargs):
+ for callback in self._callbacks:
+ callback(*args, **kwargs)
+
+
+## This class can be used to mock the QNetworkManager class and test the code using it.
+# After patching the QNetworkManager class, requests are prepared before they can be executed.
+# Any requests not prepared beforehand will cause KeyErrors.
+class NetworkManagerMock:
+
+ # An enumeration of the supported operations and their code for the network access manager.
+ _OPERATIONS = {
+ "GET": QNetworkAccessManager.GetOperation,
+ "POST": QNetworkAccessManager.PostOperation,
+ "PUT": QNetworkAccessManager.PutOperation,
+ "DELETE": QNetworkAccessManager.DeleteOperation,
+ "HEAD": QNetworkAccessManager.HeadOperation,
+ } # type: Dict[str, int]
+
+ ## Initializes the network manager mock.
+ def __init__(self) -> None:
+ # A dict with the prepared replies, using the format {(http_method, url): reply}
+ self.replies = {} # type: Dict[Tuple[str, str], MagicMock]
+ self.request_bodies = {} # type: Dict[Tuple[str, str], bytes]
+
+ # Signals used in the network manager.
+ self.finished = Signal()
+ self.authenticationRequired = Signal()
+
+ ## Mock implementation of the get, post, put, delete and head methods from the network manager.
+ # Since the methods are very simple and the same it didn't make sense to repeat the code.
+ # \param method: The method being called.
+ # \return The mocked function, if the method name is known. Defaults to the standard getattr function.
+ def __getattr__(self, method: str) -> Any:
+ ## This mock implementation will simply return the reply from the prepared ones.
+ # it raises a KeyError if requests are done without being prepared.
+ def doRequest(request: QNetworkRequest, body: Optional[bytes] = None, *_):
+ key = method.upper(), request.url().toString()
+ if body:
+ self.request_bodies[key] = body
+ return self.replies[key]
+
+ operation = self._OPERATIONS.get(method.upper())
+ if operation:
+ return doRequest
+
+ # the attribute is not one of the implemented methods, default to the standard implementation.
+ return getattr(super(), method)
+
+ ## Prepares a server reply for the given parameters.
+ # \param method: The HTTP method.
+ # \param url: The URL being requested.
+ # \param status_code: The HTTP status code for the response.
+ # \param response: The response body from the server (generally json-encoded).
+ def prepareReply(self, method: str, url: str, status_code: int, response: Union[bytes, dict]) -> None:
+ reply_mock = MagicMock()
+ reply_mock.url().toString.return_value = url
+ reply_mock.operation.return_value = self._OPERATIONS[method]
+ reply_mock.attribute.return_value = status_code
+ reply_mock.finished = FakeSignal()
+ reply_mock.isFinished.return_value = False
+ reply_mock.readAll.return_value = response if isinstance(response, bytes) else json.dumps(response).encode()
+ self.replies[method, url] = reply_mock
+ Logger.log("i", "Prepared mock {}-response to {} {}", status_code, method, url)
+
+ ## Gets the request that was sent to the network manager for the given method and URL.
+ # \param method: The HTTP method.
+ # \param url: The URL.
+ def getRequestBody(self, method: str, url: str) -> Optional[bytes]:
+ return self.request_bodies.get((method.upper(), url))
+
+ ## Emits the signal that the reply is ready to all prepared replies.
+ def flushReplies(self) -> None:
+ for key, reply in self.replies.items():
+ Logger.log("i", "Flushing reply to {} {}", *key)
+ reply.isFinished.return_value = True
+ reply.finished.emit()
+ self.finished.emit(reply)
+ self.reset()
+
+ ## Deletes all prepared replies
+ def reset(self) -> None:
+ self.replies.clear()
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudApiClient.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudApiClient.py
new file mode 100644
index 0000000000..b79d009c31
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudApiClient.py
@@ -0,0 +1,117 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import List
+from unittest import TestCase
+from unittest.mock import patch, MagicMock
+
+from cura.UltimakerCloudAuthentication import CuraCloudAPIRoot
+from ...src.Cloud import CloudApiClient
+from ...src.Cloud.Models.CloudClusterResponse import CloudClusterResponse
+from ...src.Cloud.Models.CloudClusterStatus import CloudClusterStatus
+from ...src.Cloud.Models.CloudPrintJobResponse import CloudPrintJobResponse
+from ...src.Cloud.Models.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest
+from ...src.Cloud.Models.CloudError import CloudError
+from .Fixtures import readFixture, parseFixture
+from .NetworkManagerMock import NetworkManagerMock
+
+
+class TestCloudApiClient(TestCase):
+ maxDiff = None
+
+ def _errorHandler(self, errors: List[CloudError]):
+ raise Exception("Received unexpected error: {}".format(errors))
+
+ def setUp(self):
+ super().setUp()
+ self.account = MagicMock()
+ self.account.isLoggedIn.return_value = True
+
+ self.network = NetworkManagerMock()
+ with patch.object(CloudApiClient, 'QNetworkAccessManager', return_value = self.network):
+ self.api = CloudApiClient.CloudApiClient(self.account, self._errorHandler)
+
+ def test_getClusters(self):
+ result = []
+
+ response = readFixture("getClusters")
+ data = parseFixture("getClusters")["data"]
+
+ self.network.prepareReply("GET", CuraCloudAPIRoot + "/connect/v1/clusters", 200, response)
+ # The callback is a function that adds the result of the call to getClusters to the result list
+ self.api.getClusters(lambda clusters: result.extend(clusters))
+
+ self.network.flushReplies()
+
+ self.assertEqual([CloudClusterResponse(**data[0]), CloudClusterResponse(**data[1])], result)
+
+ def test_getClusterStatus(self):
+ result = []
+
+ response = readFixture("getClusterStatusResponse")
+ data = parseFixture("getClusterStatusResponse")["data"]
+
+ url = CuraCloudAPIRoot + "/connect/v1/clusters/R0YcLJwar1ugh0ikEZsZs8NWKV6vJP_LdYsXgXqAcaNC/status"
+ self.network.prepareReply("GET", url, 200, response)
+ self.api.getClusterStatus("R0YcLJwar1ugh0ikEZsZs8NWKV6vJP_LdYsXgXqAcaNC", lambda s: result.append(s))
+
+ self.network.flushReplies()
+
+ self.assertEqual([CloudClusterStatus(**data)], result)
+
+ def test_requestUpload(self):
+
+ results = []
+
+ response = readFixture("putJobUploadResponse")
+
+ self.network.prepareReply("PUT", CuraCloudAPIRoot + "/cura/v1/jobs/upload", 200, response)
+ request = CloudPrintJobUploadRequest(job_name = "job name", file_size = 143234, content_type = "text/plain")
+ self.api.requestUpload(request, lambda r: results.append(r))
+ self.network.flushReplies()
+
+ self.assertEqual(["text/plain"], [r.content_type for r in results])
+ self.assertEqual(["uploading"], [r.status for r in results])
+
+ def test_uploadToolPath(self):
+
+ results = []
+ progress = MagicMock()
+
+ data = parseFixture("putJobUploadResponse")["data"]
+ upload_response = CloudPrintJobResponse(**data)
+
+ # Network client doesn't look into the reply
+ self.network.prepareReply("PUT", upload_response.upload_url, 200, b'{}')
+
+ mesh = ("1234" * 100000).encode()
+ self.api.uploadToolPath(upload_response, mesh, lambda: results.append("sent"), progress.advance, progress.error)
+
+ for _ in range(10):
+ self.network.flushReplies()
+ self.network.prepareReply("PUT", upload_response.upload_url, 200, b'{}')
+
+ self.assertEqual(["sent"], results)
+
+ def test_requestPrint(self):
+
+ results = []
+
+ response = readFixture("postJobPrintResponse")
+
+ cluster_id = "NWKV6vJP_LdYsXgXqAcaNCR0YcLJwar1ugh0ikEZsZs8"
+ cluster_job_id = "9a59d8e9-91d3-4ff6-b4cb-9db91c4094dd"
+ job_id = "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE="
+
+ self.network.prepareReply("POST",
+ CuraCloudAPIRoot + "/connect/v1/clusters/{}/print/{}"
+ .format(cluster_id, job_id),
+ 200, response)
+
+ self.api.requestPrint(cluster_id, job_id, lambda r: results.append(r))
+
+ self.network.flushReplies()
+
+ self.assertEqual([job_id], [r.job_id for r in results])
+ self.assertEqual([cluster_job_id], [r.cluster_job_id for r in results])
+ self.assertEqual(["queued"], [r.status for r in results])
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py
new file mode 100644
index 0000000000..c4d891302e
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py
@@ -0,0 +1,155 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+import json
+from unittest import TestCase
+from unittest.mock import patch, MagicMock
+
+from UM.Scene.SceneNode import SceneNode
+from cura.UltimakerCloudAuthentication import CuraCloudAPIRoot
+from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+from ...src.Cloud import CloudApiClient
+from ...src.Cloud.CloudOutputDevice import CloudOutputDevice
+from ...src.Cloud.Models.CloudClusterResponse import CloudClusterResponse
+from .Fixtures import readFixture, parseFixture
+from .NetworkManagerMock import NetworkManagerMock
+
+
+class TestCloudOutputDevice(TestCase):
+ maxDiff = None
+
+ CLUSTER_ID = "RIZ6cZbWA_Ua7RZVJhrdVfVpf0z-MqaSHQE4v8aRTtYq"
+ JOB_ID = "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE="
+ HOST_NAME = "ultimakersystem-ccbdd30044ec"
+ HOST_GUID = "e90ae0ac-1257-4403-91ee-a44c9b7e8050"
+ HOST_VERSION = "5.2.0"
+
+ STATUS_URL = "{}/connect/v1/clusters/{}/status".format(CuraCloudAPIRoot, CLUSTER_ID)
+ PRINT_URL = "{}/connect/v1/clusters/{}/print/{}".format(CuraCloudAPIRoot, CLUSTER_ID, JOB_ID)
+ REQUEST_UPLOAD_URL = "{}/cura/v1/jobs/upload".format(CuraCloudAPIRoot)
+
+ def setUp(self):
+ super().setUp()
+ self.app = MagicMock()
+
+ self.patches = [patch("UM.Qt.QtApplication.QtApplication.getInstance", return_value=self.app),
+ patch("UM.Application.Application.getInstance", return_value=self.app)]
+ for patched_method in self.patches:
+ patched_method.start()
+
+ self.cluster = CloudClusterResponse(self.CLUSTER_ID, self.HOST_GUID, self.HOST_NAME, is_online=True,
+ status="active", host_version=self.HOST_VERSION)
+
+ self.network = NetworkManagerMock()
+ self.account = MagicMock(isLoggedIn=True, accessToken="TestAccessToken")
+ self.onError = MagicMock()
+ with patch.object(CloudApiClient, "QNetworkAccessManager", return_value = self.network):
+ self._api = CloudApiClient.CloudApiClient(self.account, self.onError)
+
+ self.device = CloudOutputDevice(self._api, self.cluster)
+ self.cluster_status = parseFixture("getClusterStatusResponse")
+ self.network.prepareReply("GET", self.STATUS_URL, 200, readFixture("getClusterStatusResponse"))
+
+ def tearDown(self):
+ try:
+ super().tearDown()
+ self.network.flushReplies()
+ finally:
+ for patched_method in self.patches:
+ patched_method.stop()
+
+ # We test for these in order to make sure the correct file type is selected depending on the firmware version.
+ def test_properties(self):
+ self.assertEqual(self.device.firmwareVersion, self.HOST_VERSION)
+ self.assertEqual(self.device.name, self.HOST_NAME)
+
+ def test_status(self):
+ self.device._update()
+ self.network.flushReplies()
+
+ self.assertEqual([PrinterOutputModel, PrinterOutputModel], [type(printer) for printer in self.device.printers])
+
+ controller_fields = {
+ "_output_device": self.device,
+ "can_abort": False,
+ "can_control_manually": False,
+ "can_pause": False,
+ "can_pre_heat_bed": False,
+ "can_pre_heat_hotends": False,
+ "can_send_raw_gcode": False,
+ "can_update_firmware": False,
+ }
+
+ self.assertEqual({printer["uuid"] for printer in self.cluster_status["data"]["printers"]},
+ {printer.key for printer in self.device.printers})
+ self.assertEqual([controller_fields, controller_fields],
+ [printer.getController().__dict__ for printer in self.device.printers])
+
+ self.assertEqual(["UM3PrintJobOutputModel"], [type(printer).__name__ for printer in self.device.printJobs])
+ self.assertEqual({job["uuid"] for job in self.cluster_status["data"]["print_jobs"]},
+ {job.key for job in self.device.printJobs})
+ self.assertEqual({job["owner"] for job in self.cluster_status["data"]["print_jobs"]},
+ {job.owner for job in self.device.printJobs})
+ self.assertEqual({job["name"] for job in self.cluster_status["data"]["print_jobs"]},
+ {job.name for job in self.device.printJobs})
+
+ def test_remove_print_job(self):
+ self.device._update()
+ self.network.flushReplies()
+ self.assertEqual(1, len(self.device.printJobs))
+
+ self.cluster_status["data"]["print_jobs"].clear()
+ self.network.prepareReply("GET", self.STATUS_URL, 200, self.cluster_status)
+
+ self.device._last_request_time = None
+ self.device._update()
+ self.network.flushReplies()
+ self.assertEqual([], self.device.printJobs)
+
+ def test_remove_printers(self):
+ self.device._update()
+ self.network.flushReplies()
+ self.assertEqual(2, len(self.device.printers))
+
+ self.cluster_status["data"]["printers"].clear()
+ self.network.prepareReply("GET", self.STATUS_URL, 200, self.cluster_status)
+
+ self.device._last_request_time = None
+ self.device._update()
+ self.network.flushReplies()
+ self.assertEqual([], self.device.printers)
+
+ def test_print_to_cloud(self):
+ active_machine_mock = self.app.getGlobalContainerStack.return_value
+ active_machine_mock.getMetaDataEntry.side_effect = {"file_formats": "application/x-ufp"}.get
+
+ request_upload_response = parseFixture("putJobUploadResponse")
+ request_print_response = parseFixture("postJobPrintResponse")
+ self.network.prepareReply("PUT", self.REQUEST_UPLOAD_URL, 201, request_upload_response)
+ self.network.prepareReply("PUT", request_upload_response["data"]["upload_url"], 201, b"{}")
+ self.network.prepareReply("POST", self.PRINT_URL, 200, request_print_response)
+
+ file_handler = MagicMock()
+ file_handler.getSupportedFileTypesWrite.return_value = [{
+ "extension": "ufp",
+ "mime_type": "application/x-ufp",
+ "mode": 2
+ }, {
+ "extension": "gcode.gz",
+ "mime_type": "application/gzip",
+ "mode": 2,
+ }]
+ file_handler.getWriterByMimeType.return_value.write.side_effect = \
+ lambda stream, nodes: stream.write(str(nodes).encode())
+
+ scene_nodes = [SceneNode()]
+ expected_mesh = str(scene_nodes).encode()
+ self.device.requestWrite(scene_nodes, file_handler=file_handler, file_name="FileName")
+
+ self.network.flushReplies()
+ self.assertEqual(
+ {"data": {"content_type": "application/x-ufp", "file_size": len(expected_mesh), "job_name": "FileName"}},
+ json.loads(self.network.getRequestBody("PUT", self.REQUEST_UPLOAD_URL).decode())
+ )
+ self.assertEqual(expected_mesh,
+ self.network.getRequestBody("PUT", request_upload_response["data"]["upload_url"]))
+ self.assertIsNone(self.network.getRequestBody("POST", self.PRINT_URL))
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py
new file mode 100644
index 0000000000..e24ca1694e
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py
@@ -0,0 +1,123 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from unittest import TestCase
+from unittest.mock import patch, MagicMock
+
+from UM.OutputDevice.OutputDeviceManager import OutputDeviceManager
+from cura.UltimakerCloudAuthentication import CuraCloudAPIRoot
+from ...src.Cloud import CloudApiClient
+from ...src.Cloud import CloudOutputDeviceManager
+from .Fixtures import parseFixture, readFixture
+from .NetworkManagerMock import NetworkManagerMock, FakeSignal
+
+
+class TestCloudOutputDeviceManager(TestCase):
+ maxDiff = None
+
+ URL = CuraCloudAPIRoot + "/connect/v1/clusters"
+
+ def setUp(self):
+ super().setUp()
+ self.app = MagicMock()
+ self.device_manager = OutputDeviceManager()
+ self.app.getOutputDeviceManager.return_value = self.device_manager
+
+ self.patches = [patch("UM.Qt.QtApplication.QtApplication.getInstance", return_value=self.app),
+ patch("UM.Application.Application.getInstance", return_value=self.app)]
+ for patched_method in self.patches:
+ patched_method.start()
+
+ self.network = NetworkManagerMock()
+ self.timer = MagicMock(timeout = FakeSignal())
+ with patch.object(CloudApiClient, "QNetworkAccessManager", return_value = self.network), \
+ patch.object(CloudOutputDeviceManager, "QTimer", return_value = self.timer):
+ self.manager = CloudOutputDeviceManager.CloudOutputDeviceManager()
+ self.clusters_response = parseFixture("getClusters")
+ self.network.prepareReply("GET", self.URL, 200, readFixture("getClusters"))
+
+ def tearDown(self):
+ try:
+ self._beforeTearDown()
+
+ self.network.flushReplies()
+ self.manager.stop()
+ for patched_method in self.patches:
+ patched_method.stop()
+ finally:
+ super().tearDown()
+
+ ## Before tear down method we check whether the state of the output device manager is what we expect based on the
+ # mocked API response.
+ def _beforeTearDown(self):
+ # let the network send replies
+ self.network.flushReplies()
+ # get the created devices
+ devices = self.device_manager.getOutputDevices()
+ # TODO: Check active device
+
+ response_clusters = self.clusters_response.get("data", [])
+ manager_clusters = sorted([device.clusterData.toDict() for device in self.manager._remote_clusters.values()],
+ key=lambda cluster: cluster['cluster_id'], reverse=True)
+ self.assertEqual(response_clusters, manager_clusters)
+
+ ## Runs the initial request to retrieve the clusters.
+ def _loadData(self):
+ self.manager.start()
+ self.network.flushReplies()
+
+ def test_device_is_created(self):
+ # just create the cluster, it is checked at tearDown
+ self._loadData()
+
+ def test_device_is_updated(self):
+ self._loadData()
+
+ # update the cluster from member variable, which is checked at tearDown
+ self.clusters_response["data"][0]["host_name"] = "New host name"
+ self.network.prepareReply("GET", self.URL, 200, self.clusters_response)
+
+ self.manager._update_timer.timeout.emit()
+
+ def test_device_is_removed(self):
+ self._loadData()
+
+ # delete the cluster from member variable, which is checked at tearDown
+ del self.clusters_response["data"][1]
+ self.network.prepareReply("GET", self.URL, 200, self.clusters_response)
+
+ self.manager._update_timer.timeout.emit()
+
+ def test_device_connects_by_cluster_id(self):
+ active_machine_mock = self.app.getGlobalContainerStack.return_value
+ cluster1, cluster2 = self.clusters_response["data"]
+ cluster_id = cluster1["cluster_id"]
+ active_machine_mock.getMetaDataEntry.side_effect = {"um_cloud_cluster_id": cluster_id}.get
+
+ self._loadData()
+
+ self.assertTrue(self.device_manager.getOutputDevice(cluster1["cluster_id"]).isConnected())
+ self.assertIsNone(self.device_manager.getOutputDevice(cluster2["cluster_id"]))
+ self.assertEquals([], active_machine_mock.setMetaDataEntry.mock_calls)
+
+ def test_device_connects_by_network_key(self):
+ active_machine_mock = self.app.getGlobalContainerStack.return_value
+
+ cluster1, cluster2 = self.clusters_response["data"]
+ network_key = cluster2["host_name"] + ".ultimaker.local"
+ active_machine_mock.getMetaDataEntry.side_effect = {"um_network_key": network_key}.get
+
+ self._loadData()
+
+ self.assertIsNone(self.device_manager.getOutputDevice(cluster1["cluster_id"]))
+ self.assertTrue(self.device_manager.getOutputDevice(cluster2["cluster_id"]).isConnected())
+
+ active_machine_mock.setMetaDataEntry.assert_called_with("um_cloud_cluster_id", cluster2["cluster_id"])
+
+ @patch.object(CloudOutputDeviceManager, "Message")
+ def test_api_error(self, message_mock):
+ self.clusters_response = {
+ "errors": [{"id": "notFound", "title": "Not found!", "http_status": "404", "code": "notFound"}]
+ }
+ self.network.prepareReply("GET", self.URL, 200, self.clusters_response)
+ self._loadData()
+ message_mock.return_value.show.assert_called_once_with()
diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/__init__.py b/plugins/UM3NetworkPrinting/tests/Cloud/__init__.py
new file mode 100644
index 0000000000..f3f6970c54
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/Cloud/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
diff --git a/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py b/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py
new file mode 100644
index 0000000000..952d38dcf4
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py
@@ -0,0 +1,244 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+import io
+import json
+from unittest import TestCase, mock
+from unittest.mock import patch, call, MagicMock
+
+from PyQt5.QtCore import QByteArray
+
+from UM.Application import Application
+
+from cura.Machines.MaterialGroup import MaterialGroup
+from cura.Machines.MaterialNode import MaterialNode
+
+from ..src.SendMaterialJob import SendMaterialJob
+
+_FILES_MAP = {"generic_pla_white": "/materials/generic_pla_white.xml.fdm_material",
+ "generic_pla_black": "/materials/generic_pla_black.xml.fdm_material",
+ }
+
+
+@patch("builtins.open", lambda _, __: io.StringIO(""))
+class TestSendMaterialJob(TestCase):
+ # version 1
+ _LOCAL_MATERIAL_WHITE = {"type": "material", "status": "unknown", "id": "generic_pla_white",
+ "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
+ "brand": "Generic", "material": "PLA", "color_name": "White",
+ "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "1", "color_code": "#ffffff",
+ "description": "Test PLA White", "adhesion_info": "Use glue.", "approximate_diameter": "3",
+ "properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
+ "definition": "fdmprinter", "compatible": True}
+
+ # version 2
+ _LOCAL_MATERIAL_WHITE_NEWER = {"type": "material", "status": "unknown", "id": "generic_pla_white",
+ "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
+ "brand": "Generic", "material": "PLA", "color_name": "White",
+ "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "2",
+ "color_code": "#ffffff",
+ "description": "Test PLA White", "adhesion_info": "Use glue.",
+ "approximate_diameter": "3",
+ "properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
+ "definition": "fdmprinter", "compatible": True}
+
+ # invalid version: "one"
+ _LOCAL_MATERIAL_WHITE_INVALID_VERSION = {"type": "material", "status": "unknown", "id": "generic_pla_white",
+ "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
+ "brand": "Generic", "material": "PLA", "color_name": "White",
+ "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "one",
+ "color_code": "#ffffff",
+ "description": "Test PLA White", "adhesion_info": "Use glue.",
+ "approximate_diameter": "3",
+ "properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
+ "definition": "fdmprinter", "compatible": True}
+
+ _LOCAL_MATERIAL_WHITE_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
+ MaterialNode(_LOCAL_MATERIAL_WHITE))}
+
+ _LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
+ MaterialNode(_LOCAL_MATERIAL_WHITE_NEWER))}
+
+ _LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
+ MaterialNode(_LOCAL_MATERIAL_WHITE_INVALID_VERSION))}
+
+ _LOCAL_MATERIAL_BLACK = {"type": "material", "status": "unknown", "id": "generic_pla_black",
+ "base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE",
+ "brand": "Ultimaker", "material": "CPE", "color_name": "Black",
+ "GUID": "5fbb362a-41f9-4818-bb43-15ea6df34aa4", "version": "1", "color_code": "#000000",
+ "description": "Test PLA Black", "adhesion_info": "Use glue.", "approximate_diameter": "3",
+ "properties": {"density": "1.01", "diameter": "2.85", "weight": "750"},
+ "definition": "fdmprinter", "compatible": True}
+
+ _LOCAL_MATERIAL_BLACK_ALL_RESULT = {"generic_pla_black": MaterialGroup("generic_pla_black",
+ MaterialNode(_LOCAL_MATERIAL_BLACK))}
+
+ _REMOTE_MATERIAL_WHITE = {
+ "guid": "badb0ee7-87c8-4f3f-9398-938587b67dce",
+ "material": "PLA",
+ "brand": "Generic",
+ "version": 1,
+ "color": "White",
+ "density": 1.00
+ }
+
+ _REMOTE_MATERIAL_BLACK = {
+ "guid": "5fbb362a-41f9-4818-bb43-15ea6df34aa4",
+ "material": "PLA",
+ "brand": "Generic",
+ "version": 2,
+ "color": "Black",
+ "density": 1.00
+ }
+
+ def test_run(self):
+ device_mock = MagicMock()
+ job = SendMaterialJob(device_mock)
+ job.run()
+
+ # We expect the materials endpoint to be called when the job runs.
+ device_mock.get.assert_called_with("materials/", on_finished = job._onGetRemoteMaterials)
+
+ def test__onGetRemoteMaterials_withFailedRequest(self):
+ reply_mock = MagicMock()
+ device_mock = MagicMock()
+ reply_mock.attribute.return_value = 404
+ job = SendMaterialJob(device_mock)
+ job._onGetRemoteMaterials(reply_mock)
+
+ # We expect the device not to be called for any follow up.
+ self.assertEqual(0, device_mock.createFormPart.call_count)
+
+ def test__onGetRemoteMaterials_withWrongEncoding(self):
+ reply_mock = MagicMock()
+ device_mock = MagicMock()
+ reply_mock.attribute.return_value = 200
+ reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500"))
+ job = SendMaterialJob(device_mock)
+ job._onGetRemoteMaterials(reply_mock)
+
+ # Given that the parsing fails we do no expect the device to be called for any follow up.
+ self.assertEqual(0, device_mock.createFormPart.call_count)
+
+ def test__onGetRemoteMaterials_withBadJsonAnswer(self):
+ reply_mock = MagicMock()
+ device_mock = MagicMock()
+ reply_mock.attribute.return_value = 200
+ reply_mock.readAll.return_value = QByteArray(b"Six sick hicks nick six slick bricks with picks and sticks.")
+ job = SendMaterialJob(device_mock)
+ job._onGetRemoteMaterials(reply_mock)
+
+ # Given that the parsing fails we do no expect the device to be called for any follow up.
+ self.assertEqual(0, device_mock.createFormPart.call_count)
+
+ def test__onGetRemoteMaterials_withMissingGuidInRemoteMaterial(self):
+ reply_mock = MagicMock()
+ device_mock = MagicMock()
+ reply_mock.attribute.return_value = 200
+ remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy()
+ del remote_material_without_guid["guid"]
+ reply_mock.readAll.return_value = QByteArray(json.dumps([remote_material_without_guid]).encode("ascii"))
+ job = SendMaterialJob(device_mock)
+ job._onGetRemoteMaterials(reply_mock)
+
+ # Given that parsing fails we do not expect the device to be called for any follow up.
+ self.assertEqual(0, device_mock.createFormPart.call_count)
+
+ @patch("cura.Machines.MaterialManager.MaterialManager")
+ @patch("cura.Settings.CuraContainerRegistry")
+ @patch("UM.Application")
+ def test__onGetRemoteMaterials_withInvalidVersionInLocalMaterial(self, application_mock, container_registry_mock,
+ material_manager_mock):
+ reply_mock = MagicMock()
+ device_mock = MagicMock()
+ application_mock.getContainerRegistry.return_value = container_registry_mock
+ application_mock.getMaterialManager.return_value = material_manager_mock
+
+ reply_mock.attribute.return_value = 200
+ reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
+
+ material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT.copy()
+
+ with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
+ job = SendMaterialJob(device_mock)
+ job._onGetRemoteMaterials(reply_mock)
+
+ self.assertEqual(0, device_mock.createFormPart.call_count)
+
+ @patch("UM.Application.Application.getInstance")
+ def test__onGetRemoteMaterials_withNoUpdate(self, application_mock):
+ reply_mock = MagicMock()
+ device_mock = MagicMock()
+ container_registry_mock = application_mock.getContainerRegistry.return_value
+ material_manager_mock = application_mock.getMaterialManager.return_value
+
+ device_mock.createFormPart.return_value = "_xXx_"
+
+ material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy()
+
+ reply_mock.attribute.return_value = 200
+ reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
+
+ with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
+ job = SendMaterialJob(device_mock)
+ job._onGetRemoteMaterials(reply_mock)
+
+ self.assertEqual(0, device_mock.createFormPart.call_count)
+ self.assertEqual(0, device_mock.postFormWithParts.call_count)
+
+ @patch("UM.Application.Application.getInstance")
+ def test__onGetRemoteMaterials_withUpdatedMaterial(self, get_instance_mock):
+ reply_mock = MagicMock()
+ device_mock = MagicMock()
+ application_mock = get_instance_mock.return_value
+ container_registry_mock = application_mock.getContainerRegistry.return_value
+ material_manager_mock = application_mock.getMaterialManager.return_value
+
+ container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get(x)
+
+ device_mock.createFormPart.return_value = "_xXx_"
+
+ material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT.copy()
+
+ reply_mock.attribute.return_value = 200
+ reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
+
+ job = SendMaterialJob(device_mock)
+ job._onGetRemoteMaterials(reply_mock)
+
+ self.assertEqual(1, device_mock.createFormPart.call_count)
+ self.assertEqual(1, device_mock.postFormWithParts.call_count)
+ self.assertEquals(
+ [call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", ""),
+ call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
+ device_mock.method_calls)
+
+ @patch("UM.Application.Application.getInstance")
+ def test__onGetRemoteMaterials_withNewMaterial(self, application_mock):
+ reply_mock = MagicMock()
+ device_mock = MagicMock()
+ container_registry_mock = application_mock.getContainerRegistry.return_value
+ material_manager_mock = application_mock.getMaterialManager.return_value
+
+ container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get(x)
+
+ device_mock.createFormPart.return_value = "_xXx_"
+
+ all_results = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy()
+ for key, value in self._LOCAL_MATERIAL_BLACK_ALL_RESULT.items():
+ all_results[key] = value
+ material_manager_mock.getAllMaterialGroups.return_value = all_results
+
+ reply_mock.attribute.return_value = 200
+ reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_BLACK]).encode("ascii"))
+
+ with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
+ job = SendMaterialJob(device_mock)
+ job._onGetRemoteMaterials(reply_mock)
+
+ self.assertEqual(1, device_mock.createFormPart.call_count)
+ self.assertEqual(1, device_mock.postFormWithParts.call_count)
+ self.assertEquals(
+ [call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", ""),
+ call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
+ device_mock.method_calls)
diff --git a/plugins/UM3NetworkPrinting/tests/__init__.py b/plugins/UM3NetworkPrinting/tests/__init__.py
new file mode 100644
index 0000000000..f3f6970c54
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/tests/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
diff --git a/plugins/USBPrinting/AutoDetectBaudJob.py b/plugins/USBPrinting/AutoDetectBaudJob.py
index f8af61c567..36e9637c47 100644
--- a/plugins/USBPrinting/AutoDetectBaudJob.py
+++ b/plugins/USBPrinting/AutoDetectBaudJob.py
@@ -4,6 +4,7 @@
from UM.Job import Job
from UM.Logger import Logger
+from .avr_isp import ispBase
from .avr_isp.stk500v2 import Stk500v2
from time import time, sleep
@@ -14,12 +15,12 @@ from serial import Serial, SerialException
# It tries a pre-set list of baud rates. All these baud rates are validated by requesting the temperature a few times
# and checking if the results make sense. If getResult() is not None, it was able to find a correct baud rate.
class AutoDetectBaudJob(Job):
- def __init__(self, serial_port):
+ def __init__(self, serial_port: int) -> None:
super().__init__()
self._serial_port = serial_port
- self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
+ self._all_baud_rates = [115200, 250000, 500000, 230400, 57600, 38400, 19200, 9600]
- def run(self):
+ def run(self) -> None:
Logger.log("d", "Auto detect baud rate started.")
wait_response_timeouts = [3, 15, 30]
wait_bootloader_times = [1.5, 5, 15]
@@ -32,7 +33,7 @@ class AutoDetectBaudJob(Job):
try:
programmer.connect(self._serial_port)
serial = programmer.leaveISP()
- except:
+ except ispBase.IspError:
programmer.close()
for retry in range(tries):
@@ -58,7 +59,7 @@ class AutoDetectBaudJob(Job):
# We already have a serial connection, just change the baud rate.
try:
serial.baudrate = baud_rate
- except:
+ except ValueError:
continue
sleep(wait_bootloader) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number
successful_responses = 0
@@ -71,14 +72,15 @@ class AutoDetectBaudJob(Job):
while timeout_time > time():
line = serial.readline()
- if b"ok " in line and b"T:" in line:
+ if b"ok" in line and b"T:" in line:
successful_responses += 1
- if successful_responses >= 3:
+ if successful_responses >= 1:
self.setResult(baud_rate)
Logger.log("d", "Detected baud rate {baud_rate} on serial {serial} on retry {retry} with after {time_elapsed:0.2f} seconds.".format(
serial = self._serial_port, baud_rate = baud_rate, retry = retry, time_elapsed = time() - start_timeout_time))
+ serial.close() # close serial port so it can be opened by the USBPrinterOutputDevice
return
serial.write(b"M105\n")
- sleep(15) # Give the printer some time to init and try again.
+ sleep(15) # Give the printer some time to init and try again.
self.setResult(None) # Unable to detect the correct baudrate.
diff --git a/plugins/USBPrinting/AvrFirmwareUpdater.py b/plugins/USBPrinting/AvrFirmwareUpdater.py
new file mode 100644
index 0000000000..56e3f99c23
--- /dev/null
+++ b/plugins/USBPrinting/AvrFirmwareUpdater.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Logger import Logger
+
+from cura.CuraApplication import CuraApplication
+from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater, FirmwareUpdateState
+
+from .avr_isp import stk500v2, intelHex
+from serial import SerialException
+
+from time import sleep
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutputDevice import PrinterOutputDevice
+
+
+class AvrFirmwareUpdater(FirmwareUpdater):
+ def __init__(self, output_device: "PrinterOutputDevice") -> None:
+ super().__init__(output_device)
+
+ def _updateFirmware(self) -> None:
+ try:
+ hex_file = intelHex.readHex(self._firmware_file)
+ assert len(hex_file) > 0
+ except (FileNotFoundError, AssertionError):
+ Logger.log("e", "Unable to read provided hex file. Could not update firmware.")
+ self._setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
+ return
+
+ programmer = stk500v2.Stk500v2()
+ programmer.progress_callback = self._onFirmwareProgress
+
+ # Ensure that other connections are closed.
+ if self._output_device.isConnected():
+ self._output_device.close()
+
+ try:
+ programmer.connect(self._output_device._serial_port)
+ except:
+ programmer.close()
+ Logger.logException("e", "Failed to update firmware")
+ self._setFirmwareUpdateState(FirmwareUpdateState.communication_error)
+ return
+
+ # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
+ sleep(1)
+ if not programmer.isConnected():
+ Logger.log("e", "Unable to connect with serial. Could not update firmware")
+ self._setFirmwareUpdateState(FirmwareUpdateState.communication_error)
+ try:
+ programmer.programChip(hex_file)
+ except SerialException as e:
+ Logger.log("e", "A serial port exception occured during firmware update: %s" % e)
+ self._setFirmwareUpdateState(FirmwareUpdateState.io_error)
+ return
+ except Exception as e:
+ Logger.log("e", "An unknown exception occured during firmware update: %s" % e)
+ self._setFirmwareUpdateState(FirmwareUpdateState.unknown_error)
+ return
+
+ programmer.close()
+
+ # Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
+ CuraApplication.getInstance().callLater(self._output_device.connect)
+
+ self._cleanupAfterUpdate()
diff --git a/plugins/USBPrinting/FirmwareUpdateWindow.qml b/plugins/USBPrinting/FirmwareUpdateWindow.qml
deleted file mode 100644
index e0f9de314e..0000000000
--- a/plugins/USBPrinting/FirmwareUpdateWindow.qml
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2017 Ultimaker B.V.
-// Cura is released under the terms of the LGPLv3 or higher.
-
-import QtQuick 2.2
-import QtQuick.Window 2.2
-import QtQuick.Controls 1.2
-
-import UM 1.1 as UM
-
-UM.Dialog
-{
- id: base;
-
- width: minimumWidth;
- minimumWidth: 500 * screenScaleFactor;
- height: minimumHeight;
- minimumHeight: 100 * screenScaleFactor;
-
- visible: true;
- modality: Qt.ApplicationModal;
-
- title: catalog.i18nc("@title:window","Firmware Update");
-
- Column
- {
- anchors.fill: parent;
-
- Label
- {
- anchors
- {
- left: parent.left;
- right: parent.right;
- }
-
- text: {
- switch (manager.firmwareUpdateState)
- {
- case 0:
- return "" //Not doing anything (eg; idling)
- case 1:
- return catalog.i18nc("@label","Updating firmware.")
- case 2:
- return catalog.i18nc("@label","Firmware update completed.")
- case 3:
- return catalog.i18nc("@label","Firmware update failed due to an unknown error.")
- case 4:
- return catalog.i18nc("@label","Firmware update failed due to an communication error.")
- case 5:
- return catalog.i18nc("@label","Firmware update failed due to an input/output error.")
- case 6:
- return catalog.i18nc("@label","Firmware update failed due to missing firmware.")
- }
- }
-
- wrapMode: Text.Wrap;
- }
-
- ProgressBar
- {
- id: prog
- value: manager.firmwareProgress
- minimumValue: 0
- maximumValue: 100
- indeterminate: manager.firmwareProgress < 1 && manager.firmwareProgress > 0
- anchors
- {
- left: parent.left;
- right: parent.right;
- }
- }
-
- SystemPalette
- {
- id: palette;
- }
-
- UM.I18nCatalog { id: catalog; name: "cura"; }
- }
-
- rightButtons: [
- Button
- {
- text: catalog.i18nc("@action:button","Close");
- enabled: manager.firmwareUpdateCompleteStatus;
- onClicked: base.visible = false;
- }
- ]
-}
diff --git a/plugins/USBPrinting/MonitorItem.qml b/plugins/USBPrinting/MonitorItem.qml
new file mode 100644
index 0000000000..c86353f814
--- /dev/null
+++ b/plugins/USBPrinting/MonitorItem.qml
@@ -0,0 +1,48 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.10
+import QtQuick.Controls 2.0
+import QtQuick.Layouts 1.3
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+Component
+{
+ Item
+ {
+ Rectangle
+ {
+ color: UM.Theme.getColor("main_background")
+
+ anchors.right: parent.right
+ width: parent.width * 0.3
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+
+ Cura.PrintMonitor
+ {
+ anchors.fill: parent
+ }
+
+ Rectangle
+ {
+ id: footerSeparator
+ width: parent.width
+ height: UM.Theme.getSize("wide_lining").height
+ color: UM.Theme.getColor("wide_lining")
+ anchors.bottom: monitorButton.top
+ anchors.bottomMargin: UM.Theme.getSize("thick_margin").height
+ }
+
+ // MonitorButton is actually the bottom footer panel.
+ Cura.MonitorButton
+ {
+ id: monitorButton
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py
index 45b566fcab..752773723e 100644
--- a/plugins/USBPrinting/USBPrinterOutputDevice.py
+++ b/plugins/USBPrinting/USBPrinterOutputDevice.py
@@ -1,42 +1,35 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+import os
from UM.Logger import Logger
from UM.i18n import i18nCatalog
from UM.Qt.Duration import DurationFormat
-from UM.PluginRegistry import PluginRegistry
from cura.CuraApplication import CuraApplication
-from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
+from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.GenericOutputController import GenericOutputController
from .AutoDetectBaudJob import AutoDetectBaudJob
-from .avr_isp import stk500v2, intelHex
-
-from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl
+from .AvrFirmwareUpdater import AvrFirmwareUpdater
from serial import Serial, SerialException, SerialTimeoutException
from threading import Thread, Event
-from time import time, sleep
+from time import time
from queue import Queue
-from enum import IntEnum
from typing import Union, Optional, List, cast
import re
import functools # Used for reduce
-import os
catalog = i18nCatalog("cura")
class USBPrinterOutputDevice(PrinterOutputDevice):
- firmwareProgressChanged = pyqtSignal()
- firmwareUpdateStateChanged = pyqtSignal()
-
def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None:
- super().__init__(serial_port)
+ super().__init__(serial_port, connection_type = ConnectionType.UsbConnection)
self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
self.setShortDescription(catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print via USB"))
self.setDescription(catalog.i18nc("@info:tooltip", "Print via USB"))
@@ -56,14 +49,13 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._baud_rate = baud_rate
- self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
+ self._all_baud_rates = [115200, 250000, 500000, 230400, 57600, 38400, 19200, 9600]
# Instead of using a timer, we really need the update to be as a thread, as reading from serial can block.
- self._update_thread = Thread(target=self._update, daemon = True)
-
- self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True)
+ self._update_thread = Thread(target = self._update, daemon = True)
self._last_temperature_request = None # type: Optional[int]
+ self._firmware_idle_count = 0
self._is_printing = False # A print is being sent.
@@ -74,11 +66,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._accepts_commands = True
self._paused = False
-
- self._firmware_view = None
- self._firmware_location = None
- self._firmware_progress = 0
- self._firmware_update_state = FirmwareUpdateState.idle
+ self._printer_busy = False # When printer is preheating and waiting (M190/M109), or when waiting for action on the printer
self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
@@ -88,6 +76,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._command_received = Event()
self._command_received.set()
+ self._firmware_name_requested = False
+ self._firmware_updater = AvrFirmwareUpdater(self)
+
+ self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")
+
CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit)
# This is a callback function that checks if there is any printing in progress via USB when the application tries
@@ -109,7 +102,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
## Reset USB device settings
#
- def resetDeviceSettings(self):
+ def resetDeviceSettings(self) -> None:
self._firmware_name = None
## Request the current scene to be sent to a USB-connected printer.
@@ -122,7 +115,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
if self._is_printing:
return # Aleady printing
-
+ self.writeStarted.emit(self)
# cancel any ongoing preheat timer before starting a print
self._printers[0].getController().stopPreheatTimers()
@@ -135,93 +128,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._printGCode(gcode_list)
- ## Show firmware interface.
- # This will create the view if its not already created.
- def showFirmwareInterface(self):
- if self._firmware_view is None:
- path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
- self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
-
- self._firmware_view.show()
-
- @pyqtSlot(str)
- def updateFirmware(self, file):
- # the file path could be url-encoded.
- if file.startswith("file://"):
- self._firmware_location = QUrl(file).toLocalFile()
- else:
- self._firmware_location = file
- self.showFirmwareInterface()
- self.setFirmwareUpdateState(FirmwareUpdateState.updating)
- self._update_firmware_thread.start()
-
- def _updateFirmware(self):
- # Ensure that other connections are closed.
- if self._connection_state != ConnectionState.closed:
- self.close()
-
- try:
- hex_file = intelHex.readHex(self._firmware_location)
- assert len(hex_file) > 0
- except (FileNotFoundError, AssertionError):
- Logger.log("e", "Unable to read provided hex file. Could not update firmware.")
- self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
- return
-
- programmer = stk500v2.Stk500v2()
- programmer.progress_callback = self._onFirmwareProgress
-
- try:
- programmer.connect(self._serial_port)
- except:
- programmer.close()
- Logger.logException("e", "Failed to update firmware")
- self.setFirmwareUpdateState(FirmwareUpdateState.communication_error)
- return
-
- # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
- sleep(1)
- if not programmer.isConnected():
- Logger.log("e", "Unable to connect with serial. Could not update firmware")
- self.setFirmwareUpdateState(FirmwareUpdateState.communication_error)
- try:
- programmer.programChip(hex_file)
- except SerialException:
- self.setFirmwareUpdateState(FirmwareUpdateState.io_error)
- return
- except:
- self.setFirmwareUpdateState(FirmwareUpdateState.unknown_error)
- return
-
- programmer.close()
-
- # Clean up for next attempt.
- self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
- self._firmware_location = ""
- self._onFirmwareProgress(100)
- self.setFirmwareUpdateState(FirmwareUpdateState.completed)
-
- # Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
- CuraApplication.getInstance().callLater(self.connect)
-
- @pyqtProperty(float, notify = firmwareProgressChanged)
- def firmwareProgress(self):
- return self._firmware_progress
-
- @pyqtProperty(int, notify=firmwareUpdateStateChanged)
- def firmwareUpdateState(self):
- return self._firmware_update_state
-
- def setFirmwareUpdateState(self, state):
- if self._firmware_update_state != state:
- self._firmware_update_state = state
- self.firmwareUpdateStateChanged.emit()
-
- # Callback function for firmware update progress.
- def _onFirmwareProgress(self, progress, max_progress = 100):
- self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100
- self.firmwareProgressChanged.emit()
-
## Start a print based on a g-code.
# \param gcode_list List with gcode (strings).
def _printGCode(self, gcode_list: List[str]):
@@ -258,7 +164,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._baud_rate = baud_rate
def connect(self):
- self._firmware_name = None # after each connection ensure that the firmware name is removed
+ self._firmware_name = None # after each connection ensure that the firmware name is removed
if self._baud_rate is None:
if self._use_auto_detect:
@@ -272,13 +178,19 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
except SerialException:
Logger.log("w", "An exception occured while trying to create serial connection")
return
+ CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
+ self._onGlobalContainerStackChanged()
+ self.setConnectionState(ConnectionState.Connected)
+ self._update_thread.start()
+
+ def _onGlobalContainerStackChanged(self):
container_stack = CuraApplication.getInstance().getGlobalContainerStack()
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
# Ensure that a printer is created.
- self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)]
+ controller = GenericOutputController(self)
+ controller.setCanUpdateFirmware(True)
+ self._printers = [PrinterOutputModel(output_controller = controller, number_of_extruders = num_extruders)]
self._printers[0].updateName(container_stack.getName())
- self.setConnectionState(ConnectionState.connected)
- self._update_thread.start()
def close(self):
super().close()
@@ -295,8 +207,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._command_queue.put(command)
else:
self._sendCommand(command)
+
def _sendCommand(self, command: Union[str, bytes]):
- if self._serial is None or self._connection_state != ConnectionState.connected:
+ if self._serial is None or self._connection_state != ConnectionState.Connected:
return
new_command = cast(bytes, command) if type(command) is bytes else cast(str, command).encode() # type: bytes
@@ -310,24 +223,27 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._command_received.set()
def _update(self):
- while self._connection_state == ConnectionState.connected and self._serial is not None:
+ while self._connection_state == ConnectionState.Connected and self._serial is not None:
try:
line = self._serial.readline()
except:
continue
+ if not self._firmware_name_requested:
+ self._firmware_name_requested = True
+ self.sendCommand("M115")
+
+ if b"FIRMWARE_NAME:" in line:
+ self._setFirmwareName(line)
+
if self._last_temperature_request is None or time() > self._last_temperature_request + self._timeout:
# Timeout, or no request has been sent at all.
- self._command_received.set() # We haven't really received the ok, but we need to send a new command
+ if not self._printer_busy: # Don't flood the printer with temperature requests while it is busy
+ self.sendCommand("M105")
+ self._last_temperature_request = time()
- self.sendCommand("M105")
- self._last_temperature_request = time()
-
- if self._firmware_name is None:
- self.sendCommand("M115")
-
- if (b"ok " in line and b"T:" in line) or b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
- extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
+ if re.search(b"[B|T\d*]: ?\d+\.?\d*", line): # Temperature message. 'T:' for extruder and 'B:' for bed
+ extruder_temperature_matches = re.findall(b"T(\d*): ?(\d+\.?\d*) ?\/?(\d+\.?\d*)?", line)
# Update all temperature values
matched_extruder_nrs = []
for match in extruder_temperature_matches:
@@ -349,7 +265,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if match[2]:
extruder.updateTargetHotendTemperature(float(match[2]))
- bed_temperature_matches = re.findall(b"B: ?([\d\.]+) ?\/?([\d\.]+)?", line)
+ bed_temperature_matches = re.findall(b"B: ?(\d+\.?\d*) ?\/?(\d+\.?\d*) ?", line)
if bed_temperature_matches:
match = bed_temperature_matches[0]
if match[0]:
@@ -357,29 +273,39 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if match[1]:
self._printers[0].updateTargetBedTemperature(float(match[1]))
- if b"FIRMWARE_NAME:" in line:
- self._setFirmwareName(line)
+ if line == b"":
+ # An empty line means that the firmware is idle
+ # Multiple empty lines probably means that the firmware and Cura are waiting
+ # for eachother due to a missed "ok", so we keep track of empty lines
+ self._firmware_idle_count += 1
+ else:
+ self._firmware_idle_count = 0
+
+ if line.startswith(b"ok") or self._firmware_idle_count > 1:
+ self._printer_busy = False
- if b"ok" in line:
self._command_received.set()
if not self._command_queue.empty():
self._sendCommand(self._command_queue.get())
- if self._is_printing:
+ elif self._is_printing:
if self._paused:
pass # Nothing to do!
else:
self._sendNextGcodeLine()
+ if line.startswith(b"echo:busy:"):
+ self._printer_busy = True
+
if self._is_printing:
if line.startswith(b'!!'):
Logger.log('e', "Printer signals fatal error. Cancelling print. {}".format(line))
self.cancelPrint()
- elif b"resend" in line.lower() or b"rs" in line:
+ elif line.lower().startswith(b"resend") or line.startswith(b"rs"):
# A resend can be requested either by Resend, resend or rs.
try:
self._gcode_position = int(line.replace(b"N:", b" ").replace(b"N", b" ").replace(b":", b" ").split()[-1])
except:
- if b"rs" in line:
+ if line.startswith(b"rs"):
# In some cases of the RS command it needs to be handled differently.
self._gcode_position = int(line.split()[1])
@@ -445,7 +371,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
elapsed_time = int(time() - self._print_start_time)
print_job = self._printers[0].activePrintJob
if print_job is None:
- print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= CuraApplication.getInstance().getPrintInformation().jobName)
+ controller = GenericOutputController(self)
+ controller.setCanUpdateFirmware(True)
+ print_job = PrintJobOutputModel(output_controller=controller, name=CuraApplication.getInstance().getPrintInformation().jobName)
print_job.updateState("printing")
self._printers[0].updateActivePrintJob(print_job)
@@ -456,13 +384,3 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
print_job.updateTimeTotal(estimated_time)
self._gcode_position += 1
-
-
-class FirmwareUpdateState(IntEnum):
- idle = 0
- updating = 1
- completed = 2
- unknown_error = 3
- communication_error = 4
- io_error = 5
- firmware_not_found_error = 6
diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py
index 2ee85187ee..d4c0d1828e 100644
--- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py
+++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py
@@ -2,14 +2,12 @@
# Cura is released under the terms of the LGPLv3 or higher.
import threading
-import platform
import time
import serial.tools.list_ports
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger
-from UM.Resources import Resources
from UM.Signal import Signal, signalemitter
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from UM.i18n import i18nCatalog
@@ -68,7 +66,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
return
changed_device = self._usb_output_devices[serial_port]
- if changed_device.connectionState == ConnectionState.connected:
+ if changed_device.connectionState == ConnectionState.Connected:
self.getOutputDeviceManager().addOutputDevice(changed_device)
else:
self.getOutputDeviceManager().removeOutputDevice(serial_port)
@@ -87,65 +85,6 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
self._addRemovePorts(port_list)
time.sleep(5)
- @pyqtSlot(result = str)
- def getDefaultFirmwareName(self):
- # Check if there is a valid global container stack
- global_container_stack = self._application.getGlobalContainerStack()
- if not global_container_stack:
- Logger.log("e", "There is no global container stack. Can not update firmware.")
- self._firmware_view.close()
- return ""
-
- # The bottom of the containerstack is the machine definition
- machine_id = global_container_stack.getBottom().id
-
- machine_has_heated_bed = global_container_stack.getProperty("machine_heated_bed", "value")
-
- if platform.system() == "Linux":
- baudrate = 115200
- else:
- baudrate = 250000
-
- # NOTE: The keyword used here is the id of the machine. You can find the id of your machine in the *.json file, eg.
- # https://github.com/Ultimaker/Cura/blob/master/resources/machines/ultimaker_original.json#L2
- # The *.hex files are stored at a seperate repository:
- # https://github.com/Ultimaker/cura-binary-data/tree/master/cura/resources/firmware
- machine_without_extras = {"bq_witbox" : "MarlinWitbox.hex",
- "bq_hephestos_2" : "MarlinHephestos2.hex",
- "ultimaker_original" : "MarlinUltimaker-{baudrate}.hex",
- "ultimaker_original_plus" : "MarlinUltimaker-UMOP-{baudrate}.hex",
- "ultimaker_original_dual" : "MarlinUltimaker-{baudrate}-dual.hex",
- "ultimaker2" : "MarlinUltimaker2.hex",
- "ultimaker2_go" : "MarlinUltimaker2go.hex",
- "ultimaker2_plus" : "MarlinUltimaker2plus.hex",
- "ultimaker2_extended" : "MarlinUltimaker2extended.hex",
- "ultimaker2_extended_plus" : "MarlinUltimaker2extended-plus.hex",
- }
- machine_with_heated_bed = {"ultimaker_original" : "MarlinUltimaker-HBK-{baudrate}.hex",
- "ultimaker_original_dual" : "MarlinUltimaker-HBK-{baudrate}-dual.hex",
- }
- ##TODO: Add check for multiple extruders
- hex_file = None
- if machine_id in machine_without_extras.keys(): # The machine needs to be defined here!
- if machine_id in machine_with_heated_bed.keys() and machine_has_heated_bed:
- Logger.log("d", "Choosing firmware with heated bed enabled for machine %s.", machine_id)
- hex_file = machine_with_heated_bed[machine_id] # Return firmware with heated bed enabled
- else:
- Logger.log("d", "Choosing basic firmware for machine %s.", machine_id)
- hex_file = machine_without_extras[machine_id] # Return "basic" firmware
- else:
- Logger.log("w", "There is no firmware for machine %s.", machine_id)
-
- if hex_file:
- try:
- return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
- except FileNotFoundError:
- Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
- return ""
- else:
- Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
- return ""
-
## Helper to identify serial ports (and scan for them)
def _addRemovePorts(self, serial_ports):
# First, find and add all new or changed keys
diff --git a/plugins/USBPrinting/__init__.py b/plugins/USBPrinting/__init__.py
index fd5488eead..075ad2943b 100644
--- a/plugins/USBPrinting/__init__.py
+++ b/plugins/USBPrinting/__init__.py
@@ -2,9 +2,6 @@
# Cura is released under the terms of the LGPLv3 or higher.
from . import USBPrinterOutputDeviceManager
-from PyQt5.QtQml import qmlRegisterSingletonType
-from UM.i18n import i18nCatalog
-i18n_catalog = i18nCatalog("cura")
def getMetaData():
@@ -14,5 +11,4 @@ def getMetaData():
def register(app):
# We are violating the QT API here (as we use a factory, which is technically not allowed).
# but we don't really have another means for doing this (and it seems to you know -work-)
- qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "Cura", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance)
return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager(app)}
diff --git a/plugins/USBPrinting/plugin.json b/plugins/USBPrinting/plugin.json
index 27e07c45b2..45971d858b 100644
--- a/plugins/USBPrinting/plugin.json
+++ b/plugins/USBPrinting/plugin.json
@@ -1,8 +1,8 @@
{
"name": "USB printing",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
- "api": 4,
+ "version": "1.0.2",
+ "api": "6.0",
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
"i18n-catalog": "cura"
}
diff --git a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml
index b92638aa12..2a01cfaa40 100644
--- a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml
+++ b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml
@@ -17,9 +17,9 @@ Cura.MachineAction
property int rightRow: (checkupMachineAction.width * 0.60) | 0
property bool heatupHotendStarted: false
property bool heatupBedStarted: false
- property bool usbConnected: Cura.USBPrinterManager.connectedPrinterList.rowCount() > 0
+ property bool printerConnected: Cura.MachineManager.printerConnected
- UM.I18nCatalog { id: catalog; name:"cura"}
+ UM.I18nCatalog { id: catalog; name: "cura"}
Label
{
id: pageTitle
@@ -86,7 +86,7 @@ Cura.MachineAction
anchors.left: connectionLabel.right
anchors.top: parent.top
wrapMode: Text.WordWrap
- text: checkupMachineAction.usbConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected")
+ text: checkupMachineAction.printerConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected")
}
//////////////////////////////////////////////////////////
Label
@@ -97,7 +97,7 @@ Cura.MachineAction
anchors.top: connectionLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Min endstop X: ")
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
}
Label
{
@@ -107,7 +107,7 @@ Cura.MachineAction
anchors.top: connectionLabel.bottom
wrapMode: Text.WordWrap
text: manager.xMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
}
//////////////////////////////////////////////////////////////
Label
@@ -118,7 +118,7 @@ Cura.MachineAction
anchors.top: endstopXLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Min endstop Y: ")
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
}
Label
{
@@ -128,7 +128,7 @@ Cura.MachineAction
anchors.top: endstopXLabel.bottom
wrapMode: Text.WordWrap
text: manager.yMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
}
/////////////////////////////////////////////////////////////////////
Label
@@ -139,7 +139,7 @@ Cura.MachineAction
anchors.top: endstopYLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Min endstop Z: ")
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
}
Label
{
@@ -149,7 +149,7 @@ Cura.MachineAction
anchors.top: endstopYLabel.bottom
wrapMode: Text.WordWrap
text: manager.zMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
}
////////////////////////////////////////////////////////////
Label
@@ -161,7 +161,7 @@ Cura.MachineAction
anchors.top: endstopZLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Nozzle temperature check: ")
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
}
Label
{
@@ -171,7 +171,7 @@ Cura.MachineAction
anchors.left: nozzleTempLabel.right
wrapMode: Text.WordWrap
text: catalog.i18nc("@info:status","Not checked")
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
}
Item
{
@@ -181,7 +181,7 @@ Cura.MachineAction
anchors.top: nozzleTempLabel.top
anchors.left: bedTempStatus.right
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
Button
{
text: checkupMachineAction.heatupHotendStarted ? catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
@@ -209,7 +209,7 @@ Cura.MachineAction
wrapMode: Text.WordWrap
text: manager.hotendTemperature + "°C"
font.bold: true
- visible: checkupMachineAction.usbConnected
+ visible: checkupMachineAction.printerConnected
}
/////////////////////////////////////////////////////////////////////////////
Label
@@ -221,7 +221,7 @@ Cura.MachineAction
anchors.top: nozzleTempLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Build plate temperature check:")
- visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
+ visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
}
Label
@@ -232,7 +232,7 @@ Cura.MachineAction
anchors.left: bedTempLabel.right
wrapMode: Text.WordWrap
text: manager.bedTestCompleted ? catalog.i18nc("@info:status","Not checked"): catalog.i18nc("@info:status","Checked")
- visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
+ visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
}
Item
{
@@ -242,7 +242,7 @@ Cura.MachineAction
anchors.top: bedTempLabel.top
anchors.left: bedTempStatus.right
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
- visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
+ visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
Button
{
text: checkupMachineAction.heatupBedStarted ?catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
@@ -270,7 +270,7 @@ Cura.MachineAction
wrapMode: Text.WordWrap
text: manager.bedTemperature + "°C"
font.bold: true
- visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
+ visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
}
Label
{
diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py
deleted file mode 100644
index 1f0e640f04..0000000000
--- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from UM.Application import Application
-from UM.Settings.DefinitionContainer import DefinitionContainer
-from cura.MachineAction import MachineAction
-from UM.i18n import i18nCatalog
-from UM.Settings.ContainerRegistry import ContainerRegistry
-
-catalog = i18nCatalog("cura")
-
-## Upgrade the firmware of a machine by USB with this action.
-class UpgradeFirmwareMachineAction(MachineAction):
- def __init__(self):
- super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware"))
- self._qml_url = "UpgradeFirmwareMachineAction.qml"
- ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
-
- def _onContainerAdded(self, container):
- # Add this action as a supported action to all machine definitions if they support USB connection
- if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
- Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml
deleted file mode 100644
index ed771d2a04..0000000000
--- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (c) 2016 Ultimaker B.V.
-// Cura is released under the terms of the LGPLv3 or higher.
-
-import QtQuick 2.2
-import QtQuick.Controls 1.1
-import QtQuick.Layouts 1.1
-import QtQuick.Window 2.1
-import QtQuick.Dialogs 1.2 // For filedialog
-
-import UM 1.2 as UM
-import Cura 1.0 as Cura
-
-
-Cura.MachineAction
-{
- anchors.fill: parent;
- property bool printerConnected: Cura.MachineManager.printerConnected
- property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
-
- Item
- {
- id: upgradeFirmwareMachineAction
- anchors.fill: parent;
- UM.I18nCatalog { id: catalog; name:"cura"}
-
- Label
- {
- id: pageTitle
- width: parent.width
- text: catalog.i18nc("@title", "Upgrade Firmware")
- wrapMode: Text.WordWrap
- font.pointSize: 18
- }
- Label
- {
- id: pageDescription
- anchors.top: pageTitle.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- width: parent.width
- wrapMode: Text.WordWrap
- text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.")
- }
-
- Label
- {
- id: upgradeText1
- anchors.top: pageDescription.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- width: parent.width
- wrapMode: Text.WordWrap
- text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
- }
-
- Row
- {
- anchors.top: upgradeText1.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.horizontalCenter: parent.horizontalCenter
- width: childrenRect.width
- spacing: UM.Theme.getSize("default_margin").width
- property var firmwareName: Cura.USBPrinterManager.getDefaultFirmwareName()
- Button
- {
- id: autoUpgradeButton
- text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
- enabled: parent.firmwareName != "" && activeOutputDevice
- onClicked:
- {
- activeOutputDevice.updateFirmware(parent.firmwareName)
- }
- }
- Button
- {
- id: manualUpgradeButton
- text: catalog.i18nc("@action:button", "Upload custom Firmware");
- enabled: activeOutputDevice != null
- onClicked:
- {
- customFirmwareDialog.open()
- }
- }
- }
-
- FileDialog
- {
- id: customFirmwareDialog
- title: catalog.i18nc("@title:window", "Select custom firmware")
- nameFilters: "Firmware image files (*.hex)"
- selectExisting: true
- onAccepted: activeOutputDevice.updateFirmware(fileUrl)
- }
- }
-}
\ No newline at end of file
diff --git a/plugins/UltimakerMachineActions/__init__.py b/plugins/UltimakerMachineActions/__init__.py
index 495f212736..e87949580a 100644
--- a/plugins/UltimakerMachineActions/__init__.py
+++ b/plugins/UltimakerMachineActions/__init__.py
@@ -1,22 +1,16 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import BedLevelMachineAction
-from . import UpgradeFirmwareMachineAction
from . import UMOUpgradeSelection
from . import UM2UpgradeSelection
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
-
def getMetaData():
- return {
- }
+ return {}
def register(app):
return { "machine_action": [
BedLevelMachineAction.BedLevelMachineAction(),
- UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(),
UMOUpgradeSelection.UMOUpgradeSelection(),
UM2UpgradeSelection.UM2UpgradeSelection()
]}
diff --git a/plugins/UltimakerMachineActions/plugin.json b/plugins/UltimakerMachineActions/plugin.json
index 57b3e6bc8f..3e3e0af9b0 100644
--- a/plugins/UltimakerMachineActions/plugin.json
+++ b/plugins/UltimakerMachineActions/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Ultimaker machine actions",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/UserAgreement/UserAgreement.qml b/plugins/UserAgreement/UserAgreement.qml
index 4ee03f4ad5..2e5893fc41 100644
--- a/plugins/UserAgreement/UserAgreement.qml
+++ b/plugins/UserAgreement/UserAgreement.qml
@@ -36,7 +36,7 @@ UM.Dialog
width: parent.width
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
- UM.I18nCatalog { id: catalog; name:"cura" }
+ UM.I18nCatalog { id: catalog; name: "cura" }
Button
{
diff --git a/plugins/UserAgreement/plugin.json b/plugins/UserAgreement/plugin.json
index b10abc5640..b172d1f9a2 100644
--- a/plugins/UserAgreement/plugin.json
+++ b/plugins/UserAgreement/plugin.json
@@ -1,8 +1,8 @@
{
"name": "UserAgreement",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Ask the user once if he/she agrees with our license.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/MachineInstance.py b/plugins/VersionUpgrade/VersionUpgrade21to22/MachineInstance.py
index 37b6989add..ff5c33517d 100644
--- a/plugins/VersionUpgrade/VersionUpgrade21to22/MachineInstance.py
+++ b/plugins/VersionUpgrade/VersionUpgrade21to22/MachineInstance.py
@@ -1,14 +1,16 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-import UM.VersionUpgrade #To indicate that a file is of incorrect format.
-import UM.VersionUpgradeManager #To schedule more files to be upgraded.
-from UM.Resources import Resources #To get the config storage path.
-
import configparser #To read config files.
import io #To write config files to strings as if they were files.
import os.path #To get the path to write new user profiles to.
+from typing import Dict, List, Optional, Set, Tuple
import urllib #To serialise the user container file name properly.
+import urllib.parse
+
+import UM.VersionUpgrade #To indicate that a file is of incorrect format.
+import UM.VersionUpgradeManager #To schedule more files to be upgraded.
+from UM.Resources import Resources #To get the config storage path.
## Creates a new machine instance instance by parsing a serialised machine
# instance in version 1 of the file format.
@@ -18,7 +20,7 @@ import urllib #To serialise the user container file name properly.
# extension.
# \return A machine instance instance, or None if the file format is
# incorrect.
-def importFrom(serialised, filename):
+def importFrom(serialised: str, filename: str) -> Optional["MachineInstance"]:
try:
return MachineInstance(serialised, filename)
except (configparser.Error, UM.VersionUpgrade.FormatException, UM.VersionUpgrade.InvalidVersionException):
@@ -32,7 +34,7 @@ class MachineInstance:
# \param serialised A string with the contents of a machine instance file,
# without extension.
# \param filename The supposed file name of this machine instance.
- def __init__(self, serialised, filename):
+ def __init__(self, serialised: str, filename: str) -> None:
self._filename = filename
config = configparser.ConfigParser(interpolation = None)
@@ -53,11 +55,11 @@ class MachineInstance:
self._type_name = config.get("general", "type")
self._variant_name = config.get("general", "variant", fallback = "empty_variant")
self._name = config.get("general", "name", fallback = "")
- self._key = config.get("general", "key", fallback = None)
+ self._key = config.get("general", "key", fallback = "")
self._active_profile_name = config.get("general", "active_profile", fallback = "empty_quality")
self._active_material_name = config.get("general", "material", fallback = "empty_material")
- self._machine_setting_overrides = {}
+ self._machine_setting_overrides = {} # type: Dict[str, str]
for key, value in config["machine_settings"].items():
self._machine_setting_overrides[key] = value
@@ -67,7 +69,7 @@ class MachineInstance:
#
# \return A tuple containing the new filename and a serialised form of
# this machine instance, serialised in version 2 of the file format.
- def export(self):
+ def export(self) -> Tuple[List[str], List[str]]:
config = configparser.ConfigParser(interpolation = None) # Build a config file in the form of version 2.
config.add_section("general")
@@ -108,7 +110,7 @@ class MachineInstance:
version_upgrade_manager = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance()
user_version_to_paths_dict = version_upgrade_manager.getStoragePaths("user")
- paths_set = set()
+ paths_set = set() # type: Set[str]
for paths in user_version_to_paths_dict.values():
paths_set |= paths
diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/Preferences.py b/plugins/VersionUpgrade/VersionUpgrade21to22/Preferences.py
index 842499da86..953837b863 100644
--- a/plugins/VersionUpgrade/VersionUpgrade21to22/Preferences.py
+++ b/plugins/VersionUpgrade/VersionUpgrade21to22/Preferences.py
@@ -1,8 +1,9 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To read config files.
import io #To output config files to string.
+from typing import List, Optional, Tuple
import UM.VersionUpgrade #To indicate that a file is of the wrong format.
@@ -14,7 +15,7 @@ import UM.VersionUpgrade #To indicate that a file is of the wrong format.
# extension.
# \return A representation of those preferences, or None if the file format is
# incorrect.
-def importFrom(serialised, filename):
+def importFrom(serialised: str, filename: str) -> Optional["Preferences"]:
try:
return Preferences(serialised, filename)
except (configparser.Error, UM.VersionUpgrade.FormatException, UM.VersionUpgrade.InvalidVersionException):
@@ -28,7 +29,7 @@ class Preferences:
# \param serialised A serialised version 2 preferences file.
# \param filename The supposed filename of the preferences file, without
# extension.
- def __init__(self, serialised, filename):
+ def __init__(self, serialised: str, filename: str) -> None:
self._filename = filename
self._config = configparser.ConfigParser(interpolation = None)
@@ -50,7 +51,7 @@ class Preferences:
#
# \return A tuple containing the new filename and a serialised version of
# a preferences file in version 3.
- def export(self):
+ def export(self) -> Tuple[List[str], List[str]]:
#Reset the cura/categories_expanded property since it works differently now.
if self._config.has_section("cura") and self._config.has_option("cura", "categories_expanded"):
self._config.remove_option("cura", "categories_expanded")
@@ -58,11 +59,11 @@ class Preferences:
#Translate the setting names in the visible settings.
if self._config.has_section("machines") and self._config.has_option("machines", "setting_visibility"):
visible_settings = self._config.get("machines", "setting_visibility")
- visible_settings = visible_settings.split(",")
+ visible_settings_list = visible_settings.split(",")
import VersionUpgrade21to22 #Import here to prevent a circular dependency.
- visible_settings = [VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateSettingName(setting_name)
- for setting_name in visible_settings]
- visible_settings = ",".join(visible_settings)
+ visible_settings_list = [VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateSettingName(setting_name)
+ for setting_name in visible_settings_list]
+ visible_settings = ",".join(visible_settings_list)
self._config.set("machines", "setting_visibility", value = visible_settings)
#Translate the active_instance key.
diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py b/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py
index 161edcb67c..af9635d384 100644
--- a/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py
+++ b/plugins/VersionUpgrade/VersionUpgrade21to22/Profile.py
@@ -1,10 +1,9 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To read config files.
import io #To write config files to strings as if they were files.
-from typing import Dict
-from typing import List
+from typing import Dict, List, Optional, Tuple
import UM.VersionUpgrade
from UM.Logger import Logger
@@ -15,7 +14,7 @@ from UM.Logger import Logger
# \param serialised The serialised form of a profile in version 1.
# \param filename The supposed filename of the profile, without extension.
# \return A profile instance, or None if the file format is incorrect.
-def importFrom(serialised, filename):
+def importFrom(serialised: str, filename: str) -> Optional["Profile"]:
try:
return Profile(serialised, filename)
except (configparser.Error, UM.VersionUpgrade.FormatException, UM.VersionUpgrade.InvalidVersionException):
@@ -77,11 +76,11 @@ class Profile:
#
# \return A tuple containing the new filename and a serialised form of
# this profile, serialised in version 2 of the file format.
- def export(self):
+ def export(self) -> Optional[Tuple[List[str], List[str]]]:
import VersionUpgrade21to22 # Import here to prevent circular dependencies.
if self._name == "Current settings":
- return None, None #Can't upgrade these, because the new current profile needs to specify the definition ID and the old file only had the machine instance, not the definition.
+ return None #Can't upgrade these, because the new current profile needs to specify the definition ID and the old file only had the machine instance, not the definition.
config = configparser.ConfigParser(interpolation = None)
diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py b/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py
index d8036491bf..536385b19d 100644
--- a/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py
+++ b/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py
@@ -1,7 +1,8 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To get version numbers from config files.
+from typing import Dict, Iterable, List, Optional, Set, Tuple
from UM.VersionUpgrade import VersionUpgrade # Superclass of the plugin.
@@ -30,7 +31,7 @@ _machines_with_machine_quality = {
"materials": { "generic_abs", "generic_cpe", "generic_pla", "generic_pva", "generic_cpe_plus", "generic_nylon", "generic_pc", "generic_tpu" },
"variants": { "0.25 mm", "0.4 mm", "0.6 mm", "0.8 mm" }
}
-}
+} # type: Dict[str, Dict[str, Set[str]]]
## How to translate material names from the old version to the new.
_material_translations = {
@@ -41,7 +42,7 @@ _material_translations = {
"Nylon": "generic_nylon",
"PC": "generic_pc",
"TPU": "generic_tpu",
-}
+} # type: Dict[str, str]
## How to translate material names for in the profile names.
_material_translations_profiles = {
@@ -52,17 +53,17 @@ _material_translations_profiles = {
"Nylon": "nylon",
"PC": "pc",
"TPU": "tpu",
-}
+} # type: Dict[str, str]
## How to translate printer names from the old version to the new.
_printer_translations = {
"ultimaker2plus": "ultimaker2_plus"
-}
+} # type: Dict[str, str]
_printer_translations_profiles = {
"ultimaker2plus": "um2p", #Does NOT get included in PLA profiles!
"ultimaker2_extended_plus": "um2ep" #Has no profiles for CPE+, Nylon, PC and TPU!
-}
+} # type: Dict[str, str]
## How to translate profile names from the old version to the new.
#
@@ -116,13 +117,13 @@ _profile_translations = {
"tpu_0.25_high": "um2p_tpu_0.25_high",
"tpu_0.4_normal": "um2p_tpu_0.4_normal",
"tpu_0.6_fast": "um2p_tpu_0.6_fast"
-}
+} # type: Dict[str, str]
## Settings that are no longer in the new version.
_removed_settings = {
"fill_perimeter_gaps",
"support_area_smoothing"
-}
+} # type: Set[str]
## How to translate setting names from the old version to the new.
_setting_name_translations = {
@@ -142,7 +143,7 @@ _setting_name_translations = {
"support_roof_line_distance": "support_interface_line_distance",
"support_roof_line_width": "support_interface_line_width",
"support_roof_pattern": "support_interface_pattern"
-}
+} # type: Dict[str, str]
## Custom profiles become quality_changes. This dictates which quality to base
# the quality_changes profile on.
@@ -190,7 +191,7 @@ _quality_fallbacks = {
#No TPU.
}
}
-}
+} # type: Dict[str, Dict[str, Dict[str, str]]]
## How to translate variants of specific machines from the old version to the
# new.
@@ -207,7 +208,7 @@ _variant_translations = {
"0.6 mm": "ultimaker2_extended_plus_0.6",
"0.8 mm": "ultimaker2_extended_plus_0.8"
}
-}
+} # type: Dict[str, Dict[str, str]]
## How to translate variant names for in the profile names.
_variant_translations_profiles = {
@@ -215,7 +216,7 @@ _variant_translations_profiles = {
"0.4 mm": "0.4",
"0.6 mm": "0.6",
"0.8 mm": "0.8"
-}
+} # type: Dict[str, str]
## Cura 2.2's material profiles use a different naming scheme for variants.
#
@@ -233,7 +234,7 @@ _variant_translations_materials = {
"0.6 mm": "ultimaker2_plus_0.6_mm",
"0.8 mm": "ultimaker2_plus_0.8_mm"
}
-}
+} # type: Dict[str, Dict[str, str]]
## Converts configuration from Cura 2.1's file formats to Cura 2.2's.
#
@@ -245,12 +246,12 @@ class VersionUpgrade21to22(VersionUpgrade):
# number is stored in general/version, so get the data from that key.
#
# \param serialised The contents of a config file.
- # \return \type{int} The version number of that config file.
- def getCfgVersion(self, serialised):
+ # \return The version number of that config file.
+ def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
- setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
## Gets the fallback quality to use for a specific machine-variant-material
@@ -263,7 +264,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \param variant The variant ID of the user's configuration in 2.2.
# \param material The material ID of the user's configuration in 2.2.
@staticmethod
- def getQualityFallback(machine, variant, material):
+ def getQualityFallback(machine: str, variant: str, material: str) -> str:
if machine not in _quality_fallbacks:
return "normal"
if variant not in _quality_fallbacks[machine]:
@@ -277,14 +278,14 @@ class VersionUpgrade21to22(VersionUpgrade):
# This is required to test if profiles should be converted to a quality
# profile or a quality-changes profile.
@staticmethod
- def builtInProfiles():
+ def builtInProfiles() -> Iterable[str]:
return _profile_translations.keys()
## Gets a set of the machines which now have per-material quality profiles.
#
# \return A set of machine identifiers.
@staticmethod
- def machinesWithMachineQuality():
+ def machinesWithMachineQuality() -> Dict[str, Dict[str, Set[str]]]:
return _machines_with_machine_quality
## Converts machine instances from format version 1 to version 2.
@@ -295,10 +296,10 @@ class VersionUpgrade21to22(VersionUpgrade):
# \return A tuple containing the new filename and the serialised machine
# instance in version 2, or None if the input was not of the correct
# format.
- def upgradeMachineInstance(self, serialised, filename):
+ def upgradeMachineInstance(self, serialised: str, filename: str) -> Optional[Tuple[List[str], List[str]]]:
machine_instance = MachineInstance.importFrom(serialised, filename)
if not machine_instance: #Invalid file format.
- return filename, None
+ return None
return machine_instance.export()
## Converts preferences from format version 2 to version 3.
@@ -309,10 +310,10 @@ class VersionUpgrade21to22(VersionUpgrade):
# \return A tuple containing the new filename and the serialised
# preferences in version 3, or None if the input was not of the correct
# format.
- def upgradePreferences(self, serialised, filename):
+ def upgradePreferences(self, serialised: str, filename: str) -> Optional[Tuple[List[str], List[str]]]:
preferences = Preferences.importFrom(serialised, filename)
if not preferences: #Invalid file format.
- return filename, None
+ return None
return preferences.export()
## Converts profiles from format version 1 to version 2.
@@ -322,10 +323,10 @@ class VersionUpgrade21to22(VersionUpgrade):
# extension.
# \return A tuple containing the new filename and the serialised profile
# in version 2, or None if the input was not of the correct format.
- def upgradeProfile(self, serialised, filename):
+ def upgradeProfile(self, serialised: str, filename: str) -> Optional[Tuple[List[str], List[str]]]:
profile = Profile.importFrom(serialised, filename)
if not profile: # Invalid file format.
- return filename, None
+ return None
return profile.export()
## Translates a material name for the change from Cura 2.1 to 2.2.
@@ -333,7 +334,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \param material A material name in Cura 2.1.
# \return The name of the corresponding material in Cura 2.2.
@staticmethod
- def translateMaterial(material):
+ def translateMaterial(material: str) -> str:
if material in _material_translations:
return _material_translations[material]
return material
@@ -345,7 +346,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \return The name of the corresponding material in the quality profiles
# in Cura 2.2.
@staticmethod
- def translateMaterialForProfiles(material):
+ def translateMaterialForProfiles(material: str) -> str:
if material in _material_translations_profiles:
return _material_translations_profiles[material]
return material
@@ -356,7 +357,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \param printer A printer name in Cura 2.1.
# \return The name of the corresponding printer in Cura 2.2.
@staticmethod
- def translatePrinter(printer):
+ def translatePrinter(printer: str) -> str:
if printer in _printer_translations:
return _printer_translations[printer]
return printer #Doesn't need to be translated.
@@ -367,7 +368,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \param printer A printer name in 2.1.
# \return The name of the corresponding printer in Cura 2.2.
@staticmethod
- def translatePrinterForProfile(printer):
+ def translatePrinterForProfile(printer: str) -> str:
if printer in _printer_translations_profiles:
return _printer_translations_profiles[printer]
return printer
@@ -378,7 +379,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \param profile A profile name in the old version.
# \return The corresponding profile name in the new version.
@staticmethod
- def translateProfile(profile):
+ def translateProfile(profile: str) -> str:
if profile in _profile_translations:
return _profile_translations[profile]
return profile #Doesn't need to be translated.
@@ -392,7 +393,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \param settings A dictionary of settings (as key-value pairs) to update.
# \return The same dictionary.
@staticmethod
- def translateSettings(settings):
+ def translateSettings(settings: Dict[str, str]) -> Dict[str, str]:
new_settings = {}
for key, value in settings.items():
if key in _removed_settings:
@@ -414,7 +415,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \param setting The name of a setting in Cura 2.1.
# \return The name of the corresponding setting in Cura 2.2.
@staticmethod
- def translateSettingName(setting):
+ def translateSettingName(setting: str) -> str:
if setting in _setting_name_translations:
return _setting_name_translations[setting]
return setting #Doesn't need to be translated.
@@ -426,7 +427,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# 2.2's naming.
# \return The name of the corresponding variant in Cura 2.2.
@staticmethod
- def translateVariant(variant, machine):
+ def translateVariant(variant: str, machine: str) -> str:
if machine in _variant_translations and variant in _variant_translations[machine]:
return _variant_translations[machine][variant]
return variant
@@ -440,7 +441,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \return The name of the corresponding variant for in material profiles
# in Cura 2.2.
@staticmethod
- def translateVariantForMaterials(variant, machine):
+ def translateVariantForMaterials(variant: str, machine: str) -> str:
if machine in _variant_translations_materials and variant in _variant_translations_materials[machine]:
return _variant_translations_materials[machine][variant]
return variant
@@ -452,7 +453,7 @@ class VersionUpgrade21to22(VersionUpgrade):
# \return The name of the corresponding variant for in quality profiles in
# Cura 2.2.
@staticmethod
- def translateVariantForProfiles(variant):
+ def translateVariantForProfiles(variant: str) -> str:
if variant in _variant_translations_profiles:
return _variant_translations_profiles[variant]
return variant
\ No newline at end of file
diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py b/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py
index 435621ec54..67530b9d45 100644
--- a/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py
@@ -1,14 +1,16 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Any, Dict, TYPE_CHECKING
+
from . import VersionUpgrade21to22
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
+if TYPE_CHECKING:
+ from UM.Application import Application
upgrade = VersionUpgrade21to22.VersionUpgrade21to22()
-def getMetaData():
+def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
@@ -36,5 +38,5 @@ def getMetaData():
}
}
-def register(app):
+def register(app: "Application") -> Dict[str, Any]:
return { "version_upgrade": upgrade }
diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/plugin.json b/plugins/VersionUpgrade/VersionUpgrade21to22/plugin.json
index 79115f931e..cad94c2eb5 100644
--- a/plugins/VersionUpgrade/VersionUpgrade21to22/plugin.json
+++ b/plugins/VersionUpgrade/VersionUpgrade21to22/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Version Upgrade 2.1 to 2.2",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py
index 730a62e591..ded892d137 100644
--- a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py
+++ b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py
@@ -1,18 +1,18 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To get version numbers from config files.
+import io
import os
import os.path
-import io
+from typing import Dict, List, Optional, Tuple
from UM.Resources import Resources
from UM.VersionUpgrade import VersionUpgrade # Superclass of the plugin.
import UM.VersionUpgrade
class VersionUpgrade22to24(VersionUpgrade):
-
- def upgradeMachineInstance(self, serialised, filename):
+ def upgradeMachineInstance(self, serialised: str, filename: str) -> Optional[Tuple[List[str], List[str]]]:
# All of this is needed to upgrade custom variant machines from old Cura to 2.4 where
# `definition_changes` instance container has been introduced. Variant files which
# look like the the handy work of the old machine settings plugin are converted directly
@@ -22,11 +22,11 @@ class VersionUpgrade22to24(VersionUpgrade):
config.read_string(serialised) # Read the input string as config file.
if config.get("metadata", "type") == "definition_changes":
# This is not a container stack, don't upgrade it here
- return
+ return None
config.set("general", "version", "3")
- container_list = []
+ container_list = [] # type: List[str]
if config.has_section("containers"):
for index, container_id in config.items("containers"):
container_list.append(container_id)
@@ -37,14 +37,14 @@ class VersionUpgrade22to24(VersionUpgrade):
user_variants = self.__getUserVariants()
name_path_dict = {}
for variant in user_variants:
- name_path_dict[variant.get("name")] = variant.get("path")
+ name_path_dict[variant["name"]] = variant["path"]
user_variant_names = set(container_list).intersection(name_path_dict.keys())
if len(user_variant_names):
# One of the user defined variants appears in the list of containers in the stack.
for variant_name in user_variant_names: # really there should just be one variant to convert.
- config_name = self.__convertVariant(name_path_dict.get(variant_name))
+ config_name = self.__convertVariant(name_path_dict[variant_name])
# Change the name of variant and insert empty_variant into the stack.
new_container_list = []
@@ -64,16 +64,16 @@ class VersionUpgrade22to24(VersionUpgrade):
config.remove_option("general", "containers")
- for index in range(len(container_list)):
- config.set("containers", str(index), container_list[index])
+ for idx in range(len(container_list)):
+ config.set("containers", str(idx), container_list[idx])
output = io.StringIO()
config.write(output)
return [filename], [output.getvalue()]
- def __convertVariant(self, variant_path):
+ def __convertVariant(self, variant_path: str) -> str:
# Copy the variant to the machine_instances/*_settings.inst.cfg
- variant_config = configparser.ConfigParser(interpolation=None)
+ variant_config = configparser.ConfigParser(interpolation = None)
with open(variant_path, "r", encoding = "utf-8") as fhandle:
variant_config.read_file(fhandle)
@@ -99,7 +99,7 @@ class VersionUpgrade22to24(VersionUpgrade):
return config_name
- def __getUserVariants(self):
+ def __getUserVariants(self) -> List[Dict[str, str]]:
resource_path = Resources.getDataStoragePath()
variants_dir = os.path.join(resource_path, "variants")
@@ -113,7 +113,7 @@ class VersionUpgrade22to24(VersionUpgrade):
result.append( { "path": entry.path, "name": config.get("general", "name") } )
return result
- def upgradeExtruderTrain(self, serialised, filename):
+ def upgradeExtruderTrain(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
config = configparser.ConfigParser(interpolation = None)
config.read_string(serialised) # Read the input string as config file.
config.set("general", "version", "3") # Just bump the version number. That is all we need for now.
@@ -122,7 +122,7 @@ class VersionUpgrade22to24(VersionUpgrade):
config.write(output)
return [filename], [output.getvalue()]
- def upgradePreferences(self, serialised, filename):
+ def upgradePreferences(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
config = configparser.ConfigParser(interpolation = None)
config.read_string(serialised)
@@ -142,7 +142,7 @@ class VersionUpgrade22to24(VersionUpgrade):
config.write(output)
return [filename], [output.getvalue()]
- def upgradeQuality(self, serialised, filename):
+ def upgradeQuality(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
config = configparser.ConfigParser(interpolation = None)
config.read_string(serialised) # Read the input string as config file.
config.set("metadata", "type", "quality_changes") # Update metadata/type to quality_changes
@@ -152,9 +152,9 @@ class VersionUpgrade22to24(VersionUpgrade):
config.write(output)
return [filename], [output.getvalue()]
- def getCfgVersion(self, serialised):
+ def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
- setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py b/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py
index fbdbf92a4b..fe79333544 100644
--- a/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py
@@ -1,14 +1,16 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Any, Dict, TYPE_CHECKING
+
from . import VersionUpgrade
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
+if TYPE_CHECKING:
+ from UM.Application import Application
upgrade = VersionUpgrade.VersionUpgrade22to24()
-def getMetaData():
+def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
@@ -29,5 +31,5 @@ def getMetaData():
}
}
-def register(app):
+def register(app: "Application"):
return { "version_upgrade": upgrade }
diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/plugin.json b/plugins/VersionUpgrade/VersionUpgrade22to24/plugin.json
index d213042ad2..7da1e7a56d 100644
--- a/plugins/VersionUpgrade/VersionUpgrade22to24/plugin.json
+++ b/plugins/VersionUpgrade/VersionUpgrade22to24/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Version Upgrade 2.2 to 2.4",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py b/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py
index 54b561c847..6dbcfebc46 100644
--- a/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py
+++ b/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py
@@ -1,32 +1,30 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse the files we need to upgrade and write the new files.
import io #To serialise configparser output to a string.
import os
+from typing import Dict, List, Set, Tuple
from urllib.parse import quote_plus
from UM.Resources import Resources
from UM.VersionUpgrade import VersionUpgrade
-from cura.CuraApplication import CuraApplication
-
_removed_settings = { #Settings that were removed in 2.5.
"start_layers_at_same_position",
"sub_div_rad_mult"
-}
+} # type: Set[str]
_split_settings = { #These settings should be copied to all settings it was split into.
"support_interface_line_distance": {"support_roof_line_distance", "support_bottom_line_distance"}
-}
+} # type: Dict[str, Set[str]]
## A collection of functions that convert the configuration of the user in Cura
# 2.5 to a configuration for Cura 2.6.
#
# All of these methods are essentially stateless.
class VersionUpgrade25to26(VersionUpgrade):
-
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self._current_fdm_printer_count = 2
@@ -41,18 +39,18 @@ class VersionUpgrade25to26(VersionUpgrade):
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
- def getCfgVersion(self, serialised):
+ def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
- setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
## Upgrades the preferences file from version 2.5 to 2.6.
#
# \param serialised The serialised form of a preferences file.
# \param filename The name of the file to upgrade.
- def upgradePreferences(self, serialised, filename):
+ def upgradePreferences(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
@@ -88,7 +86,7 @@ class VersionUpgrade25to26(VersionUpgrade):
#
# \param serialised The serialised form of a quality profile.
# \param filename The name of the file to upgrade.
- def upgradeInstanceContainer(self, serialised, filename):
+ def upgradeInstanceContainer(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
@@ -118,8 +116,8 @@ class VersionUpgrade25to26(VersionUpgrade):
#
# \param serialised The serialised form of a quality profile.
# \param filename The name of the file to upgrade.
- def upgradeMachineStack(self, serialised, filename):
- parser = configparser.ConfigParser(interpolation=None)
+ def upgradeMachineStack(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
# NOTE: This is for Custom FDM printers
@@ -151,8 +149,8 @@ class VersionUpgrade25to26(VersionUpgrade):
return [filename], [output.getvalue()]
## Acquires the next unique extruder stack index number for the Custom FDM Printer.
- def _acquireNextUniqueCustomFdmPrinterExtruderStackIdIndex(self):
- extruder_stack_dir = Resources.getPath(CuraApplication.ResourceTypes.ExtruderStack)
+ def _acquireNextUniqueCustomFdmPrinterExtruderStackIdIndex(self) -> int:
+ extruder_stack_dir = os.path.join(Resources.getDataStoragePath(), "extruders")
file_name_list = os.listdir(extruder_stack_dir)
file_name_list = [os.path.basename(file_name) for file_name in file_name_list]
while True:
@@ -171,9 +169,9 @@ class VersionUpgrade25to26(VersionUpgrade):
return self._current_fdm_printer_count
- def _checkCustomFdmPrinterHasExtruderStack(self, machine_id):
+ def _checkCustomFdmPrinterHasExtruderStack(self, machine_id: str) -> bool:
# go through all extruders and make sure that this custom FDM printer has extruder stacks.
- extruder_stack_dir = Resources.getPath(CuraApplication.ResourceTypes.ExtruderStack)
+ extruder_stack_dir = os.path.join(Resources.getDataStoragePath(), "extruders")
has_extruders = False
for item in os.listdir(extruder_stack_dir):
file_path = os.path.join(extruder_stack_dir, item)
@@ -199,7 +197,7 @@ class VersionUpgrade25to26(VersionUpgrade):
return has_extruders
- def _createCustomFdmPrinterExtruderStack(self, machine_id: str, position: int, quality_id: str, material_id: str):
+ def _createCustomFdmPrinterExtruderStack(self, machine_id: str, position: int, quality_id: str, material_id: str) -> None:
stack_id = "custom_extruder_%s" % (position + 1)
if self._current_fdm_printer_count > 1:
stack_id += " #%s" % self._current_fdm_printer_count
@@ -245,9 +243,9 @@ class VersionUpgrade25to26(VersionUpgrade):
parser.write(extruder_output)
extruder_filename = quote_plus(stack_id) + ".extruder.cfg"
- extruder_stack_dir = Resources.getPath(CuraApplication.ResourceTypes.ExtruderStack)
- definition_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.DefinitionChangesContainer)
- user_settings_dir = Resources.getPath(CuraApplication.ResourceTypes.UserInstanceContainer)
+ extruder_stack_dir = os.path.join(Resources.getDataStoragePath(), "extruders")
+ definition_changes_dir = os.path.join(Resources.getDataStoragePath(), "definition_changes")
+ user_settings_dir = os.path.join(Resources.getDataStoragePath(), "user")
with open(os.path.join(definition_changes_dir, definition_changes_filename), "w", encoding = "utf-8") as f:
f.write(definition_changes_output.getvalue())
@@ -258,7 +256,7 @@ class VersionUpgrade25to26(VersionUpgrade):
## Creates a definition changes container which doesn't contain anything for the Custom FDM Printers.
# The container ID will be automatically generated according to the given stack name.
- def _getCustomFdmPrinterDefinitionChanges(self, stack_id: str):
+ def _getCustomFdmPrinterDefinitionChanges(self, stack_id: str) -> configparser.ConfigParser:
# In 2.5, there is no definition_changes container for the Custom FDM printer, so it should be safe to use the
# default name unless some one names the printer as something like "Custom FDM Printer_settings".
definition_changes_id = stack_id + "_settings"
@@ -279,7 +277,7 @@ class VersionUpgrade25to26(VersionUpgrade):
## Creates a user settings container which doesn't contain anything for the Custom FDM Printers.
# The container ID will be automatically generated according to the given stack name.
- def _getCustomFdmPrinterUserSettings(self, stack_id: str):
+ def _getCustomFdmPrinterUserSettings(self, stack_id: str) -> configparser.ConfigParser:
# For the extruder stacks created in the upgrade, also create user_settings containers so the user changes
# will be saved.
user_settings_id = stack_id + "_user"
diff --git a/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py b/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py
index 1419325cc1..c74b3218b6 100644
--- a/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py
@@ -1,14 +1,16 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Any, Dict, TYPE_CHECKING
+
from . import VersionUpgrade25to26
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
+if TYPE_CHECKING:
+ from UM.Application import Application
upgrade = VersionUpgrade25to26.VersionUpgrade25to26()
-def getMetaData():
+def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
@@ -44,5 +46,5 @@ def getMetaData():
}
}
-def register(app):
+def register(app: "Application") -> Dict[str, Any]:
return { "version_upgrade": upgrade }
diff --git a/plugins/VersionUpgrade/VersionUpgrade25to26/plugin.json b/plugins/VersionUpgrade/VersionUpgrade25to26/plugin.json
index 759b6368fd..e1f0a47685 100644
--- a/plugins/VersionUpgrade/VersionUpgrade25to26/plugin.json
+++ b/plugins/VersionUpgrade/VersionUpgrade25to26/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Version Upgrade 2.5 to 2.6",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade26to27/VersionUpgrade26to27.py b/plugins/VersionUpgrade/VersionUpgrade26to27/VersionUpgrade26to27.py
index 2037a0211d..39e3dea4ed 100644
--- a/plugins/VersionUpgrade/VersionUpgrade26to27/VersionUpgrade26to27.py
+++ b/plugins/VersionUpgrade/VersionUpgrade26to27/VersionUpgrade26to27.py
@@ -1,11 +1,11 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse the files we need to upgrade and write the new files.
import io #To serialise configparser output to a string.
+from typing import Dict, List, Tuple
from UM.VersionUpgrade import VersionUpgrade
-from cura.CuraApplication import CuraApplication
# a dict of renamed quality profiles: :
_renamed_quality_profiles = {
@@ -62,7 +62,7 @@ _renamed_quality_profiles = {
"um3_bb0.8_TPU_Not_Supported_Quality": "um3_bb0.8_TPU_Fast_print",
"um3_bb0.8_TPU_Not_Supported_Superdraft_Quality": "um3_bb0.8_TPU_Superdraft_Print",
-}
+} # type: Dict[str, str]
## A collection of functions that convert the configuration of the user in Cura
# 2.6 to a configuration for Cura 2.7.
@@ -80,19 +80,19 @@ class VersionUpgrade26to27(VersionUpgrade):
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
- def getCfgVersion(self, serialised):
+ def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
- setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
## Upgrades a preferences file from version 2.6 to 2.7.
#
# \param serialised The serialised form of a preferences file.
# \param filename The name of the file to upgrade.
- def upgradePreferences(self, serialised, filename):
- parser = configparser.ConfigParser(interpolation=None)
+ def upgradePreferences(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
# Update version numbers
@@ -118,8 +118,8 @@ class VersionUpgrade26to27(VersionUpgrade):
#
# \param serialised The serialised form of a container file.
# \param filename The name of the file to upgrade.
- def upgradeOtherContainer(self, serialised, filename):
- parser = configparser.ConfigParser(interpolation=None)
+ def upgradeOtherContainer(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
# Update version numbers
@@ -140,7 +140,7 @@ class VersionUpgrade26to27(VersionUpgrade):
#
# \param serialised The serialised form of a container stack.
# \param filename The name of the file to upgrade.
- def upgradeStack(self, serialised, filename):
+ def upgradeStack(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
diff --git a/plugins/VersionUpgrade/VersionUpgrade26to27/__init__.py b/plugins/VersionUpgrade/VersionUpgrade26to27/__init__.py
index 79ed5e8b68..1952c9ceff 100644
--- a/plugins/VersionUpgrade/VersionUpgrade26to27/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade26to27/__init__.py
@@ -1,14 +1,16 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Any, Dict, TYPE_CHECKING
+
from . import VersionUpgrade26to27
-from UM.i18n import i18nCatalog
-catalog = i18nCatalog("cura")
+if TYPE_CHECKING:
+ from UM.Application import Application
upgrade = VersionUpgrade26to27.VersionUpgrade26to27()
-def getMetaData():
+def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
@@ -62,5 +64,5 @@ def getMetaData():
}
}
-def register(app):
+def register(app: "Application") -> Dict[str, Any]:
return { "version_upgrade": upgrade }
diff --git a/plugins/VersionUpgrade/VersionUpgrade26to27/plugin.json b/plugins/VersionUpgrade/VersionUpgrade26to27/plugin.json
index 3c3d7fff8c..6cdbd64cbb 100644
--- a/plugins/VersionUpgrade/VersionUpgrade26to27/plugin.json
+++ b/plugins/VersionUpgrade/VersionUpgrade26to27/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Version Upgrade 2.6 to 2.7",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade27to30/VersionUpgrade27to30.py b/plugins/VersionUpgrade/VersionUpgrade27to30/VersionUpgrade27to30.py
index 5a141f1558..b594c3c6c4 100644
--- a/plugins/VersionUpgrade/VersionUpgrade27to30/VersionUpgrade27to30.py
+++ b/plugins/VersionUpgrade/VersionUpgrade27to30/VersionUpgrade27to30.py
@@ -1,9 +1,10 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse preference files.
import io #To serialise the preference files afterwards.
import os
+from typing import Dict, List, Tuple
import urllib.parse
import re
@@ -11,7 +12,7 @@ from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
_renamed_themes = {
"cura": "cura-light"
-}
+} # type: Dict[str, str]
_renamed_i18n = {
"7s": "en_7S",
"de": "de_DE",
@@ -28,7 +29,7 @@ _renamed_i18n = {
"ptbr": "pt_BR",
"ru": "ru_RU",
"tr": "tr_TR"
-}
+} # type: Dict[str, str]
class VersionUpgrade27to30(VersionUpgrade):
@@ -43,19 +44,19 @@ class VersionUpgrade27to30(VersionUpgrade):
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
- def getCfgVersion(self, serialised):
+ def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
- setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
## Upgrades a preferences file from version 2.7 to 3.0.
#
# \param serialised The serialised form of a preferences file.
# \param filename The name of the file to upgrade.
- def upgradePreferences(self, serialised, filename):
- parser = configparser.ConfigParser(interpolation=None)
+ def upgradePreferences(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
# Update version numbers
@@ -100,8 +101,8 @@ class VersionUpgrade27to30(VersionUpgrade):
#
# \param serialised The serialised form of the container file.
# \param filename The name of the file to upgrade.
- def upgradeQualityChangesContainer(self, serialised, filename):
- parser = configparser.ConfigParser(interpolation=None)
+ def upgradeQualityChangesContainer(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
# Update the skin pre-shrink settings:
@@ -156,8 +157,8 @@ class VersionUpgrade27to30(VersionUpgrade):
#
# \param serialised The serialised form of the container file.
# \param filename The name of the file to upgrade.
- def upgradeOtherContainer(self, serialised, filename):
- parser = configparser.ConfigParser(interpolation=None)
+ def upgradeOtherContainer(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
# Update the skin pre-shrink settings:
@@ -185,7 +186,7 @@ class VersionUpgrade27to30(VersionUpgrade):
#
# \param serialised The serialised form of a container stack.
# \param filename The name of the file to upgrade.
- def upgradeStack(self, serialised, filename):
+ def upgradeStack(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation=None)
parser.read_string(serialised)
diff --git a/plugins/VersionUpgrade/VersionUpgrade27to30/__init__.py b/plugins/VersionUpgrade/VersionUpgrade27to30/__init__.py
index 4da7257b1c..bddc71a1e0 100644
--- a/plugins/VersionUpgrade/VersionUpgrade27to30/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade27to30/__init__.py
@@ -1,11 +1,16 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Any, Dict, TYPE_CHECKING
+
from . import VersionUpgrade27to30
+if TYPE_CHECKING:
+ from UM.Application import Application
+
upgrade = VersionUpgrade27to30.VersionUpgrade27to30()
-def getMetaData():
+def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
@@ -51,5 +56,5 @@ def getMetaData():
}
}
-def register(app):
+def register(app: "Application") -> Dict[str, Any]:
return { "version_upgrade": upgrade }
diff --git a/plugins/VersionUpgrade/VersionUpgrade27to30/plugin.json b/plugins/VersionUpgrade/VersionUpgrade27to30/plugin.json
index 3df84ff7e6..885d741a8c 100644
--- a/plugins/VersionUpgrade/VersionUpgrade27to30/plugin.json
+++ b/plugins/VersionUpgrade/VersionUpgrade27to30/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Version Upgrade 2.7 to 3.0",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py
index a88ff5ac1c..f0b2e939b9 100644
--- a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py
+++ b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py
@@ -1,14 +1,15 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse preference files.
import io #To serialise the preference files afterwards.
+from typing import Dict, List, Set, Tuple
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
# a list of all legacy "Not Supported" quality profiles
-_OLD_NOT_SUPPORTED_PROFILES = [
+_OLD_NOT_SUPPORTED_PROFILES = {
"um2p_pp_0.25_normal",
"um2p_tpu_0.8_normal",
"um3_bb0.4_ABS_Fast_Print",
@@ -42,7 +43,7 @@ _OLD_NOT_SUPPORTED_PROFILES = [
"um3_bb0.8_PP_Superdraft_Print",
"um3_bb0.8_TPU_Fast_print",
"um3_bb0.8_TPU_Superdraft_Print",
-]
+} # type: Set[str]
# Some containers have their specific empty containers, those need to be set correctly.
@@ -51,13 +52,13 @@ _EMPTY_CONTAINER_DICT = {
"2": "empty_quality",
"3": "empty_material",
"4": "empty_variant",
-}
+} # type: Dict[str, str]
# Renamed definition files
_RENAMED_DEFINITION_DICT = {
"jellybox": "imade3d_jellybox",
-}
+} # type: Dict[str, str]
class VersionUpgrade30to31(VersionUpgrade):
@@ -72,19 +73,19 @@ class VersionUpgrade30to31(VersionUpgrade):
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
- def getCfgVersion(self, serialised):
+ def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
- setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
## Upgrades a preferences file from version 3.0 to 3.1.
#
# \param serialised The serialised form of a preferences file.
# \param filename The name of the file to upgrade.
- def upgradePreferences(self, serialised, filename):
- parser = configparser.ConfigParser(interpolation=None)
+ def upgradePreferences(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
# Update version numbers
@@ -104,8 +105,8 @@ class VersionUpgrade30to31(VersionUpgrade):
#
# \param serialised The serialised form of the container file.
# \param filename The name of the file to upgrade.
- def upgradeInstanceContainer(self, serialised, filename):
- parser = configparser.ConfigParser(interpolation=None)
+ def upgradeInstanceContainer(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
for each_section in ("general", "metadata"):
@@ -129,8 +130,8 @@ class VersionUpgrade30to31(VersionUpgrade):
#
# \param serialised The serialised form of a container stack.
# \param filename The name of the file to upgrade.
- def upgradeStack(self, serialised, filename):
- parser = configparser.ConfigParser(interpolation=None)
+ def upgradeStack(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
for each_section in ("general", "metadata"):
diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py b/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py
index 7b2c213a31..c5cc851d6a 100644
--- a/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py
@@ -1,11 +1,16 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Any, Dict, TYPE_CHECKING
+
from . import VersionUpgrade30to31
+if TYPE_CHECKING:
+ from UM.Application import Application
+
upgrade = VersionUpgrade30to31.VersionUpgrade30to31()
-def getMetaData():
+def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
@@ -55,5 +60,5 @@ def getMetaData():
}
}
-def register(app):
+def register(app: "Application") -> Dict[str, Any]:
return { "version_upgrade": upgrade }
diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/plugin.json b/plugins/VersionUpgrade/VersionUpgrade30to31/plugin.json
index d80b820976..d5f22649c1 100644
--- a/plugins/VersionUpgrade/VersionUpgrade30to31/plugin.json
+++ b/plugins/VersionUpgrade/VersionUpgrade30to31/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Version Upgrade 3.0 to 3.1",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py
index 18851b82c7..83cb15c864 100644
--- a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py
+++ b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py
@@ -3,6 +3,7 @@
import configparser #To parse preference files.
import io #To serialise the preference files afterwards.
+from typing import Dict, List, Tuple
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
@@ -51,22 +52,22 @@ _EXTRUDER_TO_POSITION = {
"ultimaker_original_dual_2nd": 1,
"vertex_k8400_dual_1st": 0,
"vertex_k8400_dual_2nd": 1
-}
+} # type: Dict[str, int]
_RENAMED_QUALITY_PROFILES = {
"low": "fast",
"um2_low": "um2_fast"
-}
+} # type: Dict[str, str]
_RENAMED_QUALITY_TYPES = {
"low": "fast"
-}
+} # type: Dict[str, str]
## Upgrades configurations from the state they were in at version 3.2 to the
# state they should be in at version 3.3.
class VersionUpgrade32to33(VersionUpgrade):
-
temporary_group_name_counter = 1
+
## Gets the version number from a CFG file in Uranium's 3.2 format.
#
# Since the format may change, this is implemented for the 3.2 format only
@@ -78,18 +79,18 @@ class VersionUpgrade32to33(VersionUpgrade):
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
- def getCfgVersion(self, serialised):
+ def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
- setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
## Upgrades a preferences file from version 3.2 to 3.3.
#
# \param serialised The serialised form of a preferences file.
# \param filename The name of the file to upgrade.
- def upgradePreferences(self, serialised, filename):
+ def upgradePreferences(self, serialised: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
@@ -117,7 +118,7 @@ class VersionUpgrade32to33(VersionUpgrade):
#
# \param serialised The serialised form of a container stack.
# \param filename The name of the file to upgrade.
- def upgradeStack(self, serialized, filename):
+ def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
@@ -141,7 +142,7 @@ class VersionUpgrade32to33(VersionUpgrade):
## Upgrades non-quality-changes instance containers to have the new version
# number.
- def upgradeInstanceContainer(self, serialized, filename):
+ def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
@@ -153,7 +154,7 @@ class VersionUpgrade32to33(VersionUpgrade):
return [filename], [result.getvalue()]
## Upgrades a quality changes container to the new format.
- def upgradeQualityChanges(self, serialized, filename):
+ def upgradeQualityChanges(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
@@ -182,7 +183,7 @@ class VersionUpgrade32to33(VersionUpgrade):
return [filename], [result.getvalue()]
## Upgrades a variant container to the new format.
- def upgradeVariants(self, serialized, filename):
+ def upgradeVariants(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py
index 5073be772d..b55ea5ebaf 100644
--- a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py
@@ -1,11 +1,16 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Any, Dict, TYPE_CHECKING
+
from . import VersionUpgrade32to33
+if TYPE_CHECKING:
+ from UM.Application import Application
+
upgrade = VersionUpgrade32to33.VersionUpgrade32to33()
-def getMetaData():
+def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
@@ -42,7 +47,7 @@ def getMetaData():
},
"user": {
"get_version": upgrade.getCfgVersion,
- "location": {"./user"}
+ "location": {"./user", "./materials/*"}
},
"variant": {
"get_version": upgrade.getCfgVersion,
@@ -51,5 +56,5 @@ def getMetaData():
}
}
-def register(app):
+def register(app: "Application") -> Dict[str, Any]:
return { "version_upgrade": upgrade }
\ No newline at end of file
diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json b/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json
index fbce09c807..eb489169e0 100644
--- a/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json
+++ b/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Version Upgrade 3.2 to 3.3",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade33to34/VersionUpgrade33to34.py b/plugins/VersionUpgrade/VersionUpgrade33to34/VersionUpgrade33to34.py
index e2241fd195..704ede02d6 100644
--- a/plugins/VersionUpgrade/VersionUpgrade33to34/VersionUpgrade33to34.py
+++ b/plugins/VersionUpgrade/VersionUpgrade33to34/VersionUpgrade33to34.py
@@ -3,17 +3,17 @@
import configparser #To parse preference files.
import io #To serialise the preference files afterwards.
+from typing import Dict, List, Tuple
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
_renamed_settings = {
"infill_hollow": "infill_support_enabled"
-}
+} # type: Dict[str, str]
## Upgrades configurations from the state they were in at version 3.3 to the
# state they should be in at version 3.4.
class VersionUpgrade33to34(VersionUpgrade):
-
## Gets the version number from a CFG file in Uranium's 3.3 format.
#
# Since the format may change, this is implemented for the 3.3 format only
@@ -25,16 +25,16 @@ class VersionUpgrade33to34(VersionUpgrade):
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
- def getCfgVersion(self, serialised):
+ def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
- setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
## Upgrades instance containers to have the new version
# number.
- def upgradeInstanceContainer(self, serialized, filename):
+ def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
diff --git a/plugins/VersionUpgrade/VersionUpgrade33to34/__init__.py b/plugins/VersionUpgrade/VersionUpgrade33to34/__init__.py
index 4faa1290b5..5fd757f843 100644
--- a/plugins/VersionUpgrade/VersionUpgrade33to34/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade33to34/__init__.py
@@ -1,17 +1,22 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Any, Dict, TYPE_CHECKING
+
from . import VersionUpgrade33to34
+if TYPE_CHECKING:
+ from UM.Application import Application
+
upgrade = VersionUpgrade33to34.VersionUpgrade33to34()
-
-def getMetaData():
+def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
("definition_changes", 3000004): ("definition_changes", 4000004, upgrade.upgradeInstanceContainer),
("quality_changes", 3000004): ("quality_changes", 4000004, upgrade.upgradeInstanceContainer),
+ ("quality", 3000004): ("quality", 4000004, upgrade.upgradeInstanceContainer),
("user", 3000004): ("user", 4000004, upgrade.upgradeInstanceContainer),
},
"sources": {
@@ -23,6 +28,10 @@ def getMetaData():
"get_version": upgrade.getCfgVersion,
"location": {"./quality_changes"}
},
+ "quality": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./quality"}
+ },
"user": {
"get_version": upgrade.getCfgVersion,
"location": {"./user"}
@@ -31,5 +40,5 @@ def getMetaData():
}
-def register(app):
+def register(app: "Application") -> Dict[str, Any]:
return { "version_upgrade": upgrade }
diff --git a/plugins/VersionUpgrade/VersionUpgrade33to34/plugin.json b/plugins/VersionUpgrade/VersionUpgrade33to34/plugin.json
index 164b79d504..9649010643 100644
--- a/plugins/VersionUpgrade/VersionUpgrade33to34/plugin.json
+++ b/plugins/VersionUpgrade/VersionUpgrade33to34/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Version Upgrade 3.3 to 3.4",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade34to40/VersionUpgrade34to40.py b/plugins/VersionUpgrade/VersionUpgrade34to35/VersionUpgrade34to35.py
similarity index 86%
rename from plugins/VersionUpgrade/VersionUpgrade34to40/VersionUpgrade34to40.py
rename to plugins/VersionUpgrade/VersionUpgrade34to35/VersionUpgrade34to35.py
index a61aae06bb..8e45d7cf73 100644
--- a/plugins/VersionUpgrade/VersionUpgrade34to40/VersionUpgrade34to40.py
+++ b/plugins/VersionUpgrade/VersionUpgrade34to35/VersionUpgrade34to35.py
@@ -3,13 +3,14 @@
import configparser
import io
+from typing import Dict, List, Set, Tuple
from UM.VersionUpgrade import VersionUpgrade
-deleted_settings = {"prime_tower_wall_thickness", "dual_pre_wipe", "prime_tower_purge_volume"}
+deleted_settings = {"prime_tower_wall_thickness", "dual_pre_wipe", "prime_tower_purge_volume"} # type: Set[str]
-changed_settings = {'retraction_combing': 'noskin'}
-updated_settings = {'retraction_combing': 'infill'}
+changed_settings = {"retraction_combing": "noskin"} # type: Dict[str, str]
+updated_settings = {"retraction_combing": "infill"} # type: Dict[str, str]
_RENAMED_MATERIAL_PROFILES = {
"dsm_arnitel2045_175_cartesio_0.25_mm": "dsm_arnitel2045_175_cartesio_0.25mm_thermoplastic_extruder",
@@ -57,15 +58,14 @@ _RENAMED_MATERIAL_PROFILES = {
"ultimaker_pva_cartesio_0.25_mm": "ultimaker_pva_cartesio_0.25mm_thermoplastic_extruder",
"ultimaker_pva_cartesio_0.4_mm": "ultimaker_pva_cartesio_0.4mm_thermoplastic_extruder",
"ultimaker_pva_cartesio_0.8_mm": "ultimaker_pva_cartesio_0.8mm_thermoplastic_extruder"
-}
+} # type: Dict[str, str]
## Upgrades configurations from the state they were in at version 3.4 to the
-# state they should be in at version 4.0.
-class VersionUpgrade34to40(VersionUpgrade):
-
- ## Gets the version number from a CFG file in Uranium's 3.3 format.
+# state they should be in at version 3.5.
+class VersionUpgrade34to35(VersionUpgrade):
+ ## Gets the version number from a CFG file in Uranium's 3.4 format.
#
- # Since the format may change, this is implemented for the 3.3 format only
+ # Since the format may change, this is implemented for the 3.4 format only
# and needs to be included in the version upgrade system rather than
# globally in Uranium.
#
@@ -74,18 +74,24 @@ class VersionUpgrade34to40(VersionUpgrade):
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
- def getCfgVersion(self, serialised):
+ def getCfgVersion(self, serialised: str) -> int:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
- setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
return format_version * 1000000 + setting_version
## Upgrades Preferences to have the new version number.
- def upgradePreferences(self, serialized, filename):
+ def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
+ # Need to show the data collection agreement again because the data Cura collects has been changed.
+ if parser.has_option("info", "asked_send_slice_info"):
+ parser.set("info", "asked_send_slice_info", "False")
+ if parser.has_option("info", "send_slice_info"):
+ parser.set("info", "send_slice_info", "True")
+
# Update version number.
parser["general"]["version"] = "6"
if "metadata" not in parser:
@@ -97,7 +103,7 @@ class VersionUpgrade34to40(VersionUpgrade):
return [filename], [result.getvalue()]
## Upgrades stacks to have the new version number.
- def upgradeStack(self, serialized, filename):
+ def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
@@ -115,7 +121,7 @@ class VersionUpgrade34to40(VersionUpgrade):
## Upgrades instance containers to have the new version
# number.
- def upgradeInstanceContainer(self, serialized, filename):
+ def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
@@ -141,7 +147,7 @@ class VersionUpgrade34to40(VersionUpgrade):
parser.write(result)
return [filename], [result.getvalue()]
- def _resetConcentric3DInfillPattern(self, parser):
+ def _resetConcentric3DInfillPattern(self, parser: configparser.ConfigParser) -> None:
if "values" not in parser:
return
@@ -155,5 +161,4 @@ class VersionUpgrade34to40(VersionUpgrade):
if key not in parser["values"]:
continue
if parser["values"][key] == "concentric_3d":
- del parser["values"][key]
-
+ del parser["values"][key]
\ No newline at end of file
diff --git a/plugins/VersionUpgrade/VersionUpgrade34to40/__init__.py b/plugins/VersionUpgrade/VersionUpgrade34to35/__init__.py
similarity index 77%
rename from plugins/VersionUpgrade/VersionUpgrade34to40/__init__.py
rename to plugins/VersionUpgrade/VersionUpgrade34to35/__init__.py
index ad7a33b61a..332bc827b9 100644
--- a/plugins/VersionUpgrade/VersionUpgrade34to40/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade34to35/__init__.py
@@ -1,12 +1,16 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from . import VersionUpgrade34to40
+from typing import Any, Dict, TYPE_CHECKING
-upgrade = VersionUpgrade34to40.VersionUpgrade34to40()
+from . import VersionUpgrade34to35
+if TYPE_CHECKING:
+ from UM.Application import Application
-def getMetaData():
+upgrade = VersionUpgrade34to35.VersionUpgrade34to35()
+
+def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
@@ -14,6 +18,7 @@ def getMetaData():
("definition_changes", 4000004): ("definition_changes", 4000005, upgrade.upgradeInstanceContainer),
("quality_changes", 4000004): ("quality_changes", 4000005, upgrade.upgradeInstanceContainer),
+ ("quality", 4000004): ("quality", 4000005, upgrade.upgradeInstanceContainer),
("user", 4000004): ("user", 4000005, upgrade.upgradeInstanceContainer),
("machine_stack", 4000004): ("machine_stack", 4000005, upgrade.upgradeStack),
@@ -40,6 +45,10 @@ def getMetaData():
"get_version": upgrade.getCfgVersion,
"location": {"./quality_changes"}
},
+ "quality": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./quality"}
+ },
"user": {
"get_version": upgrade.getCfgVersion,
"location": {"./user"}
@@ -48,5 +57,5 @@ def getMetaData():
}
-def register(app):
+def register(app: "Application") -> Dict[str, Any]:
return { "version_upgrade": upgrade }
diff --git a/plugins/VersionUpgrade/VersionUpgrade34to40/plugin.json b/plugins/VersionUpgrade/VersionUpgrade34to35/plugin.json
similarity index 56%
rename from plugins/VersionUpgrade/VersionUpgrade34to40/plugin.json
rename to plugins/VersionUpgrade/VersionUpgrade34to35/plugin.json
index 1059ca3e57..71b13ee5a9 100644
--- a/plugins/VersionUpgrade/VersionUpgrade34to40/plugin.json
+++ b/plugins/VersionUpgrade/VersionUpgrade34to35/plugin.json
@@ -1,8 +1,8 @@
- {
- "name": "Version Upgrade 3.4 to 4.0",
+{
+ "name": "Version Upgrade 3.4 to 3.5",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
- "description": "Upgrades configurations from Cura 3.4 to Cura 4.0.",
- "api": 4,
+ "version": "1.0.1",
+ "description": "Upgrades configurations from Cura 3.4 to Cura 3.5.",
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py b/plugins/VersionUpgrade/VersionUpgrade34to35/tests/TestVersionUpgrade34to35.py
similarity index 68%
rename from plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py
rename to plugins/VersionUpgrade/VersionUpgrade34to35/tests/TestVersionUpgrade34to35.py
index 22df0d6487..b74e6f35ac 100644
--- a/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py
+++ b/plugins/VersionUpgrade/VersionUpgrade34to35/tests/TestVersionUpgrade34to35.py
@@ -4,12 +4,12 @@
import configparser #To parse the resulting config files.
import pytest #To register tests with.
-import VersionUpgrade34to40 #The module we're testing.
+import VersionUpgrade34to35 #The module we're testing.
## Creates an instance of the upgrader to test with.
@pytest.fixture
def upgrader():
- return VersionUpgrade34to40.VersionUpgrade34to40()
+ return VersionUpgrade34to35.VersionUpgrade34to35()
test_upgrade_version_nr_data = [
("Empty config file",
@@ -17,6 +17,10 @@ test_upgrade_version_nr_data = [
version = 5
[metadata]
setting_version = 4
+
+ [info]
+ asked_send_slice_info = True
+ send_slice_info = True
"""
)
]
@@ -32,4 +36,8 @@ def test_upgradeVersionNr(test_name, file_data, upgrader):
#Check the new version.
assert parser["general"]["version"] == "6"
- assert parser["metadata"]["setting_version"] == "5"
\ No newline at end of file
+ assert parser["metadata"]["setting_version"] == "5"
+
+ # Check if the data collection values have been reset to their defaults
+ assert parser.get("info", "asked_send_slice_info") == "False"
+ assert parser.get("info", "send_slice_info") == "True"
diff --git a/plugins/VersionUpgrade/VersionUpgrade35to40/VersionUpgrade35to40.py b/plugins/VersionUpgrade/VersionUpgrade35to40/VersionUpgrade35to40.py
new file mode 100644
index 0000000000..900c0a7396
--- /dev/null
+++ b/plugins/VersionUpgrade/VersionUpgrade35to40/VersionUpgrade35to40.py
@@ -0,0 +1,77 @@
+import configparser
+from typing import Tuple, List, Set, Dict
+import io
+
+from UM.VersionUpgrade import VersionUpgrade
+from cura.PrinterOutputDevice import ConnectionType
+
+deleted_settings = {"bridge_wall_max_overhang"} # type: Set[str]
+renamed_configurations = {"connect_group_name": "group_name"} # type: Dict[str, str]
+
+
+class VersionUpgrade35to40(VersionUpgrade):
+ # Upgrades stacks to have the new version number.
+ def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation=None)
+ parser.read_string(serialized)
+
+ # Update version number.
+ parser["general"]["version"] = "4"
+ parser["metadata"]["setting_version"] = "6"
+
+ if parser["metadata"].get("um_network_key") is not None or parser["metadata"].get("octoprint_api_key") is not None:
+ # Set the connection type if um_network_key or the octoprint key is set.
+ parser["metadata"]["connection_type"] = str(ConnectionType.NetworkConnection.value)
+
+ if "metadata" in parser:
+ for old_name, new_name in renamed_configurations.items():
+ if old_name not in parser["metadata"]:
+ continue
+ parser["metadata"][new_name] = parser["metadata"][old_name]
+ del parser["metadata"][old_name]
+
+ result = io.StringIO()
+ parser.write(result)
+ return [filename], [result.getvalue()]
+
+ def getCfgVersion(self, serialised: str) -> int:
+ parser = configparser.ConfigParser(interpolation = None)
+ parser.read_string(serialised)
+ format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
+ return format_version * 1000000 + setting_version
+
+ ## Upgrades Preferences to have the new version number.
+ def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation=None)
+ parser.read_string(serialized)
+
+ if "metadata" not in parser:
+ parser["metadata"] = {}
+ parser["general"]["version"] = "6"
+ parser["metadata"]["setting_version"] = "6"
+
+ result = io.StringIO()
+ parser.write(result)
+ return [filename], [result.getvalue()]
+
+ ## Upgrades instance containers to have the new version
+ # number.
+ def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation=None)
+ parser.read_string(serialized)
+
+ # Update version number.
+ parser["general"]["version"] = "4"
+ parser["metadata"]["setting_version"] = "6"
+
+ #self._resetConcentric3DInfillPattern(parser)
+ if "values" in parser:
+ for deleted_setting in deleted_settings:
+ if deleted_setting not in parser["values"]:
+ continue
+ del parser["values"][deleted_setting]
+
+ result = io.StringIO()
+ parser.write(result)
+ return [filename], [result.getvalue()]
diff --git a/plugins/VersionUpgrade/VersionUpgrade35to40/__init__.py b/plugins/VersionUpgrade/VersionUpgrade35to40/__init__.py
new file mode 100644
index 0000000000..2ad1dddbf2
--- /dev/null
+++ b/plugins/VersionUpgrade/VersionUpgrade35to40/__init__.py
@@ -0,0 +1,56 @@
+from typing import Dict, Any
+
+from . import VersionUpgrade35to40
+
+upgrade = VersionUpgrade35to40.VersionUpgrade35to40()
+
+
+def getMetaData() -> Dict[str, Any]:
+ return {
+ "version_upgrade": {
+ # From To Upgrade function
+ ("preferences", 6000005): ("preferences", 6000006, upgrade.upgradePreferences),
+
+ ("definition_changes", 4000005): ("definition_changes", 4000006, upgrade.upgradeInstanceContainer),
+ ("quality_changes", 4000005): ("quality_changes", 4000006, upgrade.upgradeInstanceContainer),
+ ("quality", 4000005): ("quality", 4000006, upgrade.upgradeInstanceContainer),
+ ("user", 4000005): ("user", 4000006, upgrade.upgradeInstanceContainer),
+
+ ("machine_stack", 4000005): ("machine_stack", 4000006, upgrade.upgradeStack),
+ ("extruder_train", 4000005): ("extruder_train", 4000006, upgrade.upgradeStack),
+ },
+ "sources": {
+ "preferences": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"."}
+ },
+ "machine_stack": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./machine_instances"}
+ },
+ "extruder_train": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./extruders"}
+ },
+ "definition_changes": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./definition_changes"}
+ },
+ "quality_changes": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./quality_changes"}
+ },
+ "quality": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./quality"}
+ },
+ "user": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./user"}
+ }
+ }
+ }
+
+
+def register(app) -> Dict[str, Any]:
+ return {"version_upgrade": upgrade}
\ No newline at end of file
diff --git a/plugins/VersionUpgrade/VersionUpgrade35to40/plugin.json b/plugins/VersionUpgrade/VersionUpgrade35to40/plugin.json
new file mode 100644
index 0000000000..578594fb6d
--- /dev/null
+++ b/plugins/VersionUpgrade/VersionUpgrade35to40/plugin.json
@@ -0,0 +1,8 @@
+ {
+ "name": "Version Upgrade 3.5 to 4.0",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.0",
+ "description": "Upgrades configurations from Cura 3.5 to Cura 4.0.",
+ "api": "6.0",
+ "i18n-catalog": "cura"
+}
diff --git a/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py b/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py
new file mode 100644
index 0000000000..d80e0007aa
--- /dev/null
+++ b/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py
@@ -0,0 +1,86 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import configparser
+import io
+from typing import Dict, List, Tuple
+
+from UM.VersionUpgrade import VersionUpgrade
+
+_renamed_quality_profiles = {
+ "gmax15plus_pla_dual_normal": "gmax15plus_global_dual_normal",
+ "gmax15plus_pla_dual_thick": "gmax15plus_global_dual_thick",
+ "gmax15plus_pla_dual_thin": "gmax15plus_global_dual_thin",
+ "gmax15plus_pla_dual_very_thick": "gmax15plus_global_dual_very_thick",
+ "gmax15plus_pla_normal": "gmax15plus_global_normal",
+ "gmax15plus_pla_thick": "gmax15plus_global_thick",
+ "gmax15plus_pla_thin": "gmax15plus_global_thin",
+ "gmax15plus_pla_very_thick": "gmax15plus_global_very_thick"
+} # type: Dict[str, str]
+
+## Upgrades configurations from the state they were in at version 4.0 to the
+# state they should be in at version 4.1.
+class VersionUpgrade40to41(VersionUpgrade):
+ ## Gets the version number from a CFG file in Uranium's 4.0 format.
+ #
+ # Since the format may change, this is implemented for the 4.0 format only
+ # and needs to be included in the version upgrade system rather than
+ # globally in Uranium.
+ #
+ # \param serialised The serialised form of a CFG file.
+ # \return The version number stored in the CFG file.
+ # \raises ValueError The format of the version number in the file is
+ # incorrect.
+ # \raises KeyError The format of the file is incorrect.
+ def getCfgVersion(self, serialised: str) -> int:
+ parser = configparser.ConfigParser(interpolation = None)
+ parser.read_string(serialised)
+ format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
+ setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
+ return format_version * 1000000 + setting_version
+
+ ## Upgrades instance containers to have the new version
+ # number.
+ def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+
+ # Update version number.
+ parser["general"]["version"] = "4"
+ parser["metadata"]["setting_version"] = "7"
+
+ result = io.StringIO()
+ parser.write(result)
+ return [filename], [result.getvalue()]
+
+ ## Upgrades Preferences to have the new version number.
+ def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+
+ # Update version number.
+ parser["general"]["version"] = "6"
+ if "metadata" not in parser:
+ parser["metadata"] = {}
+ parser["metadata"]["setting_version"] = "7"
+
+ result = io.StringIO()
+ parser.write(result)
+ return [filename], [result.getvalue()]
+
+ ## Upgrades stacks to have the new version number.
+ def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
+ parser = configparser.ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+
+ # Update version number.
+ parser["general"]["version"] = "4"
+ parser["metadata"]["setting_version"] = "7"
+
+ #Update the name of the quality profile.
+ if parser["containers"]["4"] in _renamed_quality_profiles:
+ parser["containers"]["4"] = _renamed_quality_profiles[parser["containers"]["4"]]
+
+ result = io.StringIO()
+ parser.write(result)
+ return [filename], [result.getvalue()]
diff --git a/plugins/VersionUpgrade/VersionUpgrade40to41/__init__.py b/plugins/VersionUpgrade/VersionUpgrade40to41/__init__.py
new file mode 100644
index 0000000000..7f39bb9d39
--- /dev/null
+++ b/plugins/VersionUpgrade/VersionUpgrade40to41/__init__.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Any, Dict, TYPE_CHECKING
+
+from . import VersionUpgrade40to41
+
+if TYPE_CHECKING:
+ from UM.Application import Application
+
+upgrade = VersionUpgrade40to41.VersionUpgrade40to41()
+
+def getMetaData() -> Dict[str, Any]:
+ return {
+ "version_upgrade": {
+ # From To Upgrade function
+ ("preferences", 6000006): ("preferences", 6000007, upgrade.upgradePreferences),
+ ("machine_stack", 4000006): ("machine_stack", 4000007, upgrade.upgradeStack),
+ ("extruder_train", 4000006): ("extruder_train", 4000007, upgrade.upgradeStack),
+ ("definition_changes", 4000006): ("definition_changes", 4000007, upgrade.upgradeInstanceContainer),
+ ("quality_changes", 4000006): ("quality_changes", 4000007, upgrade.upgradeInstanceContainer),
+ ("quality", 4000006): ("quality", 4000007, upgrade.upgradeInstanceContainer),
+ ("user", 4000006): ("user", 4000007, upgrade.upgradeInstanceContainer),
+ },
+ "sources": {
+ "preferences": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"."}
+ },
+ "machine_stack": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./machine_instances"}
+ },
+ "extruder_train": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./extruders"}
+ },
+ "definition_changes": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./definition_changes"}
+ },
+ "quality_changes": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./quality_changes"}
+ },
+ "quality": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./quality"}
+ },
+ "user": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./user"}
+ }
+ }
+ }
+
+
+def register(app: "Application") -> Dict[str, Any]:
+ return { "version_upgrade": upgrade }
diff --git a/plugins/VersionUpgrade/VersionUpgrade40to41/plugin.json b/plugins/VersionUpgrade/VersionUpgrade40to41/plugin.json
new file mode 100644
index 0000000000..b1c6d75669
--- /dev/null
+++ b/plugins/VersionUpgrade/VersionUpgrade40to41/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Version Upgrade 4.0 to 4.1",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.1",
+ "description": "Upgrades configurations from Cura 4.0 to Cura 4.1.",
+ "api": "6.0",
+ "i18n-catalog": "cura"
+}
diff --git a/plugins/X3DReader/plugin.json b/plugins/X3DReader/plugin.json
index f18c7f033d..1fc14104ed 100644
--- a/plugins/X3DReader/plugin.json
+++ b/plugins/X3DReader/plugin.json
@@ -1,8 +1,8 @@
{
"name": "X3D Reader",
"author": "Seva Alekseyev",
- "version": "0.5.0",
+ "version": "1.0.1",
"description": "Provides support for reading X3D files.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/XRayView/XRayView.py b/plugins/XRayView/XRayView.py
index 0c4035c62d..86533fe51c 100644
--- a/plugins/XRayView/XRayView.py
+++ b/plugins/XRayView/XRayView.py
@@ -17,6 +17,7 @@ from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
from cura.CuraApplication import CuraApplication
+from cura.Scene.ConvexHullNode import ConvexHullNode
from . import XRayPass
@@ -41,6 +42,10 @@ class XRayView(View):
self._xray_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("xray").getRgb()))
for node in BreadthFirstIterator(scene.getRoot()):
+ # We do not want to render ConvexHullNode as it conflicts with the bottom of the X-Ray (z-fighting).
+ if type(node) is ConvexHullNode:
+ continue
+
if not node.render(renderer):
if node.getMeshData() and node.isVisible():
renderer.queueNode(node,
diff --git a/plugins/XRayView/plugin.json b/plugins/XRayView/plugin.json
index 4e89690c13..71cc165b6c 100644
--- a/plugins/XRayView/plugin.json
+++ b/plugins/XRayView/plugin.json
@@ -1,8 +1,8 @@
{
"name": "X-Ray View",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides the X-Ray view.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py
index 7d9b2aacc3..b6dff65403 100644
--- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py
+++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import copy
@@ -6,7 +6,7 @@ import io
import json #To parse the product-to-id mapping file.
import os.path #To find the product-to-id mapping.
import sys
-from typing import Any, Dict, List, Optional, Tuple, cast
+from typing import Any, Dict, List, Optional, Tuple, cast, Set
import xml.etree.ElementTree as ET
from UM.Resources import Resources
@@ -60,6 +60,7 @@ class XmlMaterialProfile(InstanceContainer):
def setMetaDataEntry(self, key, value, apply_to_all = True):
registry = ContainerRegistry.getInstance()
if registry.isReadOnly(self.getId()):
+ Logger.log("w", "Can't change metadata {key} of material {material_id} because it's read-only.".format(key = key, material_id = self.getId()))
return
# Prevent recursion
@@ -71,7 +72,9 @@ class XmlMaterialProfile(InstanceContainer):
material_manager = CuraApplication.getInstance().getMaterialManager()
root_material_id = self.getMetaDataEntry("base_file") #if basefile is self.getId, this is a basefile.
material_group = material_manager.getMaterialGroup(root_material_id)
-
+ if not material_group: #If the profile is not registered in the registry but loose/temporary, it will not have a base file tree.
+ super().setMetaDataEntry(key, value)
+ return
# Update the root material container
root_material_container = material_group.root_material_node.getContainer()
if root_material_container is not None:
@@ -117,7 +120,7 @@ class XmlMaterialProfile(InstanceContainer):
## Overridden from InstanceContainer
# base file: common settings + supported machines
# machine / variant combination: only changes for itself.
- def serialize(self, ignored_metadata_keys: Optional[set] = None):
+ def serialize(self, ignored_metadata_keys: Optional[Set[str]] = None):
registry = ContainerRegistry.getInstance()
base_file = self.getMetaDataEntry("base_file", "")
@@ -141,23 +144,13 @@ class XmlMaterialProfile(InstanceContainer):
# setting_version is derived from the "version" tag in the schema, so don't serialize it into a file
if ignored_metadata_keys is None:
ignored_metadata_keys = set()
- ignored_metadata_keys |= {"setting_version"}
+ ignored_metadata_keys |= {"setting_version", "definition", "status", "variant", "type", "base_file", "approximate_diameter", "id", "container_type", "name"}
# remove the keys that we want to ignore in the metadata
for key in ignored_metadata_keys:
if key in metadata:
del metadata[key]
properties = metadata.pop("properties", {})
- # Metadata properties that should not be serialized.
- metadata.pop("status", "")
- metadata.pop("variant", "")
- metadata.pop("type", "")
- metadata.pop("base_file", "")
- metadata.pop("approximate_diameter", "")
- metadata.pop("id", "")
- metadata.pop("container_type", "")
- metadata.pop("name", "")
-
## Begin Name Block
builder.start("name") # type: ignore
@@ -269,7 +262,7 @@ class XmlMaterialProfile(InstanceContainer):
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
buildplate_dict = {} # type: Dict[str, Any]
for variant_name, variant_dict in machine_variant_map[definition_id].items():
- variant_type = variant_dict["variant_node"].metadata["hardware_type"]
+ variant_type = variant_dict["variant_node"].getMetaDataEntry("hardware_type", str(VariantType.NOZZLE))
variant_type = VariantType(variant_type)
if variant_type == VariantType.NOZZLE:
# The hotend identifier is not the containers name, but its "name".
@@ -1165,6 +1158,8 @@ class XmlMaterialProfile(InstanceContainer):
with open(product_to_id_file, encoding = "utf-8") as f:
product_to_id_map = json.load(f)
product_to_id_map = {key: [value] for key, value in product_to_id_map.items()}
+ #This also loads "Ultimaker S5" -> "ultimaker_s5" even though that is not strictly necessary with the default to change spaces into underscores.
+ #However it is not always loaded with that default; this mapping is also used in serialize() without that default.
return product_to_id_map
## Parse the value of the "material compatible" property.
diff --git a/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py b/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py
index 167a9f2849..832a443637 100644
--- a/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py
+++ b/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py
@@ -1,11 +1,10 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import xml.etree.ElementTree as ET
from UM.VersionUpgrade import VersionUpgrade
-from cura.CuraApplication import CuraApplication
from .XmlMaterialProfile import XmlMaterialProfile
diff --git a/plugins/XmlMaterialProfile/__init__.py b/plugins/XmlMaterialProfile/__init__.py
index 70a359ee76..b3810c97dc 100644
--- a/plugins/XmlMaterialProfile/__init__.py
+++ b/plugins/XmlMaterialProfile/__init__.py
@@ -5,10 +5,7 @@ from . import XmlMaterialProfile
from . import XmlMaterialUpgrader
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
-from UM.i18n import i18nCatalog
-
-catalog = i18nCatalog("cura")
upgrader = XmlMaterialUpgrader.XmlMaterialUpgrader()
@@ -19,7 +16,7 @@ def getMetaData():
"mimetype": "application/x-ultimaker-material-profile"
},
"version_upgrade": {
- ("materials", 1000000): ("materials", 1000004, upgrader.upgradeMaterial),
+ ("materials", 1000000): ("materials", 1000007, upgrader.upgradeMaterial),
},
"sources": {
"materials": {
diff --git a/plugins/XmlMaterialProfile/plugin.json b/plugins/XmlMaterialProfile/plugin.json
index 17056dcb3e..bb1db82fa4 100644
--- a/plugins/XmlMaterialProfile/plugin.json
+++ b/plugins/XmlMaterialProfile/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Material Profiles",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides capabilities to read and write XML-based material profiles.",
- "api": 4,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/XmlMaterialProfile/product_to_id.json b/plugins/XmlMaterialProfile/product_to_id.json
index 3e7ce9311f..6b78d3fe64 100644
--- a/plugins/XmlMaterialProfile/product_to_id.json
+++ b/plugins/XmlMaterialProfile/product_to_id.json
@@ -6,6 +6,7 @@
"Ultimaker 2+": "ultimaker2_plus",
"Ultimaker 3": "ultimaker3",
"Ultimaker 3 Extended": "ultimaker3_extended",
+ "Ultimaker S5": "ultimaker_s5",
"Ultimaker Original": "ultimaker_original",
"Ultimaker Original+": "ultimaker_original_plus",
"Ultimaker Original Dual Extrusion": "ultimaker_original_dual",
diff --git a/resources/bundled_packages.json b/resources/bundled_packages/cura.json
similarity index 71%
rename from resources/bundled_packages.json
rename to resources/bundled_packages/cura.json
index d216415921..21da1d9fdb 100644
--- a/resources/bundled_packages.json
+++ b/resources/bundled_packages/cura.json
@@ -5,11 +5,11 @@
"package_type": "plugin",
"display_name": "3MF Reader",
"description": "Provides support for reading 3MF files.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -22,11 +22,11 @@
"package_type": "plugin",
"display_name": "3MF Writer",
"description": "Provides support for writing 3MF files.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -39,11 +39,28 @@
"package_type": "plugin",
"display_name": "Change Log",
"description": "Shows changes since latest checked version.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "plugins@ultimaker.com",
+ "website": "https://ultimaker.com"
+ }
+ }
+ },
+ "CuraDrive": {
+ "package_info": {
+ "package_id": "CuraDrive",
+ "package_type": "plugin",
+ "display_name": "Cura Backups",
+ "description": "Backup and restore your configuration.",
+ "package_version": "1.2.0",
+ "sdk_version": 6,
+ "website": "https://ultimaker.com",
+ "author": {
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -56,11 +73,11 @@
"package_type": "plugin",
"display_name": "CuraEngine Backend",
"description": "Provides the link to the CuraEngine slicing backend.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -73,11 +90,11 @@
"package_type": "plugin",
"display_name": "Cura Profile Reader",
"description": "Provides support for importing Cura profiles.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -90,11 +107,11 @@
"package_type": "plugin",
"display_name": "Cura Profile Writer",
"description": "Provides support for exporting Cura profiles.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -107,11 +124,28 @@
"package_type": "plugin",
"display_name": "Firmware Update Checker",
"description": "Checks for firmware updates.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "plugins@ultimaker.com",
+ "website": "https://ultimaker.com"
+ }
+ }
+ },
+ "FirmwareUpdater": {
+ "package_info": {
+ "package_id": "FirmwareUpdater",
+ "package_type": "plugin",
+ "display_name": "Firmware Updater",
+ "description": "Provides a machine actions for updating firmware.",
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
+ "website": "https://ultimaker.com",
+ "author": {
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -124,11 +158,11 @@
"package_type": "plugin",
"display_name": "Compressed G-code Reader",
"description": "Reads g-code from a compressed archive.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -141,11 +175,11 @@
"package_type": "plugin",
"display_name": "Compressed G-code Writer",
"description": "Writes g-code to a compressed archive.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -158,11 +192,11 @@
"package_type": "plugin",
"display_name": "G-Code Profile Reader",
"description": "Provides support for importing profiles from g-code files.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -175,8 +209,8 @@
"package_type": "plugin",
"display_name": "G-Code Reader",
"description": "Allows loading and displaying G-code files.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "VictorLarchenko",
@@ -192,11 +226,11 @@
"package_type": "plugin",
"display_name": "G-Code Writer",
"description": "Writes g-code to a file.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -209,11 +243,11 @@
"package_type": "plugin",
"display_name": "Image Reader",
"description": "Enables ability to generate printable geometry from 2D image files.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -226,11 +260,11 @@
"package_type": "plugin",
"display_name": "Legacy Cura Profile Reader",
"description": "Provides support for importing profiles from legacy Cura versions.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -243,8 +277,8 @@
"package_type": "plugin",
"display_name": "Machine Settings Action",
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "fieldOfView",
@@ -260,11 +294,11 @@
"package_type": "plugin",
"display_name": "Model Checker",
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
- "package_version": "0.1.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -277,11 +311,11 @@
"package_type": "plugin",
"display_name": "Monitor Stage",
"description": "Provides a monitor stage in Cura.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -294,11 +328,11 @@
"package_type": "plugin",
"display_name": "Per-Object Settings Tool",
"description": "Provides the per-model settings.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -311,11 +345,11 @@
"package_type": "plugin",
"display_name": "Post Processing",
"description": "Extension that allows for user created scripts for post processing.",
- "package_version": "2.2.0",
- "sdk_version": 4,
+ "package_version": "2.2.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -328,11 +362,28 @@
"package_type": "plugin",
"display_name": "Prepare Stage",
"description": "Provides a prepare stage in Cura.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "plugins@ultimaker.com",
+ "website": "https://ultimaker.com"
+ }
+ }
+ },
+ "PreviewStage": {
+ "package_info": {
+ "package_id": "PreviewStage",
+ "package_type": "plugin",
+ "display_name": "Preview Stage",
+ "description": "Provides a preview stage in Cura.",
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
+ "website": "https://ultimaker.com",
+ "author": {
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -345,11 +396,11 @@
"package_type": "plugin",
"display_name": "Removable Drive Output Device",
"description": "Provides removable drive hotplugging and writing support.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -362,11 +413,11 @@
"package_type": "plugin",
"display_name": "Simulation View",
"description": "Provides the Simulation view.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -379,11 +430,11 @@
"package_type": "plugin",
"display_name": "Slice Info",
"description": "Submits anonymous slice info. Can be disabled through preferences.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -396,11 +447,11 @@
"package_type": "plugin",
"display_name": "Solid View",
"description": "Provides a normal solid mesh view.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -413,11 +464,11 @@
"package_type": "plugin",
"display_name": "Support Eraser Tool",
"description": "Creates an eraser mesh to block the printing of support in certain places.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -430,11 +481,11 @@
"package_type": "plugin",
"display_name": "Toolbox",
"description": "Find, manage and install new Cura packages.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -447,11 +498,11 @@
"package_type": "plugin",
"display_name": "UFP Writer",
"description": "Provides support for writing Ultimaker Format Packages.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -464,11 +515,11 @@
"package_type": "plugin",
"display_name": "Ultimaker Machine Actions",
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -481,11 +532,11 @@
"package_type": "plugin",
"display_name": "UM3 Network Printing",
"description": "Manages network connections to Ultimaker 3 printers.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -498,11 +549,11 @@
"package_type": "plugin",
"display_name": "USB Printing",
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.2",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -515,11 +566,11 @@
"package_type": "plugin",
"display_name": "User Agreement",
"description": "Ask the user once if he/she agrees with our license.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -532,11 +583,11 @@
"package_type": "plugin",
"display_name": "Version Upgrade 2.1 to 2.2",
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -549,11 +600,11 @@
"package_type": "plugin",
"display_name": "Version Upgrade 2.2 to 2.4",
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -566,11 +617,11 @@
"package_type": "plugin",
"display_name": "Version Upgrade 2.5 to 2.6",
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -583,11 +634,11 @@
"package_type": "plugin",
"display_name": "Version Upgrade 2.6 to 2.7",
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -600,11 +651,11 @@
"package_type": "plugin",
"display_name": "Version Upgrade 2.7 to 3.0",
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -617,11 +668,11 @@
"package_type": "plugin",
"display_name": "Version Upgrade 3.0 to 3.1",
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -634,11 +685,11 @@
"package_type": "plugin",
"display_name": "Version Upgrade 3.2 to 3.3",
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -651,28 +702,62 @@
"package_type": "plugin",
"display_name": "Version Upgrade 3.3 to 3.4",
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
- "VersionUpgrade34to40": {
+ "VersionUpgrade34to35": {
"package_info": {
- "package_id": "VersionUpgrade34to40",
+ "package_id": "VersionUpgrade34to35",
"package_type": "plugin",
- "display_name": "Version Upgrade 3.4 to 4.0",
- "description": "Upgrades configurations from Cura 3.4 to Cura 4.0.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "display_name": "Version Upgrade 3.4 to 3.5",
+ "description": "Upgrades configurations from Cura 3.4 to Cura 3.5.",
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "plugins@ultimaker.com",
+ "website": "https://ultimaker.com"
+ }
+ }
+ },
+ "VersionUpgrade35to40": {
+ "package_info": {
+ "package_id": "VersionUpgrade35to40",
+ "package_type": "plugin",
+ "display_name": "Version Upgrade 3.5 to 4.0",
+ "description": "Upgrades configurations from Cura 3.5 to Cura 4.0.",
+ "package_version": "1.0.0",
+ "sdk_version": "6.0",
+ "website": "https://ultimaker.com",
+ "author": {
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "plugins@ultimaker.com",
+ "website": "https://ultimaker.com"
+ }
+ }
+ },
+ "VersionUpgrade40to41": {
+ "package_info": {
+ "package_id": "VersionUpgrade40to41",
+ "package_type": "plugin",
+ "display_name": "Version Upgrade 4.0 to 4.1",
+ "description": "Upgrades configurations from Cura 4.0 to Cura 4.1.",
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
+ "website": "https://ultimaker.com",
+ "author": {
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -685,8 +770,8 @@
"package_type": "plugin",
"display_name": "X3D Reader",
"description": "Provides support for reading X3D files.",
- "package_version": "0.5.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "SevaAlekseyev",
@@ -702,11 +787,11 @@
"package_type": "plugin",
"display_name": "XML Material Profiles",
"description": "Provides capabilities to read and write XML-based material profiles.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -719,11 +804,11 @@
"package_type": "plugin",
"display_name": "X-Ray View",
"description": "Provides the X-Ray view.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
@@ -736,8 +821,8 @@
"package_type": "material",
"display_name": "Generic ABS",
"description": "The generic ABS profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -754,8 +839,44 @@
"package_type": "material",
"display_name": "Generic BAM",
"description": "The generic BAM profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
+ "website": "https://github.com/Ultimaker/fdm_materials",
+ "author": {
+ "author_id": "Generic",
+ "display_name": "Generic",
+ "email": "materials@ultimaker.com",
+ "website": "https://github.com/Ultimaker/fdm_materials",
+ "description": "Professional 3D printing made accessible."
+ }
+ }
+ },
+ "GenericCFFCPE": {
+ "package_info": {
+ "package_id": "GenericCFFCPE",
+ "package_type": "material",
+ "display_name": "Generic CFF CPE",
+ "description": "The generic CFF CPE profile which other profiles can be based upon.",
+ "package_version": "1.1.1",
+ "sdk_version": "6.0",
+ "website": "https://github.com/Ultimaker/fdm_materials",
+ "author": {
+ "author_id": "Generic",
+ "display_name": "Generic",
+ "email": "materials@ultimaker.com",
+ "website": "https://github.com/Ultimaker/fdm_materials",
+ "description": "Professional 3D printing made accessible."
+ }
+ }
+ },
+ "GenericCFFPA": {
+ "package_info": {
+ "package_id": "GenericCFFPA",
+ "package_type": "material",
+ "display_name": "Generic CFF PA",
+ "description": "The generic CFF PA profile which other profiles can be based upon.",
+ "package_version": "1.1.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -772,8 +893,8 @@
"package_type": "material",
"display_name": "Generic CPE",
"description": "The generic CPE profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -790,8 +911,44 @@
"package_type": "material",
"display_name": "Generic CPE+",
"description": "The generic CPE+ profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
+ "website": "https://github.com/Ultimaker/fdm_materials",
+ "author": {
+ "author_id": "Generic",
+ "display_name": "Generic",
+ "email": "materials@ultimaker.com",
+ "website": "https://github.com/Ultimaker/fdm_materials",
+ "description": "Professional 3D printing made accessible."
+ }
+ }
+ },
+ "GenericGFFCPE": {
+ "package_info": {
+ "package_id": "GenericGFFCPE",
+ "package_type": "material",
+ "display_name": "Generic GFF CPE",
+ "description": "The generic GFF CPE profile which other profiles can be based upon.",
+ "package_version": "1.1.1",
+ "sdk_version": "6.0",
+ "website": "https://github.com/Ultimaker/fdm_materials",
+ "author": {
+ "author_id": "Generic",
+ "display_name": "Generic",
+ "email": "materials@ultimaker.com",
+ "website": "https://github.com/Ultimaker/fdm_materials",
+ "description": "Professional 3D printing made accessible."
+ }
+ }
+ },
+ "GenericGFFPA": {
+ "package_info": {
+ "package_id": "GenericGFFPA",
+ "package_type": "material",
+ "display_name": "Generic GFF PA",
+ "description": "The generic GFF PA profile which other profiles can be based upon.",
+ "package_version": "1.1.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -808,8 +965,8 @@
"package_type": "material",
"display_name": "Generic HIPS",
"description": "The generic HIPS profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -826,8 +983,8 @@
"package_type": "material",
"display_name": "Generic Nylon",
"description": "The generic Nylon profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -844,8 +1001,8 @@
"package_type": "material",
"display_name": "Generic PC",
"description": "The generic PC profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -862,8 +1019,8 @@
"package_type": "material",
"display_name": "Generic PETG",
"description": "The generic PETG profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -880,8 +1037,8 @@
"package_type": "material",
"display_name": "Generic PLA",
"description": "The generic PLA profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -898,8 +1055,8 @@
"package_type": "material",
"display_name": "Generic PP",
"description": "The generic PP profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -916,8 +1073,8 @@
"package_type": "material",
"display_name": "Generic PVA",
"description": "The generic PVA profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -934,8 +1091,8 @@
"package_type": "material",
"display_name": "Generic Tough PLA",
"description": "The generic Tough PLA profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.0.2",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -952,8 +1109,8 @@
"package_type": "material",
"display_name": "Generic TPU",
"description": "The generic TPU profile which other profiles can be based upon.",
- "package_version": "1.0.0",
- "sdk_version": 6,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@@ -970,8 +1127,8 @@
"package_type": "material",
"display_name": "Dagoma Chromatik PLA",
"description": "Filament testé et approuvé pour les imprimantes 3D Dagoma. Chromatik est l'idéal pour débuter et suivre les tutoriels premiers pas. Il vous offre qualité et résistance pour chacune de vos impressions.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://dagoma.fr/boutique/filaments.html",
"author": {
"author_id": "Dagoma",
@@ -987,8 +1144,8 @@
"package_type": "material",
"display_name": "FABtotum ABS",
"description": "This material is easy to be extruded but it is not the simplest to use. It is one of the most used in 3D printing to get very well finished objects. It is not sustainable and its smoke can be dangerous if inhaled. The reason to prefer this filament to PLA is mainly because of its precision and mechanical specs. ABS (for plastic) stands for Acrylonitrile Butadiene Styrene and it is a thermoplastic which is widely used in everyday objects. It can be printed with any FFF 3D printer which can get to high temperatures as it must be extruded in a range between 220° and 245°, so it’s compatible with all versions of the FABtotum Personal fabricator.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=40",
"author": {
"author_id": "FABtotum",
@@ -1004,8 +1161,8 @@
"package_type": "material",
"display_name": "FABtotum Nylon",
"description": "When 3D printing started this material was not listed among the extrudable filaments. It is flexible as well as resistant to tractions. It is well known for its uses in textile but also in industries which require a strong and flexible material. There are different kinds of Nylon: 3D printing mostly uses Nylon 6 and Nylon 6.6, which are the most common. It requires higher temperatures to be printed, so a 3D printer must be able to reach them (around 240°C): the FABtotum, of course, can.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=53",
"author": {
"author_id": "FABtotum",
@@ -1021,8 +1178,8 @@
"package_type": "material",
"display_name": "FABtotum PLA",
"description": "It is the most common filament used for 3D printing. It is studied to be bio-degradable as it comes from corn starch’s sugar mainly. It is completely made of renewable sources and has no footprint on polluting. PLA stands for PolyLactic Acid and it is a thermoplastic that today is still considered the easiest material to be 3D printed. It can be extruded at lower temperatures: the standard range of FABtotum’s one is between 185° and 195°.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=39",
"author": {
"author_id": "FABtotum",
@@ -1038,8 +1195,8 @@
"package_type": "material",
"display_name": "FABtotum TPU Shore 98A",
"description": "",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=66",
"author": {
"author_id": "FABtotum",
@@ -1055,12 +1212,12 @@
"package_type": "material",
"display_name": "Fiberlogy HD PLA",
"description": "With our HD PLA you have many more options. You can use this material in two ways. Choose the one you like best. You can use it as a normal PLA and get prints characterized by a very good adhesion between the layers and high precision. You can also make your prints acquire similar properties to that of ABS – better impact resistance and high temperature resistance. All you need is an oven. Yes, an oven! By annealing our HD PLA in an oven, in accordance with the manual, you will avoid all the inconveniences of printing with ABS, such as unpleasant odour or hazardous fumes.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "http://fiberlogy.com/en/fiberlogy-filaments/filament-hd-pla/",
"author": {
"author_id": "Fiberlogy",
- "diplay_name": "Fiberlogy S.A.",
+ "display_name": "Fiberlogy S.A.",
"email": "grzegorz.h@fiberlogy.com",
"website": "http://fiberlogy.com"
}
@@ -1072,8 +1229,8 @@
"package_type": "material",
"display_name": "Filo3D PLA",
"description": "Fast, safe and reliable printing. PLA is ideal for the fast and reliable printing of parts and prototypes with a great surface quality.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://dagoma.fr",
"author": {
"author_id": "Dagoma",
@@ -1089,8 +1246,8 @@
"package_type": "material",
"display_name": "IMADE3D JellyBOX PETG",
"description": "",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "http://shop.imade3d.com/filament.html",
"author": {
"author_id": "IMADE3D",
@@ -1106,8 +1263,8 @@
"package_type": "material",
"display_name": "IMADE3D JellyBOX PLA",
"description": "",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "http://shop.imade3d.com/filament.html",
"author": {
"author_id": "IMADE3D",
@@ -1123,8 +1280,8 @@
"package_type": "material",
"display_name": "Octofiber PLA",
"description": "PLA material from Octofiber.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://nl.octofiber.com/3d-printing-filament/pla.html",
"author": {
"author_id": "Octofiber",
@@ -1140,8 +1297,8 @@
"package_type": "material",
"display_name": "PolyFlex™ PLA",
"description": "PolyFlex™ is a highly flexible yet easy to print 3D printing material. Featuring good elasticity and a large strain-to- failure, PolyFlex™ opens up a completely new realm of applications.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "http://www.polymaker.com/shop/polyflex/",
"author": {
"author_id": "Polymaker",
@@ -1157,8 +1314,8 @@
"package_type": "material",
"display_name": "PolyMax™ PLA",
"description": "PolyMax™ PLA is a 3D printing material with excellent mechanical properties and printing quality. PolyMax™ PLA has an impact resistance of up to nine times that of regular PLA, and better overall mechanical properties than ABS.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "http://www.polymaker.com/shop/polymax/",
"author": {
"author_id": "Polymaker",
@@ -1174,8 +1331,8 @@
"package_type": "material",
"display_name": "PolyPlus™ PLA True Colour",
"description": "PolyPlus™ PLA is a premium PLA designed for all desktop FDM/FFF 3D printers. It is produced with our patented Jam-Free™ technology that ensures consistent extrusion and prevents jams.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "http://www.polymaker.com/shop/polyplus-true-colour/",
"author": {
"author_id": "Polymaker",
@@ -1191,8 +1348,8 @@
"package_type": "material",
"display_name": "PolyWood™ PLA",
"description": "PolyWood™ is a wood mimic printing material that contains no actual wood ensuring a clean Jam-Free™ printing experience.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "http://www.polymaker.com/shop/polywood/",
"author": {
"author_id": "Polymaker",
@@ -1208,11 +1365,30 @@
"package_type": "material",
"display_name": "Ultimaker ABS",
"description": "Example package for material and quality profiles for Ultimaker materials.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "materials@ultimaker.com",
+ "website": "https://ultimaker.com",
+ "description": "Professional 3D printing made accessible.",
+ "support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
+ }
+ }
+ },
+ "UltimakerBAM": {
+ "package_info": {
+ "package_id": "UltimakerBAM",
+ "package_type": "material",
+ "display_name": "Ultimaker Breakaway",
+ "description": "Example package for material and quality profiles for Ultimaker materials.",
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
+ "website": "https://ultimaker.com/products/materials/breakaway",
+ "author": {
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
@@ -1227,11 +1403,30 @@
"package_type": "material",
"display_name": "Ultimaker CPE",
"description": "Example package for material and quality profiles for Ultimaker materials.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "materials@ultimaker.com",
+ "website": "https://ultimaker.com",
+ "description": "Professional 3D printing made accessible.",
+ "support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
+ }
+ }
+ },
+ "UltimakerCPEP": {
+ "package_info": {
+ "package_id": "UltimakerCPEP",
+ "package_type": "material",
+ "display_name": "Ultimaker CPE+",
+ "description": "Example package for material and quality profiles for Ultimaker materials.",
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
+ "website": "https://ultimaker.com/products/materials/cpe",
+ "author": {
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
@@ -1246,11 +1441,11 @@
"package_type": "material",
"display_name": "Ultimaker Nylon",
"description": "Example package for material and quality profiles for Ultimaker materials.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
@@ -1265,11 +1460,11 @@
"package_type": "material",
"display_name": "Ultimaker PC",
"description": "Example package for material and quality profiles for Ultimaker materials.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.2.2",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com/products/materials/pc",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
@@ -1284,11 +1479,30 @@
"package_type": "material",
"display_name": "Ultimaker PLA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "materials@ultimaker.com",
+ "website": "https://ultimaker.com",
+ "description": "Professional 3D printing made accessible.",
+ "support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
+ }
+ }
+ },
+ "UltimakerPP": {
+ "package_info": {
+ "package_id": "UltimakerPP",
+ "package_type": "material",
+ "display_name": "Ultimaker PP",
+ "description": "Example package for material and quality profiles for Ultimaker materials.",
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
+ "website": "https://ultimaker.com/products/materials/pp",
+ "author": {
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
@@ -1303,11 +1517,49 @@
"package_type": "material",
"display_name": "Ultimaker PVA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
- "author_id": "Ultimaker",
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "materials@ultimaker.com",
+ "website": "https://ultimaker.com",
+ "description": "Professional 3D printing made accessible.",
+ "support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
+ }
+ }
+ },
+ "UltimakerTPU": {
+ "package_info": {
+ "package_id": "UltimakerTPU",
+ "package_type": "material",
+ "display_name": "Ultimaker TPU 95A",
+ "description": "Example package for material and quality profiles for Ultimaker materials.",
+ "package_version": "1.2.1",
+ "sdk_version": "6.0",
+ "website": "https://ultimaker.com/products/materials/tpu-95a",
+ "author": {
+ "author_id": "UltimakerPackages",
+ "display_name": "Ultimaker B.V.",
+ "email": "materials@ultimaker.com",
+ "website": "https://ultimaker.com",
+ "description": "Professional 3D printing made accessible.",
+ "support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
+ }
+ }
+ },
+ "UltimakerTPLA": {
+ "package_info": {
+ "package_id": "UltimakerTPLA",
+ "package_type": "material",
+ "display_name": "Ultimaker Tough PLA",
+ "description": "Example package for material and quality profiles for Ultimaker materials.",
+ "package_version": "1.0.3",
+ "sdk_version": "6.0",
+ "website": "https://ultimaker.com/products/materials/tough-pla",
+ "author": {
+ "author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
@@ -1322,8 +1574,8 @@
"package_type": "material",
"display_name": "Vertex Delta ABS",
"description": "ABS material and quality files for the Delta Vertex K8800.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@@ -1339,8 +1591,8 @@
"package_type": "material",
"display_name": "Vertex Delta PET",
"description": "ABS material and quality files for the Delta Vertex K8800.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@@ -1356,8 +1608,8 @@
"package_type": "material",
"display_name": "Vertex Delta PLA",
"description": "ABS material and quality files for the Delta Vertex K8800.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@@ -1373,8 +1625,8 @@
"package_type": "material",
"display_name": "Vertex Delta TPU",
"description": "ABS material and quality files for the Delta Vertex K8800.",
- "package_version": "1.0.0",
- "sdk_version": 4,
+ "package_version": "1.0.1",
+ "sdk_version": "6.0",
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@@ -1384,4 +1636,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/resources/definitions/3dator.def.json b/resources/definitions/3dator.def.json
index 91f261906b..e91c46920b 100644
--- a/resources/definitions/3dator.def.json
+++ b/resources/definitions/3dator.def.json
@@ -7,7 +7,6 @@
"author": "3Dator GmbH",
"manufacturer": "3Dator GmbH",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"supports_usb_connection": true,
"platform": "3dator_platform.stl",
"machine_extruder_trains":
diff --git a/resources/definitions/alfawise_u20.def.json b/resources/definitions/alfawise_u20.def.json
new file mode 100644
index 0000000000..8a6badeca6
--- /dev/null
+++ b/resources/definitions/alfawise_u20.def.json
@@ -0,0 +1,93 @@
+{
+ "name": "Alfawise U20",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Samuel Pinches",
+ "manufacturer": "Alfawise",
+ "file_formats": "text/x-gcode",
+ "preferred_quality_type": "fast",
+ "machine_extruder_trains":
+ {
+ "0": "alfawise_u20_extruder_0"
+ }
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "Alfawise U20"
+ },
+ "machine_start_gcode": {
+ "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 Y60.0 Z0 E9.0 F1000.0;intro line\nG1 Y100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --"
+ },
+ "machine_end_gcode": {
+ "default_value": "; -- END GCODE --\nM104 S0 ;turn off nozzle heater\nM140 S0 ;turn off bed heater\nG91 ;set to relative positioning\nG1 E-10 F300 ;retract the filament slightly\nG90 ;set to absolute positioning\nG28 X0 ;move to the X-axis origin (Home)\nG0 Y280 F600 ;bring the bed to the front for easy print removal\nM84 ;turn off stepper motors\n; -- end of END GCODE --"
+ },
+ "machine_width": {
+ "default_value": 300
+ },
+ "machine_height": {
+ "default_value": 400
+ },
+ "machine_depth": {
+ "default_value": 300
+ },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "machine_center_is_zero": {
+ "default_value": false
+ },
+ "gantry_height": {
+ "default_value": 10
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "material_print_temperature": {
+ "default_value": 210
+ },
+ "material_bed_temperature": {
+ "default_value": 50
+ },
+ "layer_height_0": {
+ "default_value": 0.2
+ },
+ "wall_thickness": {
+ "default_value": 1.2
+ },
+ "speed_print": {
+ "default_value": 40
+ },
+ "speed_infill": {
+ "default_value": 40
+ },
+ "speed_wall": {
+ "default_value": 35
+ },
+ "speed_topbottom": {
+ "default_value": 35
+ },
+ "speed_travel": {
+ "default_value": 120
+ },
+ "speed_layer_0": {
+ "default_value": 20
+ },
+ "support_enable": {
+ "default_value": true
+ },
+ "retraction_enable": {
+ "default_value": true
+ },
+ "retraction_amount": {
+ "default_value": 5
+ },
+ "retraction_speed": {
+ "default_value": 45
+ }
+ }
+}
diff --git a/resources/definitions/alfawise_u30.def.json b/resources/definitions/alfawise_u30.def.json
new file mode 100644
index 0000000000..65f6adcfe0
--- /dev/null
+++ b/resources/definitions/alfawise_u30.def.json
@@ -0,0 +1,93 @@
+{
+ "name": "Alfawise U30",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Nicolas Nussbaum",
+ "manufacturer": "Alfawise",
+ "file_formats": "text/x-gcode",
+ "preferred_quality_type": "fast",
+ "machine_extruder_trains":
+ {
+ "0": "alfawise_u30_extruder_0"
+ }
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "Alfawise U30"
+ },
+ "machine_start_gcode": {
+ "default_value": "; -- START GCODE --\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z1 F1000 ;move up slightly\nG1 Y60.0 Z0 E9.0 F1000.0;intro line\nG1 Y100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\nG1 F80\n;Put printing message on LCD screen\nM117 Printing...\n; -- end of START GCODE --"
+ },
+ "machine_end_gcode": {
+ "default_value": "; -- END GCODE --\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F80 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM107 ;turn the fan off; -- end of END GCODE --"
+ },
+ "machine_width": {
+ "default_value": 220
+ },
+ "machine_height": {
+ "default_value": 250
+ },
+ "machine_depth": {
+ "default_value": 220
+ },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "machine_center_is_zero": {
+ "default_value": false
+ },
+ "gantry_height": {
+ "default_value": 10
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "material_print_temperature": {
+ "default_value": 210
+ },
+ "material_bed_temperature": {
+ "default_value": 50
+ },
+ "layer_height_0": {
+ "default_value": 0.2
+ },
+ "wall_thickness": {
+ "default_value": 1.2
+ },
+ "speed_print": {
+ "default_value": 40
+ },
+ "speed_infill": {
+ "default_value": 40
+ },
+ "speed_wall": {
+ "default_value": 35
+ },
+ "speed_topbottom": {
+ "default_value": 35
+ },
+ "speed_travel": {
+ "default_value": 120
+ },
+ "speed_layer_0": {
+ "default_value": 20
+ },
+ "support_enable": {
+ "default_value": true
+ },
+ "retraction_enable": {
+ "default_value": true
+ },
+ "retraction_amount": {
+ "default_value": 5
+ },
+ "retraction_speed": {
+ "default_value": 45
+ }
+ }
+}
diff --git a/resources/definitions/anycubic_4max.def.json b/resources/definitions/anycubic_4max.def.json
new file mode 100644
index 0000000000..c14ce1ac31
--- /dev/null
+++ b/resources/definitions/anycubic_4max.def.json
@@ -0,0 +1,88 @@
+{
+ "version": 2,
+ "name": "Anycubic 4Max",
+ "inherits": "fdmprinter",
+ "metadata":
+ {
+ "visible": true,
+ "author": "Jason Scurtu",
+ "manufacturer": "Anycubic",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "icon": "icon_ultimaker2",
+ "platform": "anycubic_4max_platform.stl",
+ "has_materials": true,
+ "quality_definition": "anycubic_4max",
+ "has_machine_quality": true,
+ "preferred_quality_type": "normal",
+ "machine_extruder_trains":
+ {
+ "0": "anycubic_4max_extruder_0"
+ }
+ },
+
+ "overrides":
+ {
+ "machine_name": { "default_value": "Anycubic 4Max" },
+ "machine_heated_bed": { "default_value": true },
+ "machine_width": { "default_value": 220 },
+ "machine_height": {"default_value": 300 },
+ "machine_depth": { "default_value": 220 },
+ "machine_center_is_zero": { "default_value": false },
+ "machine_max_feedrate_x": { "default_value": 300 },
+ "machine_max_feedrate_y": { "default_value": 300 },
+ "machine_max_feedrate_z": { "default_value": 10 },
+ "machine_acceleration": { "default_value": 1500 },
+ "machine_max_acceleration_x": { "default_value": 1500 },
+ "machine_max_acceleration_y": { "default_value": 1500 },
+ "machine_max_acceleration_z": { "default_value": 100 },
+ "machine_max_jerk_xy": { "default_value": 11.0 },
+ "machine_max_jerk_z": { "default_value": 0.4 },
+ "machine_max_jerk_e": { "default_value": 11.0 },
+
+ "jerk_enabled": { "value": "True" },
+ "jerk_layer_0": { "value": "jerk_topbottom" },
+ "jerk_prime_tower": { "value": "math.ceil(jerk_print * 15 / 25)" },
+ "jerk_print": { "value": "11" },
+ "jerk_support": { "value": "math.ceil(jerk_print * 15 / 25)" },
+ "jerk_support_interface": { "value": "jerk_topbottom" },
+ "jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
+ "jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
+ "jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
+
+ "gantry_height": { "default_value": 25.0 },
+ "skin_overlap": { "value": "10" },
+
+ "acceleration_enabled": { "value": "True" },
+ "acceleration_layer_0": { "value": "acceleration_topbottom" },
+ "acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
+ "acceleration_print": { "value": "900" },
+ "acceleration_support": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
+ "acceleration_support_interface": { "value": "acceleration_topbottom" },
+ "acceleration_topbottom": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
+ "acceleration_travel": { "value": "acceleration_print" },
+ "acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
+ "acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
+
+ "speed_layer_0": { "value": "20" },
+ "speed_print": { "value": "40" },
+ "speed_support": { "value": "speed_wall_0" },
+ "speed_support_interface": { "value": "speed_topbottom" },
+ "speed_topbottom": { "value": "math.ceil(speed_print * 20 / 35)" },
+ "speed_travel": { "value": "60" },
+ "speed_wall": { "value": "math.ceil(speed_print * 30 / 35)" },
+ "speed_wall_0": { "value": "math.ceil(speed_wall * 20 / 30)" },
+ "speed_wall_x": { "value": "speed_wall" },
+
+ "infill_pattern": {"value": "'zigzag'" },
+ "infill_before_walls": {"value": false },
+
+ "adhesion_type": { "default_value": "skirt" },
+ "material_bed_temperature": { "maximum_value": "150" },
+ "material_bed_temperature_layer_0": { "maximum_value": "150" },
+
+ "machine_gcode_flavor":{"default_value": "RepRap (Marlin/Sprinter)"},
+ "machine_start_gcode":{"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F{speed_travel} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM117 Printing...\nG5"},
+ "machine_end_gcode":{"default_value": "M104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nM84 ; disable motors\nM107\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle\nto release some of the pressure\nG1 Z+0.5 E-5 ;X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 X0 ;Y0 ;move X/Y to min endstops\nso the head is out of the way\nG1 Y180 F2000\nM84 ;steppers off\nG90\nM300 P300 S4000"}
+ }
+}
diff --git a/resources/definitions/anycubic_i3_mega.def.json b/resources/definitions/anycubic_i3_mega.def.json
index a6c1567dc4..8a96d98023 100644
--- a/resources/definitions/anycubic_i3_mega.def.json
+++ b/resources/definitions/anycubic_i3_mega.def.json
@@ -8,9 +8,8 @@
"author": "TheTobby",
"manufacturer": "Anycubic",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "anycubic_i3_mega_platform.stl",
- "has_materials": false,
+ "has_materials": true,
"has_machine_quality": true,
"preferred_quality_type": "normal",
"machine_extruder_trains":
diff --git a/resources/definitions/bibo2_dual.def.json b/resources/definitions/bibo2_dual.def.json
new file mode 100644
index 0000000000..1ae16a49b1
--- /dev/null
+++ b/resources/definitions/bibo2_dual.def.json
@@ -0,0 +1,92 @@
+{
+ "id": "BIBO2 dual",
+ "version": 2,
+ "name": "BIBO2 dual",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "na",
+ "manufacturer": "BIBO",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "has_materials": true,
+ "machine_extruder_trains": {
+ "0": "bibo2_dual_extruder_0",
+ "1": "bibo2_dual_extruder_1"
+ },
+ "first_start_actions": [
+ "MachineSettingsAction"
+ ]
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "BIBO2 dual"
+ },
+ "machine_width": {
+ "default_value": 214
+ },
+ "machine_height": {
+ "default_value": 160
+ },
+ "machine_depth": {
+ "default_value": 186
+ },
+ "machine_center_is_zero": {
+ "default_value": true
+ },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "machine_nozzle_heat_up_speed": {
+ "default_value": 2
+ },
+ "machine_nozzle_cool_down_speed": {
+ "default_value": 2
+ },
+ "machine_head_with_fans_polygon": {
+ "default_value": [
+ [
+ -68.18,
+ 64.63
+ ],
+ [
+ -68.18,
+ -47.38
+ ],
+ [
+ 35.18,
+ 64.63
+ ],
+ [
+ 35.18,
+ -47.38
+ ]
+ ]
+ },
+ "gantry_height": {
+ "default_value": 12
+ },
+ "machine_use_extruder_offset_to_offset_coords": {
+ "default_value": true
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "machine_start_gcode": {
+ "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z2.0 F400 ;move the platform down 15mm\nT0\nG92 E0\nG28\nG1 Y0 F1200 E0\nG92 E0\nM117 BIBO Printing..."
+ },
+ "machine_end_gcode": {
+ "default_value": ";End GCode\nM104 T0 S0 ;extruder heater off\nM104 T1 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91\nG1 Z1 F100 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-2 X-20 Y-20 F300 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
+ },
+ "machine_extruder_count": {
+ "default_value": 2
+ },
+ "prime_tower_position_x": {
+ "default_value": 50
+ },
+ "prime_tower_position_y": {
+ "default_value": 50
+ }
+ }
+}
+
diff --git a/resources/definitions/bq_hephestos.def.json b/resources/definitions/bq_hephestos.def.json
index 8dc67a8cad..be024cd6fa 100644
--- a/resources/definitions/bq_hephestos.def.json
+++ b/resources/definitions/bq_hephestos.def.json
@@ -12,7 +12,8 @@
"machine_extruder_trains":
{
"0": "bq_hephestos_extruder_0"
- }
+ },
+ "firmware_file": "MarlinHephestos2.hex"
},
"overrides": {
diff --git a/resources/definitions/bq_hephestos_2.def.json b/resources/definitions/bq_hephestos_2.def.json
index ca0e66ada2..90a86433fb 100644
--- a/resources/definitions/bq_hephestos_2.def.json
+++ b/resources/definitions/bq_hephestos_2.def.json
@@ -17,8 +17,8 @@
"overrides": {
"machine_name": { "default_value": "BQ Hephestos 2" },
- "machine_start_gcode": { "default_value": "; -- START GCODE --\nM104 S{material_print_temperature} ; Heat up extruder while leveling\nM800 ; Custom GCODE to fire start print procedure\nM109 S{material_print_temperature} ; Makes sure the temperature is correct before printing\n; -- end of START GCODE --" },
- "machine_end_gcode": { "default_value": "; -- END GCODE --\nM801 ; Custom GCODE to fire end print procedure\n; -- end of END GCODE --" },
+ "machine_start_gcode": { "default_value": "; -- START GCODE --\nM104 S{material_print_temperature}\nG28 ; Zero-ing position\nG29 ; Auto bed-leveling\nG0 X4 Y297 Z15 F4000 ; Fast move to BQ's start position\nG90 ; Set to Absolute Positioning\nG92 E0 ; Reset extruder 0\nG1 F1800 ; Set default feedrate\nM109 S{material_print_temperature} ; Makes sure the temperature is correct before printing\n; -- end of START GCODE --" },
+ "machine_end_gcode": { "default_value": "; -- END GCODE --\nM801 ; Marlin G-CODE to fire end print procedure\n; -- end of END GCODE --" },
"machine_width": { "default_value": 210 },
"machine_depth": { "default_value": 297 },
"machine_height": { "default_value": 220 },
diff --git a/resources/definitions/bq_witbox.def.json b/resources/definitions/bq_witbox.def.json
index 0ae1c5e339..b96da6179c 100644
--- a/resources/definitions/bq_witbox.def.json
+++ b/resources/definitions/bq_witbox.def.json
@@ -12,7 +12,8 @@
"machine_extruder_trains":
{
"0": "bq_witbox_extruder_0"
- }
+ },
+ "firmware_file": "MarlinWitbox.hex"
},
"overrides": {
diff --git a/resources/definitions/cartesio.def.json b/resources/definitions/cartesio.def.json
index 57c16241a0..9c7a95cceb 100644
--- a/resources/definitions/cartesio.def.json
+++ b/resources/definitions/cartesio.def.json
@@ -11,7 +11,6 @@
"has_machine_quality": true,
"has_materials": true,
"has_machine_materials": true,
- "has_variant_materials": true,
"has_variants": true,
"variants_name": "Tool",
diff --git a/resources/definitions/cocoon_create_modelmaker.def.json b/resources/definitions/cocoon_create_modelmaker.def.json
new file mode 100644
index 0000000000..22aa75d09e
--- /dev/null
+++ b/resources/definitions/cocoon_create_modelmaker.def.json
@@ -0,0 +1,96 @@
+{
+ "name": "Cocoon Create ModelMaker",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Samuel Pinches",
+ "manufacturer": "Cocoon Create",
+ "file_formats": "text/x-gcode",
+ "preferred_quality_type": "fine",
+ "machine_extruder_trains":
+ {
+ "0": "cocoon_create_modelmaker_extruder_0"
+ }
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "Cocoon Create ModelMaker"
+ },
+ "machine_start_gcode": {
+ "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 X60.0 Z0 E9.0 F1000.0;intro line\nG1 X100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --"
+ },
+ "machine_end_gcode": {
+ "default_value": "; -- END GCODE --\nM104 S0 ;turn off nozzle heater\nG91 ;set to relative positioning\nG1 E-10 F300 ;retract the filament slightly\nG90 ;set to absolute positioning\nG28 X0 Y0 ;move to the XY-axis origin (Home)\nM84 ;turn off stepper motors\n; -- end of END GCODE --"
+ },
+ "machine_width": {
+ "default_value": 120
+ },
+ "machine_height": {
+ "default_value": 100
+ },
+ "machine_depth": {
+ "default_value": 135
+ },
+ "machine_heated_bed": {
+ "default_value": false
+ },
+ "machine_center_is_zero": {
+ "default_value": false
+ },
+ "gantry_height": {
+ "default_value": 10
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "material_print_temperature": {
+ "default_value": 220
+ },
+ "layer_height": {
+ "default_value": 0.10
+ },
+ "layer_height_0": {
+ "default_value": 0.2
+ },
+ "wall_thickness": {
+ "default_value": 1.2
+ },
+ "top_bottom_thickness": {
+ "default_value": 0.6
+ },
+ "speed_print": {
+ "default_value": 40
+ },
+ "speed_infill": {
+ "default_value": 40
+ },
+ "speed_wall": {
+ "default_value": 35
+ },
+ "speed_topbottom": {
+ "default_value": 35
+ },
+ "speed_travel": {
+ "default_value": 70
+ },
+ "speed_layer_0": {
+ "default_value": 20
+ },
+ "support_enable": {
+ "default_value": true
+ },
+ "retraction_enable": {
+ "default_value": true
+ },
+ "retraction_amount": {
+ "default_value": 7
+ },
+ "retraction_speed": {
+ "default_value": 40
+ }
+ }
+}
diff --git a/resources/definitions/creality_cr-x.def.json b/resources/definitions/creality_cr-x.def.json
new file mode 100644
index 0000000000..94ac20cbb5
--- /dev/null
+++ b/resources/definitions/creality_cr-x.def.json
@@ -0,0 +1,51 @@
+{
+ "id": "CR-X",
+ "version": 2,
+ "name": "Creality CR-X",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "SRC",
+ "manufacturer": "Creality3D",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "platform": "cr-x.stl",
+ "has_variants": false,
+ "has_machine_quality": false,
+ "preferred_quality_type": "draft",
+ "machine_extruder_trains": {
+ "0": "cr-x_extruder_0",
+ "1": "cr-x_extruder_1"
+ }
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Creality CR-X" },
+ "machine_extruder_count": { "default_value": 2 },
+ "machine_heated_bed": { "default_value": true },
+ "machine_width": { "default_value": 300 },
+ "machine_depth": { "default_value": 300 },
+ "machine_height": { "default_value": 400 },
+ "machine_center_is_zero": { "default_value": false },
+ "retraction_amount": { "default_value": 3 },
+ "retraction_speed": { "default_value": 70},
+ "adhesion_type": { "default_value": "skirt" },
+ "gantry_height": { "default_value": 30 },
+ "speed_print": { "default_value": 60 },
+ "speed_travel": { "default_value": 120 },
+ "machine_max_acceleration_x": { "default_value": 500 },
+ "machine_max_acceleration_y": { "default_value": 500 },
+ "machine_max_acceleration_z": { "default_value": 100 },
+ "machine_max_acceleration_e": { "default_value": 5000 },
+ "machine_max_jerk_xy": { "default_value": 5.0 },
+ "machine_max_jerk_z": { "default_value": 0.4 },
+ "machine_max_jerk_e": { "default_value": 5.0 },
+ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
+ "machine_start_gcode": { "default_value": "G21 ;metric values\nG28 ;home all\nG90 ;absolute positioning\nM107 ;start with the fan off\nG1 F2400 Z15.0 ;raise the nozzle 15mm\nM109 S{material_print_temperature} ;Set Extruder Temperature and Wait\nM190 S{material_bed_temperature}; Wait for bed temperature to reach target temp\nT0 ;Switch to Extruder 1\nG1 F3000 X5 Y10 Z0.2 ;move to prime start position\nG92 E0 ;reset extrusion distance\nG1 F600 X160 E15 ;prime nozzle in a line\nG1 F5000 X180 ;quick wipe\nG92 E0 ;reset extrusion distance" },
+ "machine_end_gcode": { "default_value": "M104 S0 ;hotend off\nM140 S0 ;bed off\nG92 E0\nG1 F2000 E-100 ;retract filament 100mm\nG92 E0\nG1 F3000 X0 Y270 ;move bed for easy part removal\nM84 ;disable steppers" },
+ "material_print_temperature": { "default_value": 200 },
+ "wall_thickness": { "default_value": 1 },
+ "top_bottom_thickness": { "default_value": 1 },
+ "bottom_thickness": { "default_value": 1 }
+ }
+}
diff --git a/resources/definitions/creality_cr10.def.json b/resources/definitions/creality_cr10.def.json
index b727834db3..fb63867163 100644
--- a/resources/definitions/creality_cr10.def.json
+++ b/resources/definitions/creality_cr10.def.json
@@ -37,7 +37,7 @@
"top_bottom_thickness": {
"default_value": 0.6
},
- "top_bottom_pattern": {
+ "top_bottom_pattern_0": {
"default_value": "concentric"
},
"infill_pattern": {
diff --git a/resources/definitions/creality_ender3.def.json b/resources/definitions/creality_ender3.def.json
new file mode 100755
index 0000000000..08d8e92b72
--- /dev/null
+++ b/resources/definitions/creality_ender3.def.json
@@ -0,0 +1,96 @@
+{
+ "name": "Creality Ender-3",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Sacha Telgenhof",
+ "manufacturer": "Creality3D",
+ "file_formats": "text/x-gcode",
+ "platform": "creality_ender3_platform.stl",
+ "preferred_quality_type": "draft",
+ "machine_extruder_trains":
+ {
+ "0": "creality_ender3_extruder_0"
+ }
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "Creality Ender-3"
+ },
+ "machine_width": {
+ "default_value": 235
+ },
+ "machine_height": {
+ "default_value": 250
+ },
+ "machine_depth": {
+ "default_value": 235
+ },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "gantry_height": {
+ "default_value": 30
+ },
+ "machine_head_polygon": {
+ "default_value": [
+ [-30, 34],
+ [-30, -32],
+ [30, -32],
+ [30, 34]
+ ]
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "acceleration_enabled": {
+ "default_value": true
+ },
+ "acceleration_print": {
+ "default_value": 500
+ },
+ "acceleration_travel": {
+ "default_value": 500
+ },
+ "jerk_enabled": {
+ "default_value": true
+ },
+ "jerk_travel": {
+ "default_value": 20
+ },
+ "layer_height": {
+ "default_value": 0.10
+ },
+ "layer_height_0": {
+ "default_value": 0.2
+ },
+ "adhesion_type": {
+ "default_value": "skirt"
+ },
+ "top_bottom_thickness": {
+ "default_value": 0.6
+ },
+ "retraction_amount": {
+ "default_value": 5
+ },
+ "retraction_speed": {
+ "default_value": 40
+ },
+ "cool_min_layer_time": {
+ "default_value": 10
+ },
+ "skirt_line_count": {
+ "default_value": 4
+ },
+ "skirt_gap": {
+ "default_value": 5
+ },
+ "machine_start_gcode": {
+ "default_value": "; Ender 3 Custom Start G-code\nG28 ; Home all axes\nG92 E0 ; Reset Extruder\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X0.1 Y20 Z0.3 F5000.0 ; Move to start position\nG1 X0.1 Y200.0 Z0.3 F1500.0 E15 ; Draw the first line\nG1 X0.4 Y200.0 Z0.3 F5000.0 ; Move to side a little\nG1 X0.4 Y20 Z0.3 F1500.0 E30 ; Draw the second line\nG92 E0 ; Reset Extruder\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\n; End of custom start GCode"
+ },
+ "machine_end_gcode": {
+ "default_value": "; Ender 3 Custom End G-code\nG4 ; Wait\nM220 S100 ; Reset Speed factor override percentage to default (100%)\nM221 S100 ; Reset Extrude factor override percentage to default (100%)\nG91 ; Set coordinates to relative\nG1 F1800 E-3 ; Retract filament 3 mm to prevent oozing\nG1 F3000 Z20 ; Move Z Axis up 20 mm to allow filament ooze freely\nG90 ; Set coordinates to absolute\nG1 X0 Y{machine_depth} F1000 ; Move Heat Bed to the front for easy print removal\nM84 ; Disable stepper motors\n; End of custom end GCode"
+ }
+ }
+}
diff --git a/resources/definitions/creatable_d3.def.json b/resources/definitions/creatable_d3.def.json
new file mode 100644
index 0000000000..3fb1205ead
--- /dev/null
+++ b/resources/definitions/creatable_d3.def.json
@@ -0,0 +1,45 @@
+{
+ "version": 2,
+ "name": "Creatable D3",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Creatable Labs",
+ "manufacturer": "Ateam Ventures Co. Ltd.",
+ "file_formats": "text/x-gcode",
+ "icon": "icon_ultimaker.png",
+ "platform": "rostock_platform.stl",
+ "machine_extruder_trains":
+ {
+ "0": "creatable_d3_extruder_0"
+ }
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Creatable D3" },
+ "machine_center_is_zero": { "default_value": true },
+ "machine_gcode_flavor": { "default_value": "RepRap (RepRap)" },
+ "machine_width": { "default_value": 250 },
+ "machine_height": { "default_value": 200 },
+ "machine_depth": { "default_value": 250 },
+ "machine_heated_bed": { "default_value": true },
+ "machine_shape": { "default_value": "elliptic" },
+ "machine_max_feedrate_z": { "default_value": 300 },
+ "gantry_height": {"default_value": 43},
+ "layer_height": { "default_value": 0.1 },
+ "relative_extrusion": { "default_value": false },
+ "retraction_combing": { "default_value": "off" },
+ "retraction_hop_enabled": { "default_value": true },
+ "retraction_hop_only_when_collides": { "default_value": false },
+ "retraction_retract_speed": { "default_value": 100 },
+ "retraction_speed": { "default_value": 100 },
+ "retraction_amount": { "default_value": 4.5 },
+ "retraction_prime_speed": { "default_value": 45 },
+ "machine_start_gcode": {
+ "default_value": "G21\nG90\nM82\nM106 S255\nG28\nG92 E0\nG1 Z100 F5000\nM190 S50\nM109 S200\nG1 X-135\nG1 Z0.3\nG92 E-32\nG1 E0 F1000\nG1 E50 F200\nG1 F1000\nG1 X-125\nG92 E0"
+ },
+ "machine_end_gcode": {
+ "default_value": "M400\nG28\nM104 S0\nM140 S0\nM107\nG92 E0\nG1 E-32 F300\nM84\nG90"
+ }
+ }
+}
diff --git a/resources/definitions/cubicon_3dp_110f.def.json b/resources/definitions/cubicon_3dp_110f.def.json
new file mode 100644
index 0000000000..168b57cd66
--- /dev/null
+++ b/resources/definitions/cubicon_3dp_110f.def.json
@@ -0,0 +1,41 @@
+{
+ "id": "3DP-110F",
+ "version": 2,
+ "name": "Cubicon Single",
+ "inherits": "cubicon_common",
+ "metadata": {
+ "author": "Cubicon R&D Center",
+ "manufacturer": "Cubicon",
+ "visible": true,
+ "file_formats": "text/x-gcode",
+ "supports_usb_connection": false,
+ "machine_extruder_trains": {
+ "0": "cubicon_3dp_110f_extruder_0"
+ },
+ "platform_offset": [
+ 0,
+ -32.05,
+ -20
+ ]
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "Cubicon Single"
+ },
+ "machine_start_gcode": {
+ "default_value": "M911 3DP-110F\nM201 X400 Y400\nM202 X400 Y400\nG28 ; Home\nG1 Z15.0 F6000 ;move the platform down 15mm\n;Prime the extruder\nG92 E0\nG1 F200 E3\nG92 E0"
+ },
+ "machine_width": {
+ "default_value": 240
+ },
+ "machine_depth": {
+ "default_value": 190
+ },
+ "machine_height": {
+ "default_value": 200
+ },
+ "material_bed_temp_wait": {
+ "default_value": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/definitions/cubicon_3dp_210f.def.json b/resources/definitions/cubicon_3dp_210f.def.json
new file mode 100644
index 0000000000..cc99899f92
--- /dev/null
+++ b/resources/definitions/cubicon_3dp_210f.def.json
@@ -0,0 +1,41 @@
+{
+ "id": "3DP-210F",
+ "version": 2,
+ "name": "Cubicon Style",
+ "inherits": "cubicon_common",
+ "metadata": {
+ "author": "Cubicon R&D Center",
+ "manufacturer": "Cubicon",
+ "visible": true,
+ "file_formats": "text/x-gcode",
+ "supports_usb_connection": false,
+ "machine_extruder_trains": {
+ "0": "cubicon_3dp_210f_extruder_0"
+ },
+ "platform_offset": [
+ 0,
+ -18.8,
+ 0
+ ]
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "Cubicon Style"
+ },
+ "machine_start_gcode": {
+ "default_value": "M911 3DP-210F\nM201 X400 Y400\nM202 X400 Y400\nG28 ; Home\nG1 Z15.0 F6000 ;move the platform down 15mm\n;Prime the extruder\nG92 E0\nG1 F200 E3\nG92 E0"
+ },
+ "machine_width": {
+ "default_value": 150
+ },
+ "machine_depth": {
+ "default_value": 150
+ },
+ "machine_height": {
+ "default_value": 150
+ },
+ "material_bed_temp_wait":{
+ "default_value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/definitions/cubicon_3dp_310f.def.json b/resources/definitions/cubicon_3dp_310f.def.json
new file mode 100644
index 0000000000..90d0e3f25c
--- /dev/null
+++ b/resources/definitions/cubicon_3dp_310f.def.json
@@ -0,0 +1,41 @@
+{
+ "id": "3DP-310F",
+ "version": 2,
+ "name": "Cubicon Single Plus",
+ "inherits": "cubicon_common",
+ "metadata": {
+ "author": "Cubicon R&D Center",
+ "manufacturer": "Cubicon",
+ "visible": true,
+ "file_formats": "text/x-gcode",
+ "supports_usb_connection": false,
+ "machine_extruder_trains": {
+ "0": "cubicon_3dp_310f_extruder_0"
+ },
+ "platform_offset": [
+ 0,
+ -32.05,
+ -20
+ ]
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "Cubicon Single Plus"
+ },
+ "machine_start_gcode": {
+ "default_value": "M911 3DP-310F\nM201 X400 Y400\nM202 X400 Y400\nG28 ; Home\nG1 Z15.0 F6000 ;move the platform down 15mm\n;Prime the extruder\nG92 E0\nG1 F200 E3\nG92 E0"
+ },
+ "machine_width": {
+ "default_value": 240
+ },
+ "machine_depth": {
+ "default_value": 190
+ },
+ "machine_height": {
+ "default_value": 200
+ },
+ "material_bed_temp_wait": {
+ "default_value": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/definitions/cubicon_common.def.json b/resources/definitions/cubicon_common.def.json
new file mode 100644
index 0000000000..ae085c7552
--- /dev/null
+++ b/resources/definitions/cubicon_common.def.json
@@ -0,0 +1,99 @@
+{
+ "version": 2,
+ "name": "Cubicon Common",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "author": "Cubicon R&D Center",
+ "manufacturer": "Cubicon",
+ "visible": false
+ },
+ "overrides": {
+ "machine_start_gcode": {
+ "default_value": "M201 X400 Y400\nM202 X400 Y400\nG28 ; Home\nG1 Z15.0 F6000 ;move the platform down 15mm\n;Prime the extruder\nG92 E0\nG1 F200 E3\nG92 E0"
+ },
+ "machine_end_gcode": {
+ "default_value": "M104 S0\nM140 S0\nM904\nM117 Print completed! \nM84"
+ },
+ "machine_gcode_flavor": {
+ "default_value": "Repetier"
+ },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "travel_compensate_overlapping_walls_enabled": {
+ "default_value": false
+ },
+ "travel_compensate_overlapping_walls_0_enabled": {
+ "default_value": false
+ },
+ "travel_compensate_overlapping_walls_x_enabled": {
+ "default_value": false
+ },
+ "layer_height": {
+ "default_value": 0.2
+ },
+ "layer_height_0": {
+ "default_value": 0.2
+ },
+ "infill_line_width": {
+ "default_value": 0.6
+ },
+ "adhesion_type": {
+ "default_value": "raft"
+ },
+ "roofing_pattern": { "default_value": "lines" },
+ "top_bottom_pattern": { "default_value": "lines" },
+ "top_bottom_pattern_0": {
+ "default_value": "zigzag"
+ },
+ "fill_perimeter_gaps": { "default_value": "everywhere" },
+ "infill_pattern": {
+ "default_value": "zigzag"
+ },
+ "infill_sparse_density": { "default_value": 20 },
+ "infill_overlap": {
+ "default_value": 15
+ },
+ "infill_before_walls": { "default_value": false },
+ "infill_sparse_thickness": { "default_value": 0.2 },
+ "top_bottom_thickness": {
+ "default_value": 1.0
+ },
+ "top_thickness": {
+ "default_value": 1.0
+ },
+ "bottom_thickness": {
+ "default_value": 0.6,
+ "value": "top_bottom_thickness * 0.6"
+ },
+ "roofing_layer_count": {
+ "default_value": 1
+ },
+ "skin_preshrink": { "default_value": true },
+ "material_flow_layer_0": { "default_value": 100 },
+ "top_skin_preshrink": { "default_value": 1.2 },
+ "bottom_skin_preshrink": { "default_value": 1.2 },
+ "max_skin_angle_for_expansion": { "default_value": 90 },
+ "min_skin_width_for_expansion": { "default_value": 2.7475 },
+ "skin_angles": { "default_value": "[135,45]" },
+ "roofing_angles": { "default_value": "[135,45]" },
+ "coasting_volume": { "default_value": 0.032 },
+ "wall_thickness": { "default_value": 1.2 },
+ "wall_line_count": { "default_value": 3 },
+ "speed_wall_0": { "default_value": 25 },
+ "skin_overlap": { "default_value": 5 },
+ "cool_min_layer_time_fan_speed_max": { "default_value": 15 },
+ "cool_min_layer_time": { "default_value": 15 },
+ "support_roof_pattern": { "default_value": "zigzag" },
+ "support_bottom_pattern": { "default_value": "zigzag" },
+ "support_interface_pattern": { "default_value": "zigzag" },
+ "support_pattern": { "default_value": "zigzag" },
+ "retraction_amount": { "default_value": 1.5 },
+ "top_layers": {
+ "default_value": 5
+ },
+ "bottom_layers": {
+ "default_value": 3
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/definitions/dagoma_neva_magis.def.json b/resources/definitions/dagoma_magis.def.json
similarity index 92%
rename from resources/definitions/dagoma_neva_magis.def.json
rename to resources/definitions/dagoma_magis.def.json
index 0b7b50cb5f..75e6e449cd 100644
--- a/resources/definitions/dagoma_neva_magis.def.json
+++ b/resources/definitions/dagoma_magis.def.json
@@ -1,5 +1,5 @@
{
- "name": "Dagoma NEVA Magis",
+ "name": "Dagoma Magis",
"version": 2,
"inherits": "fdmprinter",
"metadata": {
@@ -13,7 +13,7 @@
"has_materials": true,
"machine_extruder_trains":
{
- "0": "dagoma_neva_magis_extruder_0"
+ "0": "dagoma_magis_extruder_0"
}
},
"overrides": {
@@ -43,9 +43,6 @@
"machine_shape": {
"default_value": "elliptic"
},
- "machine_gcode_flavor": {
- "default_value": "RepRap"
- },
"machine_start_gcode": {
"default_value": ";Gcode by Cura\nG90\nG28\nM107\nM109 R100\nG29\nM109 S{material_print_temperature_layer_0} U-55 X55 V-85 Y-85 W0.26 Z0.26\nM82\nG92 E0\nG1 F200 E6\nG92 E0\nG1 F200 E-3.5\nG0 Z0.15\nG0 X10\nG0 Z3\nG1 F6000\n"
},
diff --git a/resources/definitions/dagoma_neva.def.json b/resources/definitions/dagoma_neva.def.json
index cdd5725765..67c8795678 100644
--- a/resources/definitions/dagoma_neva.def.json
+++ b/resources/definitions/dagoma_neva.def.json
@@ -43,9 +43,6 @@
"machine_shape": {
"default_value": "elliptic"
},
- "machine_gcode_flavor": {
- "default_value": "RepRap"
- },
"machine_start_gcode": {
"default_value": ";Gcode by Cura\nG90\nG28\nM107\nM109 R100\nG29\nM109 S{material_print_temperature_layer_0} U-55 X55 V-85 Y-85 W0.26 Z0.26\nM82\nG92 E0\nG1 F200 E6\nG92 E0\nG1 F200 E-3.5\nG0 Z0.15\nG0 X10\nG0 Z3\nG1 F6000\n"
},
diff --git a/resources/definitions/delta_go.def.json b/resources/definitions/delta_go.def.json
index 968bf281a5..cd1fb180c2 100644
--- a/resources/definitions/delta_go.def.json
+++ b/resources/definitions/delta_go.def.json
@@ -8,7 +8,6 @@
"manufacturer": "Deltaprintr",
"file_formats": "text/x-gcode",
"platform_offset": [0, 0, 0],
- "platform": "",
"machine_extruder_trains":
{
"0": "delta_go_extruder_0"
diff --git a/resources/definitions/deltacomb.def.json b/resources/definitions/deltacomb.def.json
old mode 100644
new mode 100755
index a4b2d47a7b..8fec0f8950
--- a/resources/definitions/deltacomb.def.json
+++ b/resources/definitions/deltacomb.def.json
@@ -8,7 +8,6 @@
"manufacturer": "Deltacomb 3D",
"category": "Other",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "deltacomb.stl",
"has_machine_quality": true,
"machine_extruder_trains":
@@ -33,6 +32,7 @@
"material_final_print_temperature": { "value": "material_print_temperature - 5" },
"material_initial_print_temperature": { "value": "material_print_temperature" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
+ "material_diameter": { "default_value": 1.75 },
"travel_avoid_distance": { "default_value": 1, "value": "1" },
"speed_print" : { "default_value": 70 },
"speed_travel": { "value": "150.0" },
@@ -55,6 +55,7 @@
"support_use_towers" : { "default_value": false },
"jerk_wall_0" : { "value": "30" },
"jerk_travel" : { "default_value": 20 },
- "acceleration_travel" : { "value": 10000 }
+ "acceleration_travel" : { "value": 10000 },
+ "machine_max_feedrate_z" : { "default_value": 150 }
}
}
diff --git a/resources/definitions/fabtotum.def.json b/resources/definitions/fabtotum.def.json
index 1908e42913..10c8f68844 100644
--- a/resources/definitions/fabtotum.def.json
+++ b/resources/definitions/fabtotum.def.json
@@ -9,7 +9,6 @@
"category": "Other",
"file_formats": "text/x-gcode",
"platform": "fabtotum_platform.stl",
- "icon": "fabtotum_platform.png",
"has_machine_quality": true,
"has_variants": true,
"variants_name": "Head",
diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json
index 3f84ed69a4..0af1e68075 100644
--- a/resources/definitions/fdmextruder.def.json
+++ b/resources/definitions/fdmextruder.def.json
@@ -78,7 +78,7 @@
"machine_extruder_start_code":
{
"label": "Extruder Start G-Code",
- "description": "Start g-code to execute whenever turning the extruder on.",
+ "description": "Start g-code to execute when switching to this extruder.",
"type": "str",
"default_value": "",
"settable_per_mesh": false,
@@ -124,7 +124,7 @@
"machine_extruder_end_code":
{
"label": "Extruder End G-Code",
- "description": "End g-code to execute whenever turning the extruder off.",
+ "description": "End g-code to execute when switching away from this extruder.",
"type": "str",
"default_value": "",
"settable_per_mesh": false,
@@ -178,7 +178,19 @@
"maximum_value": "machine_height",
"settable_per_mesh": false,
"settable_per_extruder": true
- }
+ },
+ "machine_extruder_cooling_fan_number":
+ {
+ "label": "Extruder Print Cooling Fan",
+ "description": "The number of the print cooling fan associated with this extruder. Only change this from the default value of 0 when you have a different print cooling fan for each extruder.",
+ "type": "int",
+ "default_value": 0,
+ "minimum_value": "0",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true,
+ "settable_per_meshgroup": false,
+ "settable_globally": false
+ }
}
},
"platform_adhesion":
diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json
index 3eb7cb1c32..f11c2a3588 100644
--- a/resources/definitions/fdmprinter.def.json
+++ b/resources/definitions/fdmprinter.def.json
@@ -77,6 +77,20 @@
"type": "str",
"enabled": false
},
+ "material_diameter":
+ {
+ "label": "Diameter",
+ "description": "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 2.85,
+ "minimum_value": "0.0001",
+ "minimum_value_warning": "0.4",
+ "maximum_value_warning": "3.5",
+ "enabled": "machine_gcode_flavor != \"UltiGCode\"",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
"material_bed_temp_wait":
{
"label": "Wait for Build Plate Heatup",
@@ -216,6 +230,7 @@
"label": "Number of Extruders that are enabled",
"description": "Number of extruder trains that are enabled; automatically set in software",
"value": "machine_extruder_count",
+ "default_value": 1,
"minimum_value": "1",
"maximum_value": "16",
"type": "int",
@@ -831,6 +846,7 @@
"default_value": 0.4,
"type": "float",
"value": "line_width",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
@@ -860,7 +876,7 @@
"default_value": 0.4,
"type": "float",
"value": "line_width",
- "enabled": "resolveOrValue('adhesion_type') == 'skirt' or resolveOrValue('adhesion_type') == 'brim'",
+ "enabled": "resolveOrValue('adhesion_type') == 'skirt' or resolveOrValue('adhesion_type') == 'brim' or resolveOrValue('prime_tower_brim_enable')",
"settable_per_mesh": false,
"settable_per_extruder": true
},
@@ -1177,6 +1193,7 @@
"zigzag": "Zig Zag"
},
"default_value": "lines",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
@@ -1192,6 +1209,7 @@
"zigzag": "Zig Zag"
},
"default_value": "lines",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"value": "top_bottom_pattern",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
@@ -1199,11 +1217,11 @@
"connect_skin_polygons":
{
"label": "Connect Top/Bottom Polygons",
- "description": "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happend midway over infill this feature can reduce the top surface quality.",
+ "description": "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happen midway over infill this feature can reduce the top surface quality.",
"type": "bool",
"default_value": false,
- "enabled": "top_bottom_pattern == 'concentric'",
- "limit_to_extruder": "infill_extruder_nr",
+ "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern == 'concentric'",
+ "limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
"skin_angles":
@@ -1212,7 +1230,7 @@
"description": "A list of integer line directions to use when the top/bottom layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees).",
"type": "[int]",
"default_value": "[ ]",
- "enabled": "top_bottom_pattern != 'concentric'",
+ "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern != 'concentric'",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
@@ -1297,8 +1315,8 @@
"default_value": 0,
"type": "float",
"enabled": "travel_compensate_overlapping_walls_0_enabled or travel_compensate_overlapping_walls_x_enabled",
- "settable_per_mesh": false,
- "settable_per_extruder": true
+ "settable_per_mesh": true,
+ "settable_per_extruder": false
},
"wall_min_flow_retract":
{
@@ -1307,8 +1325,8 @@
"type": "bool",
"default_value": false,
"enabled": "(travel_compensate_overlapping_walls_0_enabled or travel_compensate_overlapping_walls_x_enabled) and wall_min_flow > 0",
- "settable_per_mesh": false,
- "settable_per_extruder": true
+ "settable_per_mesh": true,
+ "settable_per_extruder": false
},
"fill_perimeter_gaps":
{
@@ -1438,7 +1456,8 @@
"label": "Ignore Small Z Gaps",
"description": "When the model has small vertical gaps, about 5% extra computation time can be spent on generating top and bottom skin in these narrow spaces. In such case, disable the setting.",
"type": "bool",
- "default_value": true,
+ "default_value": false,
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
@@ -1450,6 +1469,7 @@
"minimum_value": "0",
"maximum_value_warning": "10",
"type": "int",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
@@ -1624,7 +1644,7 @@
"infill_pattern":
{
"label": "Infill Pattern",
- "description": "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction.",
+ "description": "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Gyroid, cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction.",
"type": "enum",
"options":
{
@@ -1639,7 +1659,8 @@
"concentric": "Concentric",
"zigzag": "Zig Zag",
"cross": "Cross",
- "cross_3d": "Cross 3D"
+ "cross_3d": "Cross 3D",
+ "gyroid": "Gyroid"
},
"default_value": "grid",
"enabled": "infill_sparse_density > 0",
@@ -1654,7 +1675,7 @@
"type": "bool",
"default_value": false,
"value": "infill_pattern == 'cross' or infill_pattern == 'cross_3d'",
- "enabled": "infill_pattern == 'lines' or infill_pattern == 'grid' or infill_pattern == 'triangles' or infill_pattern == 'trihexagon' or infill_pattern == 'cubic' or infill_pattern == 'tetrahedral' or infill_pattern == 'quarter_cubic' or infill_pattern == 'cross' or infill_pattern == 'cross_3d'",
+ "enabled": "infill_pattern == 'lines' or infill_pattern == 'grid' or infill_pattern == 'triangles' or infill_pattern == 'trihexagon' or infill_pattern == 'cubic' or infill_pattern == 'tetrahedral' or infill_pattern == 'quarter_cubic' or infill_pattern == 'cross' or infill_pattern == 'cross_3d' or infill_pattern == 'gyroid'",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
@@ -1664,8 +1685,8 @@
"description": "Connect infill paths where they run next to each other. For infill patterns which consist of several closed polygons, enabling this setting greatly reduces the travel time.",
"type": "bool",
"default_value": true,
- "value": "infill_pattern == 'cross' or infill_pattern == 'cross_3d' or infill_multiplier % 2 == 0",
- "enabled": "infill_pattern == 'cross' or infill_pattern == 'cross_3d' or infill_multiplier % 2 == 0",
+ "value": "(infill_pattern == 'cross' or infill_pattern == 'cross_3d' or infill_multiplier % 2 == 0) and infill_wall_line_count > 0",
+ "enabled": "infill_pattern == 'cross' or infill_pattern == 'cross_3d' or infill_pattern == 'concentric' or infill_multiplier % 2 == 0 or infill_wall_line_count > 1",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
@@ -1716,7 +1737,7 @@
"infill_wall_line_count":
{
"label": "Extra Infill Wall Count",
- "description": "Add extra wals around the infill area. Such walls can make top/bottom skin lines sag down less which means you need less top/bottom skin layers for the same quality at the cost of some extra material.\nThis feature can combine with the Connect Infill Polygons to connect all the infill into a single extrusion path without the need for travels or retractions if configured right.",
+ "description": "Add extra walls around the infill area. Such walls can make top/bottom skin lines sag down less which means you need less top/bottom skin layers for the same quality at the cost of some extra material.\nThis feature can combine with the Connect Infill Polygons to connect all the infill into a single extrusion path without the need for travels or retractions if configured right.",
"default_value": 0,
"type": "int",
"minimum_value": "0",
@@ -1771,14 +1792,14 @@
"skin_overlap":
{
"label": "Skin Overlap Percentage",
- "description": "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall.",
+ "description": "Adjust the amount of overlap between the walls and (the endpoints of) the skin-centerlines, as a percentage of the line widths of the skin lines and the innermost wall. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any percentage over 50% may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.",
"unit": "%",
"type": "float",
"default_value": 5,
"minimum_value_warning": "-50",
"maximum_value_warning": "100",
"value": "5 if top_bottom_pattern != 'concentric' else 0",
- "enabled": "top_bottom_pattern != 'concentric'",
+ "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern != 'concentric'",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true,
"children":
@@ -1786,14 +1807,14 @@
"skin_overlap_mm":
{
"label": "Skin Overlap",
- "description": "The amount of overlap between the skin and the walls. A slight overlap allows the walls to connect firmly to the skin.",
+ "description": "Adjust the amount of overlap between the walls and (the endpoints of) the skin-centerlines. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any value over half the width of the wall may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.",
"unit": "mm",
"type": "float",
"default_value": 0.02,
"minimum_value_warning": "-0.5 * machine_nozzle_size",
"maximum_value_warning": "machine_nozzle_size",
"value": "0.5 * (skin_line_width + (wall_line_width_x if wall_line_count > 1 else wall_line_width_0)) * skin_overlap / 100 if top_bottom_pattern != 'concentric' else 0",
- "enabled": "top_bottom_pattern != 'concentric'",
+ "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern != 'concentric'",
"settable_per_mesh": true
}
}
@@ -1819,9 +1840,9 @@
"unit": "mm",
"type": "float",
"default_value": 0.1,
- "minimum_value": "resolveOrValue('layer_height')",
+ "minimum_value": "resolveOrValue('layer_height') if infill_line_distance > 0 else -999999",
"maximum_value_warning": "0.75 * machine_nozzle_size",
- "maximum_value": "resolveOrValue('layer_height') * (1.45 if spaghetti_infill_enabled else 8)",
+ "maximum_value": "resolveOrValue('layer_height') * (1.45 if spaghetti_infill_enabled else 8) if infill_line_distance > 0 else 999999",
"value": "resolveOrValue('layer_height')",
"enabled": "infill_sparse_density > 0 and not spaghetti_infill_enabled",
"limit_to_extruder": "infill_extruder_nr",
@@ -1906,6 +1927,7 @@
"default_value": 0,
"value": "wall_line_width_0 + (wall_line_count - 1) * wall_line_width_x",
"minimum_value": "0",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true,
"children":
@@ -1919,6 +1941,7 @@
"default_value": 0,
"value": "skin_preshrink",
"minimum_value": "0",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
@@ -1931,6 +1954,7 @@
"default_value": 0,
"value": "skin_preshrink",
"minimum_value": "0",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
}
@@ -1946,6 +1970,7 @@
"value": "wall_line_width_0 + (wall_line_count - 1) * wall_line_width_x",
"minimum_value": "-skin_preshrink",
"limit_to_extruder": "top_bottom_extruder_nr",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"settable_per_mesh": true,
"children":
{
@@ -1958,6 +1983,7 @@
"default_value": 2.8,
"value": "expand_skins_expand_distance",
"minimum_value": "-top_skin_preshrink",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
@@ -1970,6 +1996,7 @@
"default_value": 2.8,
"value": "expand_skins_expand_distance",
"minimum_value": "-bottom_skin_preshrink",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
}
@@ -1985,7 +2012,7 @@
"minimum_value_warning": "2",
"maximum_value": "90",
"default_value": 90,
- "enabled": "top_skin_expand_distance > 0 or bottom_skin_expand_distance > 0",
+ "enabled": "(top_layers > 0 or bottom_layers > 0) and (top_skin_expand_distance > 0 or bottom_skin_expand_distance > 0)",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true,
"children":
@@ -1999,7 +2026,7 @@
"default_value": 2.24,
"value": "top_layers * layer_height / math.tan(math.radians(max_skin_angle_for_expansion))",
"minimum_value": "0",
- "enabled": "top_skin_expand_distance > 0 or bottom_skin_expand_distance > 0",
+ "enabled": "(top_layers > 0 or bottom_layers > 0) and (top_skin_expand_distance > 0 or bottom_skin_expand_distance > 0)",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
}
@@ -2379,7 +2406,7 @@
"switch_extruder_retraction_amount":
{
"label": "Nozzle Switch Retraction Distance",
- "description": "The amount of retraction: Set at 0 for no retraction at all. This should generally be the same as the length of the heat zone.",
+ "description": "The amount of retraction when switching extruders. Set to 0 for no retraction at all. This should generally be the same as the length of the heat zone.",
"type": "float",
"unit": "mm",
"enabled": "retraction_enable",
@@ -2549,6 +2576,7 @@
"default_value": 30,
"value": "speed_print / 2",
"limit_to_extruder": "top_bottom_extruder_nr",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"settable_per_mesh": true
},
"speed_support":
@@ -2873,6 +2901,7 @@
"default_value": 3000,
"value": "acceleration_topbottom",
"enabled": "resolveOrValue('acceleration_enabled') and roofing_layer_count > 0 and top_layers > 0",
+ "enabled": "top_layers > 0 or bottom_layers > 0",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true
},
@@ -3173,7 +3202,7 @@
"maximum_value_warning": "50",
"default_value": 20,
"value": "jerk_print",
- "enabled": "resolveOrValue('jerk_enabled')",
+ "enabled": "(top_layers > 0 or bottom_layers > 0) and resolveOrValue('jerk_enabled')",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
@@ -3356,7 +3385,7 @@
"retraction_combing":
{
"label": "Combing Mode",
- "description": "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas and also to only comb within the infill. Note that the 'Within Infill' option behaves exactly like the 'Not in Skin' option in earlier Cura releases.",
+ "description": "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas or to only comb within the infill.",
"type": "enum",
"options":
{
@@ -3366,7 +3395,7 @@
"infill": "Within Infill"
},
"default_value": "all",
- "resolve": "'noskin' if 'noskin' in extruderValues('retraction_combing') else ('all' if 'all' in extruderValues('retraction_combing') else 'off')",
+ "resolve": "'noskin' if 'noskin' in extruderValues('retraction_combing') else ('infill' if 'infill' in extruderValues('retraction_combing') else ('all' if 'all' in extruderValues('retraction_combing') else 'off'))",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -3782,7 +3811,8 @@
"triangles": "Triangles",
"concentric": "Concentric",
"zigzag": "Zig Zag",
- "cross": "Cross"
+ "cross": "Cross",
+ "gyroid": "Gyroid"
},
"default_value": "zigzag",
"enabled": "support_enable or support_tree_enable",
@@ -3802,7 +3832,8 @@
"value": "1 if (support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'concentric') else 0",
"enabled": "support_enable",
"limit_to_extruder": "support_infill_extruder_nr",
- "settable_per_mesh": true
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
},
"zig_zaggify_support":
{
@@ -3810,8 +3841,8 @@
"description": "Connect the ends of the support lines together. Enabling this setting can make your support more sturdy and reduce underextrusion, but it will cost more material.",
"type": "bool",
"default_value": false,
- "value": "support_pattern == 'cross'",
- "enabled": "support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'cross'",
+ "value": "support_pattern == 'cross' or support_pattern == 'gyroid'",
+ "enabled": "support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'cross' or support_pattern == 'gyroid'",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
@@ -3875,6 +3906,61 @@
}
}
},
+ "support_infill_angle":
+ {
+ "label": "Support Infill Line Direction",
+ "description": "Orientation of the infill pattern for supports. The support infill pattern is rotated in the horizontal plane.",
+ "unit": "°",
+ "type": "float",
+ "minimum_value": "-180",
+ "maximum_value": "180",
+ "default_value": 0,
+ "enabled": "support_enable and support_pattern != 'concentric' and support_infill_rate > 0",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "support_brim_enable":
+ {
+ "label": "Enable Support Brim",
+ "description": "Generate a brim within the support infill regions of the first layer. This brim is printed underneath the support, not around it. Enabling this setting increases the adhesion of support to the build plate.",
+ "type": "bool",
+ "default_value": false,
+ "enabled": "support_enable or support_tree_enable",
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "support_brim_width":
+ {
+ "label": "Support Brim Width",
+ "description": "The width of the brim to print underneath the support. A larger brim enhances adhesion to the build plate, at the cost of some extra material.",
+ "type": "float",
+ "unit": "mm",
+ "default_value": 8.0,
+ "minimum_value": "0.0",
+ "maximum_value_warning": "50.0",
+ "enabled": "support_enable",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true,
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "children":
+ {
+ "support_brim_line_count":
+ {
+ "label": "Support Brim Line Count",
+ "description": "The number of lines used for the support brim. More brim lines enhance adhesion to the build plate, at the cost of some extra material.",
+ "type": "int",
+ "default_value": 20,
+ "minimum_value": "0",
+ "maximum_value_warning": "50 / skirt_brim_line_width",
+ "value": "math.ceil(support_brim_width / (skirt_brim_line_width * initial_layer_line_width_factor / 100.0))",
+ "enabled": "support_enable",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true,
+ "limit_to_extruder": "support_infill_extruder_nr"
+ }
+ }
+ },
"support_z_distance":
{
"label": "Support Z Distance",
@@ -4054,6 +4140,18 @@
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false
},
+ "minimum_support_area":
+ {
+ "label": "Minimum Support Area",
+ "description": "Minimum area size for support polygons. Polygons which have an area smaller than this value will not be generated.",
+ "unit": "mm²",
+ "type": "float",
+ "default_value": 0.0,
+ "minimum_value": "0",
+ "enabled": "support_enable",
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "settable_per_mesh": true
+ },
"support_interface_enable":
{
"label": "Enable Support Interface",
@@ -4293,6 +4391,94 @@
}
}
},
+ "minimum_interface_area":
+ {
+ "label": "Minimum Support Interface Area",
+ "description": "Minimum area size for support interface polygons. Polygons which have an area smaller than this value will not be generated.",
+ "unit": "mm²",
+ "type": "float",
+ "default_value": 1.0,
+ "minimum_value": "0",
+ "minimum_value_warning": "minimum_support_area",
+ "limit_to_extruder": "support_interface_extruder_nr",
+ "enabled": "support_interface_enable and support_enable",
+ "settable_per_mesh": true,
+ "children":
+ {
+ "minimum_roof_area":
+ {
+ "label": "Minimum Support Roof Area",
+ "description": "Minimum area size for the roofs of the support. Polygons which have an area smaller than this value will not be generated.",
+ "unit": "mm²",
+ "type": "float",
+ "default_value": 1.0,
+ "value": "extruderValue(support_roof_extruder_nr, 'minimum_interface_area')",
+ "minimum_value": "0",
+ "minimum_value_warning": "minimum_support_area",
+ "limit_to_extruder": "support_roof_extruder_nr",
+ "enabled": "support_roof_enable and support_enable",
+ "settable_per_mesh": true
+ },
+ "minimum_bottom_area":
+ {
+ "label": "Minimum Support Floor Area",
+ "description": "Minimum area size for the floors of the support. Polygons which have an area smaller than this value will not be generated.",
+ "unit": "mm²",
+ "type": "float",
+ "default_value": 1.0,
+ "value": "extruderValue(support_bottom_extruder_nr, 'minimum_interface_area')",
+ "minimum_value": "0",
+ "minimum_value_warning": "minimum_support_area",
+ "limit_to_extruder": "support_bottom_extruder_nr",
+ "enabled": "support_bottom_enable and support_enable",
+ "settable_per_mesh": true
+ }
+ }
+ },
+ "support_interface_offset":
+ {
+ "label": "Support Interface Horizontal Expansion",
+ "description": "Amount of offset applied to the support interface polygons.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.0,
+ "maximum_value": "extruderValue(support_extruder_nr, 'support_offset')",
+ "limit_to_extruder": "support_interface_extruder_nr",
+ "enabled": "support_interface_enable and (support_enable or support_tree_enable)",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true,
+ "children":
+ {
+ "support_roof_offset":
+ {
+ "label": "Support Roof Horizontal Expansion",
+ "description": "Amount of offset applied to the roofs of the support.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.0,
+ "value": "extruderValue(support_roof_extruder_nr, 'support_interface_offset')",
+ "maximum_value": "extruderValue(support_extruder_nr, 'support_offset')",
+ "limit_to_extruder": "support_roof_extruder_nr",
+ "enabled": "support_roof_enable and (support_enable or support_tree_enable)",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "support_bottom_offset":
+ {
+ "label": "Support Floor Horizontal Expansion",
+ "description": "Amount of offset applied to the floors of the support.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.0,
+ "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_offset')",
+ "maximum_value": "extruderValue(support_extruder_nr, 'support_offset')",
+ "limit_to_extruder": "support_bottom_extruder_nr",
+ "enabled": "support_bottom_enable and (support_enable or support_tree_enable)",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ }
+ }
+ },
"support_fan_enable":
{
"label": "Fan Speed Override",
@@ -4449,7 +4635,7 @@
"type": "extruder",
"default_value": "0",
"value": "defaultExtruderPosition()",
- "enabled": "extruders_enabled_count > 1 and resolveOrValue('adhesion_type') != 'none'",
+ "enabled": "extruders_enabled_count > 1 and (resolveOrValue('adhesion_type') != 'none' or resolveOrValue('prime_tower_brim_enable'))",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -4490,7 +4676,7 @@
"minimum_value": "0",
"minimum_value_warning": "25",
"maximum_value_warning": "2500",
- "enabled": "resolveOrValue('adhesion_type') == 'skirt' or resolveOrValue('adhesion_type') == 'brim'",
+ "enabled": "resolveOrValue('adhesion_type') == 'skirt' or resolveOrValue('adhesion_type') == 'brim' or resolveOrValue('prime_tower_brim_enable')",
"settable_per_mesh": false,
"settable_per_extruder": true
},
@@ -4503,7 +4689,8 @@
"default_value": 8.0,
"minimum_value": "0.0",
"maximum_value_warning": "50.0",
- "enabled": "resolveOrValue('adhesion_type') == 'brim'",
+ "maximum_value": "0.5 * min(machine_width, machine_depth)",
+ "enabled": "resolveOrValue('adhesion_type') == 'brim' or resolveOrValue('prime_tower_brim_enable')",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "adhesion_extruder_nr",
@@ -4517,14 +4704,26 @@
"default_value": 20,
"minimum_value": "0",
"maximum_value_warning": "50 / skirt_brim_line_width",
+ "maximum_value": "0.5 * min(machine_width, machine_depth) / skirt_brim_line_width",
"value": "math.ceil(brim_width / (skirt_brim_line_width * initial_layer_line_width_factor / 100.0))",
- "enabled": "resolveOrValue('adhesion_type') == 'brim'",
+ "enabled": "resolveOrValue('adhesion_type') == 'brim' or resolveOrValue('prime_tower_brim_enable')",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "adhesion_extruder_nr"
}
}
},
+ "brim_replaces_support":
+ {
+ "label": "Brim Replaces Support",
+ "description": "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions of the first layer of support by brim regions.",
+ "type": "bool",
+ "default_value": true,
+ "enabled": "resolveOrValue('adhesion_type') == 'brim' and support_enable",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true,
+ "limit_to_extruder": "support_infill_extruder_nr"
+ },
"brim_outside_only":
{
"label": "Brim Only on Outside",
@@ -5074,7 +5273,7 @@
"unit": "mm",
"enabled": "resolveOrValue('prime_tower_enable')",
"default_value": 200,
- "value": "machine_width - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0) - 1",
+ "value": "machine_width - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' or (prime_tower_brim_enable and adhesion_type != 'raft') else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enable') else 0) - 1",
"maximum_value": "machine_width / 2 if machine_center_is_zero else machine_width",
"minimum_value": "resolveOrValue('prime_tower_size') - machine_width / 2 if machine_center_is_zero else resolveOrValue('prime_tower_size')",
"settable_per_mesh": false,
@@ -5088,7 +5287,7 @@
"unit": "mm",
"enabled": "resolveOrValue('prime_tower_enable')",
"default_value": 200,
- "value": "machine_depth - prime_tower_size - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0) - 1",
+ "value": "machine_depth - prime_tower_size - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' or (prime_tower_brim_enable and adhesion_type != 'raft') else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enable') else 0) - 1",
"maximum_value": "machine_depth / 2 - resolveOrValue('prime_tower_size') if machine_center_is_zero else machine_depth - resolveOrValue('prime_tower_size')",
"minimum_value": "machine_depth / -2 if machine_center_is_zero else 0",
"settable_per_mesh": false,
@@ -5119,6 +5318,16 @@
"settable_per_mesh": false,
"settable_per_extruder": true
},
+ "prime_tower_brim_enable":
+ {
+ "label": "Prime Tower Brim",
+ "description": "Prime-towers might need the extra adhesion afforded by a brim even if the model doesn't. Presently can't be used with the 'Raft' adhesion-type.",
+ "type": "bool",
+ "enabled": "resolveOrValue('prime_tower_enable') and (resolveOrValue('adhesion_type') != 'raft')",
+ "default_value": false,
+ "settable_per_mesh": false,
+ "settable_per_extruder": false
+ },
"ooze_shield_enabled":
{
"label": "Enable Ooze Shield",
@@ -5834,7 +6043,7 @@
"description": "Alternate the direction in which the top/bottom layers are printed. Normally they are printed diagonally only. This setting adds the X-only and Y-only directions.",
"type": "bool",
"default_value": false,
- "enabled": "top_bottom_pattern != 'concentric'",
+ "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern != 'concentric'",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
},
@@ -6483,6 +6692,29 @@
"settable_per_extruder": false,
"settable_per_meshgroup": false
},
+ "wall_overhang_angle":
+ {
+ "label": "Overhanging Wall Angle",
+ "description": "Walls that overhang more than this angle will be printed using overhanging wall settings. When the value is 90, no walls will be treated as overhanging.",
+ "unit": "°",
+ "type": "float",
+ "minimum_value": "0",
+ "minimum_value_warning": "2",
+ "maximum_value": "90",
+ "default_value": 90,
+ "settable_per_mesh": true
+ },
+ "wall_overhang_speed_factor":
+ {
+ "label": "Overhanging Wall Speed",
+ "description": "Overhanging walls will be printed at this percentage of their normal print speed.",
+ "unit": "%",
+ "type": "float",
+ "default_value": 100,
+ "minimum_value": "0.001",
+ "minimum_value_warning": "25",
+ "settable_per_mesh": true
+ },
"bridge_settings_enabled":
{
"label": "Enable Bridge Settings",
@@ -6502,8 +6734,8 @@
"minimum_value": "0",
"default_value": 5,
"enabled": "bridge_settings_enabled",
- "settable_per_mesh": false,
- "settable_per_extruder": true
+ "settable_per_mesh": true,
+ "settable_per_extruder": false
},
"bridge_skin_support_threshold":
{
@@ -6517,18 +6749,6 @@
"enabled": "bridge_settings_enabled",
"settable_per_mesh": true
},
- "bridge_wall_max_overhang":
- {
- "label": "Bridge Wall Max Overhang",
- "description": "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings.",
- "unit": "%",
- "default_value": 100,
- "type": "float",
- "minimum_value": "0",
- "maximum_value": "100",
- "enabled": "bridge_settings_enabled",
- "settable_per_mesh": true
- },
"bridge_wall_coast":
{
"label": "Bridge Wall Coasting",
@@ -6539,7 +6759,7 @@
"minimum_value": "0",
"maximum_value": "500",
"enabled": "bridge_settings_enabled",
- "settable_per_mesh": false
+ "settable_per_mesh": true
},
"bridge_wall_speed":
{
diff --git a/resources/definitions/gmax15plus.def.json b/resources/definitions/gmax15plus.def.json
index 16695714f4..069b8be999 100644
--- a/resources/definitions/gmax15plus.def.json
+++ b/resources/definitions/gmax15plus.def.json
@@ -14,19 +14,24 @@
"has_variants": true,
"variants_name": "Hotend",
"preferred_variant_name": "0.5mm E3D (Default)",
+ "preferred_quality_type": "gmax15plus_global_normal",
"machine_extruder_trains": {
"0": "gmax15plus_extruder_0"
}
+
+
},
"overrides": {
- "machine_extruder_count": { "default_value": 1 },
+ "machine_extruder_count": { "default_value": 1 },
"machine_name": { "default_value": "gMax 1.5 Plus" },
"machine_heated_bed": { "default_value": false },
"machine_width": { "default_value": 406 },
"machine_depth": { "default_value": 406 },
"machine_height": { "default_value": 533 },
"machine_center_is_zero": { "default_value": false },
+ "material_diameter": { "default_value": 1.75 },
+ "machine_nozzle_size": { "default_value": 0.5 },
"layer_height": { "default_value": 0.2 },
"layer_height_0": { "default_value": 0.3 },
"retraction_amount": { "default_value": 1 },
@@ -43,10 +48,10 @@
"machine_max_jerk_z": { "default_value": 0.4 },
"machine_max_jerk_e": { "default_value": 5.0 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
- "machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 ;Home X/Y/Z\nG29 ; Bed level\nM104 S{material_print_temperature} ; Preheat\nM109 S{material_print_temperature} ; Preheat\nG91 ;relative positioning\nG90 ;absolute positioning\nG1 Z25.0 F9000 ;raise nozzle 25mm\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." },
+ "machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 ;Home X/Y/Z\nM104 S{material_print_temperature} ; Preheat\nM109 S{material_print_temperature} ; Preheat\nG91 ;relative positioning\nG90 ;absolute positioning\nG1 Z25.0 F9000 ;raise nozzle 25mm\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." },
"machine_end_gcode": { "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning" },
- "material_print_temperature": { "default_value": 202 },
- "wall_thickness": { "default_value": 1 },
+ "material_print_temperature": { "default_value": 202 },
+ "wall_thickness": { "default_value": 1 },
"top_bottom_thickness": { "default_value": 1 },
"bottom_thickness": { "default_value": 1 }
}
diff --git a/resources/definitions/gmax15plus_dual.def.json b/resources/definitions/gmax15plus_dual.def.json
index 5972061933..0264ef5977 100644
--- a/resources/definitions/gmax15plus_dual.def.json
+++ b/resources/definitions/gmax15plus_dual.def.json
@@ -10,24 +10,26 @@
"category": "Other",
"file_formats": "text/x-gcode",
"platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
- "has_variants": true,
- "has_machine_quality": true,
- "variants_name": "Hotend",
- "preferred_variant_name": "0.5mm E3D (Default)",
- "machine_extruder_trains": {
- "0": "gmax15plus_dual_extruder_0",
- "1": "gmax15plus_dual_extruder_1"
- }
+ "has_variants": true,
+ "variants_name": "Hotend",
+ "preferred_variant_name": "0.5mm E3D (Default)",
+ "preferred_quality_type": "gmax15plus_global_dual_normal",
+ "machine_extruder_trains": {
+ "0": "gmax15plus_dual_extruder_0",
+ "1": "gmax15plus_dual_extruder_1"
+ }
},
"overrides": {
"machine_name": { "default_value": "gMax 1.5 Plus Dual Extruder" },
- "machine_extruder_count": { "default_value": 2 },
+ "machine_extruder_count": { "default_value": 2 },
"machine_heated_bed": { "default_value": false },
"machine_width": { "default_value": 406 },
"machine_depth": { "default_value": 406 },
"machine_height": { "default_value": 533 },
"machine_center_is_zero": { "default_value": false },
+ "material_diameter": { "default_value": 1.75 },
+ "machine_nozzle_size": { "default_value": 0.5 },
"layer_height": { "default_value": 0.2 },
"layer_height_0": { "default_value": 0.3 },
"retraction_amount": { "default_value": 1 },
@@ -44,10 +46,10 @@
"machine_max_jerk_z": { "default_value": 0.4 },
"machine_max_jerk_e": { "default_value": 5.0 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
- "machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 ;Home X/Y/Z\nG29 ; Bed level\nM104 S{material_print_temperature} T0 ; Preheat Left Extruder\nM104 S{material_print_temperature} T1 ; Preheat Right Extruder\nM109 S{material_print_temperature} T0 ; Preheat Left Extruder\nM109 S{material_print_temperature} T1 ; Preheat Right Extruder\nG91 ;relative positioning\nG90 ;absolute positioning\nM218 T1 X34.3 Y0; Set 2nd extruder offset. This can be changed later if needed\nG1 Z25.0 F9000 ;raise nozzle 25mm\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." },
+ "machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 ;Home X/Y/Z\nM104 S{material_print_temperature} T0 ; Preheat Left Extruder\nM104 S{material_print_temperature} T1 ; Preheat Right Extruder\nM109 S{material_print_temperature} T0 ; Preheat Left Extruder\nM109 S{material_print_temperature} T1 ; Preheat Right Extruder\nG91 ;relative positioning\nG90 ;absolute positioning\nM218 T1 X34.3 Y0; Set 2nd extruder offset. This can be changed later if needed\nG1 Z25.0 F9000 ;raise nozzle 25mm\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." },
"machine_end_gcode": { "default_value": "M104 S0 T0;Left extruder off\nM104 S0 T1; Right extruder off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning" },
- "material_print_temperature": { "default_value": 202 },
- "wall_thickness": { "default_value": 1 },
+ "material_print_temperature": { "default_value": 202 },
+ "wall_thickness": { "default_value": 1 },
"top_bottom_thickness": { "default_value": 1 },
"bottom_thickness": { "default_value": 1 }
}
diff --git a/resources/definitions/grr_neo.def.json b/resources/definitions/grr_neo.def.json
index 0153fc4c01..67d6a92023 100644
--- a/resources/definitions/grr_neo.def.json
+++ b/resources/definitions/grr_neo.def.json
@@ -7,7 +7,6 @@
"author": "Simon Cor",
"manufacturer": "German RepRap",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker.png",
"platform": "grr_neo_platform.stl",
"machine_extruder_trains":
{
diff --git a/resources/definitions/jgaurora_a1.def.json b/resources/definitions/jgaurora_a1.def.json
new file mode 100644
index 0000000000..b9a921c311
--- /dev/null
+++ b/resources/definitions/jgaurora_a1.def.json
@@ -0,0 +1,93 @@
+{
+ "name": "JGAurora A1",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Samuel Pinches",
+ "manufacturer": "JGAurora",
+ "file_formats": "text/x-gcode",
+ "preferred_quality_type": "fast",
+ "machine_extruder_trains":
+ {
+ "0": "jgaurora_a1_extruder_0"
+ }
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "JGAurora A1"
+ },
+ "machine_start_gcode": {
+ "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nM420 S1 ;turn on mesh bed levelling if enabled in firmware\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 X60.0 Z0 E9.0 F1000.0;intro line\nG1 X100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --"
+ },
+ "machine_end_gcode": {
+ "default_value": "; -- END GCODE --\nM104 S0 ;turn off nozzle heater\nM140 S0 ;turn off bed heater\nG91 ;set to relative positioning\nG1 E-10 F300 ;retract the filament slightly\nG90 ;set to absolute positioning\nG28 X0 ;move to the X-axis origin (Home)\nG0 Y280 F600 ;bring the bed to the front for easy print removal\nM84 ;turn off stepper motors\n; -- end of END GCODE --"
+ },
+ "machine_width": {
+ "default_value": 300
+ },
+ "machine_height": {
+ "default_value": 300
+ },
+ "machine_depth": {
+ "default_value": 300
+ },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "machine_center_is_zero": {
+ "default_value": false
+ },
+ "gantry_height": {
+ "default_value": 10
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "material_print_temperature": {
+ "default_value": 215
+ },
+ "material_bed_temperature": {
+ "default_value": 67
+ },
+ "layer_height_0": {
+ "default_value": 0.12
+ },
+ "wall_thickness": {
+ "default_value": 1.2
+ },
+ "speed_print": {
+ "default_value": 40
+ },
+ "speed_infill": {
+ "default_value": 40
+ },
+ "speed_wall": {
+ "default_value": 35
+ },
+ "speed_topbottom": {
+ "default_value": 35
+ },
+ "speed_travel": {
+ "default_value": 120
+ },
+ "speed_layer_0": {
+ "default_value": 12
+ },
+ "support_enable": {
+ "default_value": true
+ },
+ "retraction_enable": {
+ "default_value": true
+ },
+ "retraction_amount": {
+ "default_value": 6
+ },
+ "retraction_speed": {
+ "default_value": 40
+ }
+ }
+}
diff --git a/resources/definitions/jgaurora_a5.def.json b/resources/definitions/jgaurora_a5.def.json
new file mode 100644
index 0000000000..d84a8440e6
--- /dev/null
+++ b/resources/definitions/jgaurora_a5.def.json
@@ -0,0 +1,95 @@
+{
+ "name": "JGAurora A5 & A5S",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Samuel Pinches",
+ "manufacturer": "JGAurora",
+ "file_formats": "text/x-gcode",
+ "platform": "jgaurora_a5.stl",
+ "platform_offset": [-242, -101, 273],
+ "preferred_quality_type": "fast",
+ "machine_extruder_trains":
+ {
+ "0": "jgaurora_a5_extruder_0"
+ }
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "JGAurora A5 & A5S"
+ },
+ "machine_start_gcode": {
+ "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nM420 S1 ;turn on mesh bed levelling if enabled in firmware\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 X60.0 Z0 E9.0 F1000.0;intro line\nG1 X100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --"
+ },
+ "machine_end_gcode": {
+ "default_value": "; -- END GCODE --\nM104 S0 ;turn off nozzle heater\nM140 S0 ;turn off bed heater\nG91 ;set to relative positioning\nG1 E-10 F300 ;retract the filament slightly\nG90 ;set to absolute positioning\nG28 X0 ;move to the X-axis origin (Home)\nG0 Y280 F600 ;bring the bed to the front for easy print removal\nM84 ;turn off stepper motors\n; -- end of END GCODE --"
+ },
+ "machine_width": {
+ "default_value": 300
+ },
+ "machine_height": {
+ "default_value": 320
+ },
+ "machine_depth": {
+ "default_value": 300
+ },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "machine_center_is_zero": {
+ "default_value": false
+ },
+ "gantry_height": {
+ "default_value": 10
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "material_print_temperature": {
+ "default_value": 215
+ },
+ "material_bed_temperature": {
+ "default_value": 67
+ },
+ "layer_height_0": {
+ "default_value": 0.12
+ },
+ "wall_thickness": {
+ "default_value": 1.2
+ },
+ "speed_print": {
+ "default_value": 40
+ },
+ "speed_infill": {
+ "default_value": 40
+ },
+ "speed_wall": {
+ "default_value": 35
+ },
+ "speed_topbottom": {
+ "default_value": 35
+ },
+ "speed_travel": {
+ "default_value": 120
+ },
+ "speed_layer_0": {
+ "default_value": 12
+ },
+ "support_enable": {
+ "default_value": true
+ },
+ "retraction_enable": {
+ "default_value": true
+ },
+ "retraction_amount": {
+ "default_value": 8
+ },
+ "retraction_speed": {
+ "default_value": 45
+ }
+ }
+}
diff --git a/resources/definitions/jgaurora_z_603s.def.json b/resources/definitions/jgaurora_z_603s.def.json
new file mode 100644
index 0000000000..3a78585240
--- /dev/null
+++ b/resources/definitions/jgaurora_z_603s.def.json
@@ -0,0 +1,93 @@
+{
+ "name": "JGAurora Z-603S",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Samuel Pinches",
+ "manufacturer": "JGAurora",
+ "file_formats": "text/x-gcode",
+ "preferred_quality_type": "fast",
+ "machine_extruder_trains":
+ {
+ "0": "jgaurora_z_603s_extruder_0"
+ }
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "JGAurora Z-603S"
+ },
+ "machine_start_gcode": {
+ "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nM420 S1 ;turn on mesh bed levelling if enabled in firmware\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 X60.0 Z0 E9.0 F1000.0;intro line\nG1 X100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --"
+ },
+ "machine_end_gcode": {
+ "default_value": "; -- END GCODE --\nM104 S0 ;turn off nozzle heater\nM140 S0 ;turn off bed heater\nG91 ;set to relative positioning\nG1 E-10 F300 ;retract the filament slightly\nG90 ;set to absolute positioning\nG28 X0 ;move to the X-axis origin (Home)\nG0 Y280 F600 ;bring the bed to the front for easy print removal\nM84 ;turn off stepper motors\n; -- end of END GCODE --"
+ },
+ "machine_width": {
+ "default_value": 280
+ },
+ "machine_height": {
+ "default_value": 175
+ },
+ "machine_depth": {
+ "default_value": 180
+ },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "machine_center_is_zero": {
+ "default_value": false
+ },
+ "gantry_height": {
+ "default_value": 10
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "material_print_temperature": {
+ "default_value": 210
+ },
+ "material_bed_temperature": {
+ "default_value": 55
+ },
+ "layer_height_0": {
+ "default_value": 0.2
+ },
+ "wall_thickness": {
+ "default_value": 1.2
+ },
+ "speed_print": {
+ "default_value": 60
+ },
+ "speed_infill": {
+ "default_value": 60
+ },
+ "speed_wall": {
+ "default_value": 30
+ },
+ "speed_topbottom": {
+ "default_value": 45
+ },
+ "speed_travel": {
+ "default_value": 125
+ },
+ "speed_layer_0": {
+ "default_value": 20
+ },
+ "support_enable": {
+ "default_value": true
+ },
+ "retraction_enable": {
+ "default_value": true
+ },
+ "retraction_amount": {
+ "default_value": 5
+ },
+ "retraction_speed": {
+ "default_value": 50
+ }
+ }
+}
diff --git a/resources/definitions/kossel_mini.def.json b/resources/definitions/kossel_mini.def.json
index 76fe72dac1..91f374fb6d 100644
--- a/resources/definitions/kossel_mini.def.json
+++ b/resources/definitions/kossel_mini.def.json
@@ -7,7 +7,6 @@
"author": "Claudio Sampaio (Patola)",
"manufacturer": "Other",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "kossel_platform.stl",
"platform_offset": [0, -0.25, 0],
"machine_extruder_trains":
diff --git a/resources/definitions/kossel_pro.def.json b/resources/definitions/kossel_pro.def.json
index 9fadd0db91..e104538b2c 100644
--- a/resources/definitions/kossel_pro.def.json
+++ b/resources/definitions/kossel_pro.def.json
@@ -7,7 +7,6 @@
"author": "Chris Petersen",
"manufacturer": "OpenBeam",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "kossel_pro_build_platform.stl",
"platform_offset": [0, -0.25, 0],
"machine_extruder_trains":
diff --git a/resources/definitions/makeR_pegasus.def.json b/resources/definitions/makeR_pegasus.def.json
index 9bd4547c9b..ac09aa01ac 100644
--- a/resources/definitions/makeR_pegasus.def.json
+++ b/resources/definitions/makeR_pegasus.def.json
@@ -7,7 +7,6 @@
"author": "makeR",
"manufacturer": "makeR",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "makeR_pegasus_platform.stl",
"platform_offset": [-200, -10, 200],
"machine_extruder_trains":
diff --git a/resources/definitions/makeR_prusa_tairona_i3.def.json b/resources/definitions/makeR_prusa_tairona_i3.def.json
index d22af5c516..0e59874978 100644
--- a/resources/definitions/makeR_prusa_tairona_i3.def.json
+++ b/resources/definitions/makeR_prusa_tairona_i3.def.json
@@ -7,7 +7,6 @@
"author": "makeR",
"manufacturer": "makeR",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "makeR_prusa_tairona_i3_platform.stl",
"platform_offset": [-2, 0, 0],
"machine_extruder_trains":
diff --git a/resources/definitions/makeit_pro_l.def.json b/resources/definitions/makeit_pro_l.def.json
index 2f9173c90e..d40d63f97b 100644
--- a/resources/definitions/makeit_pro_l.def.json
+++ b/resources/definitions/makeit_pro_l.def.json
@@ -8,7 +8,6 @@
"manufacturer": "NA",
"file_formats": "text/x-gcode",
"has_materials": false,
- "supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
"machine_extruder_trains":
{
"0": "makeit_l_dual_1st",
diff --git a/resources/definitions/makeit_pro_m.def.json b/resources/definitions/makeit_pro_m.def.json
index 0cd7b42df3..1f0381df86 100644
--- a/resources/definitions/makeit_pro_m.def.json
+++ b/resources/definitions/makeit_pro_m.def.json
@@ -8,7 +8,6 @@
"manufacturer": "NA",
"file_formats": "text/x-gcode",
"has_materials": false,
- "supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
"machine_extruder_trains":
{
"0": "makeit_dual_1st",
diff --git a/resources/definitions/maker_starter.def.json b/resources/definitions/maker_starter.def.json
index 8fb67623ed..be85e54967 100644
--- a/resources/definitions/maker_starter.def.json
+++ b/resources/definitions/maker_starter.def.json
@@ -7,7 +7,6 @@
"author": "tvlgiao",
"manufacturer": "3DMaker",
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj",
- "icon": "icon_ultimaker2.png",
"platform": "makerstarter_platform.stl",
"preferred_quality_type": "draft",
"machine_extruder_trains":
diff --git a/resources/definitions/makerbotreplicator.def.json b/resources/definitions/makerbotreplicator.def.json
index 1770b7a979..3b02215e74 100644
--- a/resources/definitions/makerbotreplicator.def.json
+++ b/resources/definitions/makerbotreplicator.def.json
@@ -6,6 +6,7 @@
"visible": true,
"author": "Ultimaker",
"manufacturer": "MakerBot",
+ "machine_x3g_variant": "r1",
"file_formats": "application/x3g",
"platform_offset": [ 0, 0, 0],
"machine_extruder_trains":
diff --git a/resources/definitions/mendel90.def.json b/resources/definitions/mendel90.def.json
index 104ca7f42f..85d82ae176 100644
--- a/resources/definitions/mendel90.def.json
+++ b/resources/definitions/mendel90.def.json
@@ -5,7 +5,7 @@
"metadata":
{
"visible": true,
- "author": "Bo Herrmannsen",
+ "author": "Wilmer Gaona",
"manufacturer": "Nophead",
"file_formats": "text/x-gcode",
"platform": "mendel90_platform.stl",
@@ -23,10 +23,10 @@
"overrides": {
"machine_name": { "default_value": "Mendel90" },
"machine_start_gcode": {
- "default_value": "G21 ;metric values\nG90 ;absolute positioning\nG92 E0 ;zero the extruded length\nM107 ;start with the fan off\nG1 X90 Y200 F6000 ;go to the middle of the front\nG1 Z0.05 ;close to the bed\nG1 Z0.3 ;lift Z\n"
+ "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;absolute extrusion\nM107 ;start with the fan off\nG28 ;home\nG92 E0 ;zero the extruded length\nM140 S{material_bed_temperature_layer_0} ; set the bed temperature and continue on\nG1 X-50 Y98 F9000 ;go to the left of the top\nG1 Z0.05 ; close to the bed\nM104 S{material_print_temperature_layer_0}; pre-heat the extruder continue on\nM190 S{material_bed_temperature_layer_0} ;set the bed temp & wait\nM109 S{material_print_temperature_layer_0};set the extruder temp for layer 0 & wait\nG92 E0 ;zero the extruded length\nG1 X50 E10 F300 ; make a thick line to prime extruder\nG92 E0 ; reset extruder\nG1 E-4 F1800\nG1 Z0.3 ;lift Z\n"
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM107 ;carriage fan off\nG91 ;relative positioning\nG1 Z10 ;Move up Z 10mm\nG90 ;back to absolute mode\nG1 E-1 F1200 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG92 E0 ;zero the extruded length\nG1 Y200 F5000 ;Move Y to middle of bed cooling fan\nM42 P42 S255 ;Turn on Bed cooling fan on\nG4 S420 ;Wait 7 mins\nM42 P42 S0 ;Turn off bed cooling fan\nG1 Y10 F5000 ;Move Y to front\nM84 ;steppers off\n"
+ "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM107 ;carriage fan off\nG91 ;relative positioning\nG1 Z10 ;Move up Z 10mm\nG90 ;back to absolute mode\nG1 E-1 F1200 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG92 E0 ;zero the extruded length\nG1 Z200 X-100 F9000 ; go to top\nG1 Y100 F5000 ;Move Y to back\nM42 P42 S255 ;Turn on Bed cooling fan on\nG4 S10 ;Wait 10 seconds\nM42 P42 S0 ;Turn off bed cooling fan\nM84 ;steppers off\n"
},
"material_bed_temp_wait": {
"default_value": true
@@ -47,7 +47,7 @@
"default_value": true
},
"machine_center_is_zero": {
- "default_value": false
+ "default_value": true
},
"machine_extruder_count": {
"default_value": 1
diff --git a/resources/definitions/monoprice_ultimate.def.json b/resources/definitions/monoprice_ultimate.def.json
new file mode 100644
index 0000000000..48290f0941
--- /dev/null
+++ b/resources/definitions/monoprice_ultimate.def.json
@@ -0,0 +1,52 @@
+{
+ "version": 2,
+ "name": "Monoprice Ultimate",
+ "inherits": "wanhao_d6",
+ "metadata": {
+ "visible": true,
+ "author": "Danny Tuppeny",
+ "manufacturer": "monoprice",
+ "file_formats": "text/x-gcode",
+ "icon": "wanhao-icon.png",
+ "has_materials": true,
+ "platform": "wanhao_200_200_platform.obj",
+ "platform_texture": "Wanhaobackplate.png",
+ "machine_extruder_trains": {
+ "0": "wanhao_d6_extruder_0"
+ },
+ "platform_offset": [
+ 0,
+ -28,
+ 0
+ ]
+ },
+ "overrides": {
+ "machine_name": {
+ "default_value": "Monoprice Ultimate"
+ },
+ "machine_max_acceleration_x": {
+ "default_value": 3000
+ },
+ "machine_max_acceleration_y": {
+ "default_value": 3000
+ },
+ "machine_max_acceleration_z": {
+ "default_value": 100
+ },
+ "machine_max_acceleration_e": {
+ "default_value": 500
+ },
+ "machine_acceleration": {
+ "default_value": 800
+ },
+ "machine_max_jerk_xy": {
+ "default_value": 10.0
+ },
+ "machine_max_jerk_z": {
+ "default_value": 0.4
+ },
+ "machine_max_jerk_e": {
+ "default_value": 1.0
+ }
+ }
+}
diff --git a/resources/definitions/nwa3d_a5.def.json b/resources/definitions/nwa3d_a5.def.json
new file mode 100644
index 0000000000..3deb0027fd
--- /dev/null
+++ b/resources/definitions/nwa3d_a5.def.json
@@ -0,0 +1,64 @@
+{
+ "name": "NWA3D A5",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "DragonJe",
+ "manufacturer": "NWA 3D LLC",
+ "file_formats": "text/x-gcode",
+ "platform_offset": [0, 0, 0],
+ "has_materials": true,
+ "has_variants": false,
+ "has_machine_materials": true,
+ "has_variant_materials": false,
+ "preferred_quality_type": "normal",
+ "has_machine_quality": true,
+ "preferred_material": "generic_pla",
+ "machine_extruder_trains":
+ {
+ "0": "nwa3d_a5_extruder_0"
+ }
+ },
+
+ "overrides": {
+ "machine_name": {
+ "default_value": "NWA3D A5"
+ },
+ "machine_width": {
+ "default_value": 125
+ },
+ "machine_height": {
+ "default_value": 100
+ },
+ "machine_depth": {
+ "default_value": 150
+ },
+ "machine_head_polygon": {
+ "default_value": [
+ [-30, 34],
+ [-30, -32],
+ [30, -32],
+ [30, 34]
+ ]
+ },
+ "gantry_height": {
+ "default_value": 30
+ },
+ "machine_heated_bed": {
+ "default_value": false
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (RepRap)"
+ },
+ "machine_start_gcode": {
+ "default_value": "G28 ; Home\nG1 Z15.0 F6000 ; Move Z axis up 15mm\n ; Prime the extruder\nG92 E0\nG1 F200 E3\nG92 E0"
+ },
+ "machine_end_gcode": {
+ "default_value": "M104 S0\nM140 S0\n ; Retract the filament\nG92 E1\nG1 E-1 F300\nG28 X0 Y0\nM84"
+ }
+ }
+}
diff --git a/resources/definitions/peopoly_moai.def.json b/resources/definitions/peopoly_moai.def.json
index 5c03444d49..a578cc4240 100644
--- a/resources/definitions/peopoly_moai.def.json
+++ b/resources/definitions/peopoly_moai.def.json
@@ -4,13 +4,14 @@
"inherits": "fdmprinter",
"metadata": {
"visible": true,
- "author": "fieldOfView",
+ "author": "Peopoly",
"manufacturer": "Peopoly",
"file_formats": "text/x-gcode",
"has_machine_quality": true,
"has_materials": false,
- "machine_extruder_trains":
- {
+ "platform": "moai.obj",
+ "platform_texture": "moai.jpg",
+ "machine_extruder_trains": {
"0": "peopoly_moai_extruder_0"
}
},
@@ -46,7 +47,6 @@
"machine_end_gcode": {
"default_value": "M104 S0\nM140 S0\nG28 X0 Y0\nM84"
},
-
"line_width": {
"minimum_value_warning": "machine_nozzle_size"
},
@@ -75,7 +75,14 @@
"value": "0.1"
},
"top_bottom_thickness": {
- "minimum_value_warning": "0.1"
+ "minimum_value_warning": "0.1",
+ "value": "0.1"
+ },
+ "top_thickness": {
+ "minimum_value_warning": "resolveOrValue('layer_height')"
+ },
+ "bottom_thickness": {
+ "minimum_value_warning": "resolveOrValue('layer_height')"
},
"infill_sparse_thickness": {
"maximum_value_warning": "0.5"
@@ -102,24 +109,23 @@
"value": "speed_print"
},
"speed_travel": {
- "value": "300"
+ "value": 150
},
"speed_travel_layer_0": {
- "value": "300"
+ "value": 150
},
"speed_layer_0": {
- "value": "5"
+ "value": 5
},
"speed_slowdown_layers": {
- "value": "2"
+ "value": 3
},
"infill_overlap": {
- "value": "15"
+ "value": 15
},
"adhesion_type": {
- "value": "\"none\""
+ "value": "'none'"
},
-
"acceleration_enabled": {
"value": "False"
},
@@ -139,6 +145,10 @@
"enabled": false,
"value": "False"
},
+ "cool_fan_speed_min": {
+ "enabled": false,
+ "value": 0
+ },
"retraction_enable": {
"enabled": false,
"value": "False"
@@ -148,7 +158,8 @@
"value": "'off'"
},
"retract_at_layer_change": {
- "enabled": false
+ "enabled": false,
+ "value": false
},
"cool_min_layer_time_fan_speed_max": {
"enabled": false
@@ -158,6 +169,117 @@
},
"cool_fan_full_layer": {
"enabled": false
+ },
+ "minimum_polygon_circumference": {
+ "value": "0.1"
+ },
+ "meshfix_maximum_resolution": {
+ "value": "0.005"
+ },
+ "skin_outline_count": {
+ "value": 0
+ },
+ "travel_compensate_overlapping_walls_enabled": {
+ "value": "False"
+ },
+ "travel_compensate_overlapping_walls_0_enabled": {
+ "value": "False"
+ },
+ "travel_compensate_overlapping_walls_x_enabled": {
+ "value": "False"
+ },
+ "wall_0_wipe_dist": {
+ "value": "machine_nozzle_size / 3"
+ },
+ "wall_thickness": {
+ "value": 0.5
+ },
+ "infill_sparse_density": {
+ "value": 70
+ },
+ "infill_pattern": {
+ "value": "'lines'"
+ },
+ "infill_angles": {
+ "value": "[0,90]"
+ },
+ "cool_min_layer_time": {
+ "enabled": false,
+ "value": 0
+ },
+ "cool_min_speed": {
+ "enabled": false,
+ "value": 0
+ },
+ "cool_lift_head": {
+ "enabled": false,
+ "value": "False"
+ },
+ "material_flow": {
+ "enabled": false
+ },
+ "material_flow_layer_0": {
+ "enabled": false
+ },
+ "speed_equalize_flow_enabled": {
+ "enabled": false,
+ "value": "False"
+ },
+ "draft_shield_enabled": {
+ "enabled": false,
+ "value": "False"
+ },
+ "z_seam_corner": {
+ "value": "'z_seam_corner_none'"
+ },
+ "z_seam_type": {
+ "value": "'shortest'"
+ },
+ "skin_no_small_gaps_heuristic": {
+ "value": "False"
+ },
+ "ironing_enabled": {
+ "enabled": false,
+ "value": "False"
+ },
+ "skin_overlap": {
+ "value": 5
+ },
+ "infill_wipe_dist": {
+ "value": 0
+ },
+ "expand_skins_expand_distance": {
+ "value": "( wall_line_width_0 + (wall_line_count - 1) * wall_line_width_x ) / 2"
+ },
+ "max_feedrate_z_override": {
+ "value": 0,
+ "enabled": false
+ },
+ "flow_rate_max_extrusion_offset": {
+ "enabled": false
+ },
+ "flow_rate_extrusion_offset_factor": {
+ "enabled": false
+ },
+ "adaptive_layer_height_enabled": {
+ "value": "False",
+ "enabled": false
+ },
+ "bridge_settings_enabled": {
+ "value": "False",
+ "enabled": false
+ },
+ "acceleration_enabled": {
+ "value": "False",
+ "enabled": false
+ },
+ "relative_extrusion": {
+ "value": "False",
+ "enabled": false
+ },
+ "coasting_enable": {
+ "value": "False",
+ "enabled": false
}
}
}
diff --git a/resources/definitions/printrbot_play_heated.def.json b/resources/definitions/printrbot_play_heated.def.json
index 680496bf56..7987f55010 100644
--- a/resources/definitions/printrbot_play_heated.def.json
+++ b/resources/definitions/printrbot_play_heated.def.json
@@ -7,7 +7,6 @@
"author": "Chris Pearson",
"manufacturer": "Printrbot",
"file_formats": "text/x-gcode",
- "platform": "",
"machine_extruder_trains":
{
"0": "printrbot_play_heated_extruder_0"
diff --git a/resources/definitions/prusa_i3.def.json b/resources/definitions/prusa_i3.def.json
index c676f7fe96..1f0eb37aec 100644
--- a/resources/definitions/prusa_i3.def.json
+++ b/resources/definitions/prusa_i3.def.json
@@ -7,7 +7,6 @@
"author": "Quillford",
"manufacturer": "Prusajr",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "prusai3_platform.stl",
"machine_extruder_trains":
{
diff --git a/resources/definitions/prusa_i3_mk2.def.json b/resources/definitions/prusa_i3_mk2.def.json
index 169eb6ffc2..5c5583b56f 100644
--- a/resources/definitions/prusa_i3_mk2.def.json
+++ b/resources/definitions/prusa_i3_mk2.def.json
@@ -7,7 +7,6 @@
"author": "Apsu, Nounours2099",
"manufacturer": "Prusa Research",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "prusai3_platform.stl",
"has_materials": true,
"machine_extruder_trains":
diff --git a/resources/definitions/prusa_i3_xl.def.json b/resources/definitions/prusa_i3_xl.def.json
index eafed22df1..9931be5c72 100644
--- a/resources/definitions/prusa_i3_xl.def.json
+++ b/resources/definitions/prusa_i3_xl.def.json
@@ -7,7 +7,6 @@
"author": "guigashm",
"manufacturer": "Prusajr",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2.png",
"platform": "prusai3_xl_platform.stl",
"machine_extruder_trains":
{
diff --git a/resources/definitions/seemecnc_artemis.def.json b/resources/definitions/seemecnc_artemis.def.json
index aa788865df..ec92f528d7 100644
--- a/resources/definitions/seemecnc_artemis.def.json
+++ b/resources/definitions/seemecnc_artemis.def.json
@@ -7,7 +7,6 @@
"author": "PouncingIguana, JJ",
"manufacturer": "SeeMeCNC",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "artemis_platform.stl",
"has_materials": true,
"machine_extruder_trains":
diff --git a/resources/definitions/seemecnc_v32.def.json b/resources/definitions/seemecnc_v32.def.json
index 5a855f67fc..d4316c25d9 100644
--- a/resources/definitions/seemecnc_v32.def.json
+++ b/resources/definitions/seemecnc_v32.def.json
@@ -7,7 +7,6 @@
"author": "PouncingIguana, JJ",
"manufacturer": "SeeMeCNC",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "rostock_platform.stl",
"has_materials": true,
"machine_extruder_trains":
diff --git a/resources/definitions/tam.def.json b/resources/definitions/tam.def.json
index 9865abedda..0ed8d657a2 100644
--- a/resources/definitions/tam.def.json
+++ b/resources/definitions/tam.def.json
@@ -10,7 +10,6 @@
"platform": "tam_series1.stl",
"platform_offset": [-580.0, -6.23, 253.5],
"has_materials": false,
- "supported_actions": ["UpgradeFirmware"],
"machine_extruder_trains":
{
"0": "tam_extruder_0"
diff --git a/resources/definitions/tevo_blackwidow.def.json b/resources/definitions/tevo_blackwidow.def.json
index b193023867..25e7a2620d 100644
--- a/resources/definitions/tevo_blackwidow.def.json
+++ b/resources/definitions/tevo_blackwidow.def.json
@@ -7,7 +7,6 @@
"author": "TheTobby",
"manufacturer": "Tevo",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"has_materials": false,
"has_machine_quality": true,
"platform": "tevo_blackwidow.stl",
diff --git a/resources/definitions/tevo_tarantula.def.json b/resources/definitions/tevo_tarantula.def.json
index 40d579552e..ec4ae667d5 100644
--- a/resources/definitions/tevo_tarantula.def.json
+++ b/resources/definitions/tevo_tarantula.def.json
@@ -8,7 +8,6 @@
"author": "TheAssassin",
"manufacturer": "Tevo",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "prusai3_platform.stl",
"machine_extruder_trains":
{
@@ -43,7 +42,7 @@
"machine_max_feedrate_x": { "default_value": 255 },
"machine_max_feedrate_y": { "default_value": 225 },
"machine_max_feedrate_z": { "default_value": 3 },
- "machine_max_acceleration_x": { "default_value": 2620 },
+ "machine_max_acceleration_x": { "default_value": 2650 },
"machine_max_acceleration_y": { "default_value": 2650 },
"acceleration_print": { "default_value": 2650 },
"machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." },
diff --git a/resources/definitions/tevo_tornado.def.json b/resources/definitions/tevo_tornado.def.json
index e121c8e097..cb3a6c45bd 100644
--- a/resources/definitions/tevo_tornado.def.json
+++ b/resources/definitions/tevo_tornado.def.json
@@ -7,7 +7,6 @@
"author": "nean",
"manufacturer": "Tevo",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2.png",
"has_materials": true,
"machine_extruder_trains": {
"0": "tevo_tornado_extruder_0"
diff --git a/resources/definitions/tizyx_evy.def.json b/resources/definitions/tizyx_evy.def.json
new file mode 100644
index 0000000000..fe9a02a31c
--- /dev/null
+++ b/resources/definitions/tizyx_evy.def.json
@@ -0,0 +1,74 @@
+{
+ "name": "TiZYX EVY",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "TiZYX",
+ "manufacturer": "TiZYX",
+ "file_formats": "text/x-gcode",
+
+ "has_machine_quality": true,
+ "has_materials": true,
+ "has_machine_materials": true,
+ "has_variants": true,
+
+ "preferred_variant_name": "0.4mm",
+ "preferred_material": "tizyx_pla",
+ "preferred_quality_type": "normal",
+ "exclude_materials": ["chromatik_pla", "dsm_arnitel2045_175", "dsm_novamid1070_175", "fabtotum_abs", "fabtotum_nylon", "fabtotum_pla", "fabtotum_tpu", "fiberlogy_hd_pla", "filo3d_pla", "filo3d_pla_green", "filo3d_pla_red", "generic_bam", "generic_cpe", "generic_cpe_175", "generic_cpe_plus", "generic_hips", "generic_hips_175", "generic_nylon", "generic_nylon_175", "generic_pc", "generic_pc_175","generic_pp", "generic_pva", "generic_pva_175", "generic_tpu", "imade3d_petg_green", "imade3d_petg_pink", "imade3d_pla_green", "imade3d_pla_pink", "innofill_innoflex60_175", "octofiber_pla", "polyflex_pla", "polymax_pla", "polyplus_pla", "polywood_pla", "ultimaker_abs_black", "ultimaker_abs_blue", "ultimaker_abs_green", "ultimaker_abs_grey", "ultimaker_abs_orange", "ultimaker_abs_pearl-gold", "ultimaker_abs_red", "ultimaker_abs_silver-metallic", "ultimaker_abs_white", "ultimaker_abs_yellow", "ultimaker_bam", "ultimaker_cpe_black", "ultimaker_cpe_blue", "ultimaker_cpe_dark-grey", "ultimaker_cpe_green", "ultimaker_cpe_light-grey", "ultimaker_cpe_plus_black", "ultimaker_cpe_plus_transparent", "ultimaker_cpe_plus_white", "ultimaker_cpe_red", "ultimaker_cpe_transparent", "ultimaker_cpe_white", "ultimaker_cpe_yellow", "ultimaker_nylon_black", "ultimaker_nylon_transparent", "ultimaker_pc_black", "ultimaker_pc_transparent", "ultimaker_pc_white", "ultimaker_pla_black", "ultimaker_pla_blue", "ultimaker_pla_green", "ultimaker_pla_magenta", "ultimaker_pla_orange", "ultimaker_pla_pearl-white", "ultimaker_pla_red", "ultimaker_pla_silver-metallic", "ultimaker_pla_transparent", "ultimaker_pla_white", "ultimaker_pla_yellow", "ultimaker_pp_transparent", "ultimaker_pva", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white", "ultimaker_tpu_black", "ultimaker_tpu_blue", "ultimaker_tpu_red", "ultimaker_tpu_white", "verbatim_bvoh_175", "Vertex_Delta_ABS", "Vertex_Delta_PET", "Vertex_Delta_PLA", "Vertex_Delta_TPU", "zyyx_pro_flex", "zyyx_pro_pla" ],
+
+ "machine_extruder_trains":
+ {
+ "0": "tizyx_evy_extruder_0"
+ },
+ "platform": "tizyx_k25_platform.stl",
+ "platform_offset": [0, -4, 0],
+ "first_start_actions": ["MachineSettingsAction"],
+ "supported_actions": ["MachineSettingsAction"]
+ },
+
+ "overrides": {
+ "machine_extruder_count": { "default_value": 1 },
+ "machine_heated_bed": { "default_value": true },
+ "machine_center_is_zero": { "default_value": false },
+ "gantry_height": { "default_value": 500 },
+ "machine_height": { "default_value": 255 },
+ "machine_depth": { "default_value": 255 },
+ "machine_width": { "default_value": 255 },
+ "machine_head_with_fans_polygon": {
+ "default_value": [
+ [25, 49],
+ [25, -49],
+ [-25, -49],
+ [25, 49]
+ ]
+ },
+
+ "adhesion_type": { "default_value": "skirt" },
+ "skirt_line_count": {"default_value": 2},
+ "skirt_gap": { "default_value": 2},
+ "fill_outline_gaps": { "default_value": true},
+ "infill_sparse_density": { "default_value": 15},
+ "retraction_amount": { "default_value": 2.5},
+ "retraction_min_travel": { "default_value": 2},
+ "retraction_speed": { "default_value": 30},
+ "speed_print": { "default_value": 60},
+ "speed_topbottom": { "default_value": 50},
+ "speed_wall_0": { "default_value": 40},
+ "top_layers": { "default_value": 4},
+ "wall_line_count": { "default_value": 2},
+ "cool_min_layer_time": { "default_value": 11},
+ "layer_height": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
+ "layer_height_0": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
+
+ "machine_start_gcode":
+ {
+ "default_value": "M82\nG90\nG28 X\nG28 Y\nG28 Z\nG29\nG91\nG1 Z0\nG90\nM82\nG92 E0\nG1 X125 Y245 F3000\nG1 Z0"
+ },
+ "machine_end_gcode":
+ {
+ "default_value": "M104 S0\nM140 S0\nG91\nG1 E-5 F300\nG1 Z+3 F3000\nG1 Y245 F3000\nM84"
+ }
+ }
+}
diff --git a/resources/definitions/tizyx_k25.def.json b/resources/definitions/tizyx_k25.def.json
index 94a20b371e..d6a5ff5ecd 100644
--- a/resources/definitions/tizyx_k25.def.json
+++ b/resources/definitions/tizyx_k25.def.json
@@ -14,6 +14,8 @@
"preferred_material": "tizyx_pla",
"has_machine_quality": true,
"has_materials": true,
+ "has_variants": true,
+ "preferred_variant_name": "0.4 mm",
"machine_extruder_trains":
{
"0": "tizyx_k25_extruder_0"
diff --git a/resources/definitions/ubuild-3d_mr_bot_280.def.json b/resources/definitions/ubuild-3d_mr_bot_280.def.json
index 1b5cb1456c..7eb65c3e78 100644
--- a/resources/definitions/ubuild-3d_mr_bot_280.def.json
+++ b/resources/definitions/ubuild-3d_mr_bot_280.def.json
@@ -9,8 +9,6 @@
"manufacturer": "uBuild-3D",
"category": "Other",
"file_formats": "text/x-gcode",
- "icon": "icon_uBuild-3D",
- "platform": "mr_bot_280_platform.stl",
"has_materials": true,
"preferred_quality_type": "draft",
"machine_extruder_trains":
diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json
index aa684946c2..4cc291ff45 100644
--- a/resources/definitions/ultimaker2.def.json
+++ b/resources/definitions/ultimaker2.def.json
@@ -8,19 +8,20 @@
"manufacturer": "Ultimaker B.V.",
"weight": 3,
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2.png",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2backplate.png",
"platform_offset": [9, 0, 0],
"has_materials": false,
"has_machine_quality": true,
+ "preferred_variant_name": "0.4 mm",
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
"first_start_actions": ["UM2UpgradeSelection"],
- "supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"],
+ "supported_actions":["UM2UpgradeSelection"],
"machine_extruder_trains":
{
"0": "ultimaker2_extruder_0"
- }
+ },
+ "firmware_file": "MarlinUltimaker2.hex"
},
"overrides": {
"machine_name": { "default_value": "Ultimaker 2" },
diff --git a/resources/definitions/ultimaker2_extended.def.json b/resources/definitions/ultimaker2_extended.def.json
index af169c94fb..572634c602 100644
--- a/resources/definitions/ultimaker2_extended.def.json
+++ b/resources/definitions/ultimaker2_extended.def.json
@@ -8,13 +8,13 @@
"quality_definition": "ultimaker2",
"weight": 3,
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2.png",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2Extendedbackplate.png",
"machine_extruder_trains":
{
"0": "ultimaker2_extended_extruder_0"
- }
+ },
+ "firmware_file": "MarlinUltimaker2extended.hex"
},
"overrides": {
diff --git a/resources/definitions/ultimaker2_extended_plus.def.json b/resources/definitions/ultimaker2_extended_plus.def.json
index f3a8bfcf9f..0242115057 100644
--- a/resources/definitions/ultimaker2_extended_plus.def.json
+++ b/resources/definitions/ultimaker2_extended_plus.def.json
@@ -10,11 +10,11 @@
"file_formats": "text/x-gcode",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2ExtendedPlusbackplate.png",
- "supported_actions": ["UpgradeFirmware"],
"machine_extruder_trains":
{
"0": "ultimaker2_extended_plus_extruder_0"
- }
+ },
+ "firmware_file": "MarlinUltimaker2extended-plus.hex"
},
"overrides": {
diff --git a/resources/definitions/ultimaker2_go.def.json b/resources/definitions/ultimaker2_go.def.json
index c66fb38fc0..9e374b2c88 100644
--- a/resources/definitions/ultimaker2_go.def.json
+++ b/resources/definitions/ultimaker2_go.def.json
@@ -8,16 +8,15 @@
"quality_definition": "ultimaker2",
"weight": 3,
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2.png",
"platform": "ultimaker2go_platform.obj",
"platform_texture": "Ultimaker2Gobackplate.png",
"platform_offset": [0, 0, 0],
"first_start_actions": [],
- "supported_actions": ["UpgradeFirmware"],
"machine_extruder_trains":
{
"0": "ultimaker2_go_extruder_0"
- }
+ },
+ "firmware_file": "MarlinUltimaker2go.hex"
},
"overrides": {
diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json
index bc4d3a6230..28fd2b71f9 100644
--- a/resources/definitions/ultimaker2_plus.def.json
+++ b/resources/definitions/ultimaker2_plus.def.json
@@ -15,11 +15,12 @@
"has_machine_materials": true,
"has_machine_quality": true,
"first_start_actions": [],
- "supported_actions": ["UpgradeFirmware"],
+ "supported_actions": [],
"machine_extruder_trains":
{
"0": "ultimaker2_plus_extruder_0"
- }
+ },
+ "firmware_file": "MarlinUltimaker2plus.hex"
},
"overrides": {
diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json
index 08fe01a76b..72756de2a5 100644
--- a/resources/definitions/ultimaker3.def.json
+++ b/resources/definitions/ultimaker3.def.json
@@ -13,7 +13,6 @@
"has_machine_quality": true,
"has_materials": true,
"has_machine_materials": true,
- "has_variant_materials": true,
"has_variants": true,
"preferred_variant_name": "AA 0.4",
"preferred_quality_type": "normal",
@@ -25,7 +24,16 @@
},
"first_start_actions": [ "DiscoverUM3Action" ],
"supported_actions": [ "DiscoverUM3Action" ],
- "supports_usb_connection": false
+ "supports_usb_connection": false,
+ "firmware_update_info": {
+ "id": 9066,
+ "check_urls":
+ [
+ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources",
+ "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt"
+ ],
+ "update_url": "https://ultimaker.com/firmware"
+ }
},
@@ -90,7 +98,7 @@
"infill_overlap": { "value": "0" },
"infill_pattern": { "value": "'triangles'" },
"infill_wipe_dist": { "value": "0" },
- "initial_layer_line_width_factor": { "value": "120" },
+ "initial_layer_line_width_factor": { "value": "120" },
"jerk_enabled": { "value": "True" },
"jerk_layer_0": { "value": "jerk_topbottom" },
"jerk_prime_tower": { "value": "math.ceil(jerk_print * 15 / 25)" },
diff --git a/resources/definitions/ultimaker3_extended.def.json b/resources/definitions/ultimaker3_extended.def.json
index 1e6c322c73..68f26969b7 100644
--- a/resources/definitions/ultimaker3_extended.def.json
+++ b/resources/definitions/ultimaker3_extended.def.json
@@ -13,7 +13,6 @@
"platform_offset": [0, 0, 0],
"has_machine_quality": true,
"has_machine_materials": true,
- "has_variant_materials": true,
"has_materials": true,
"has_variants": true,
"preferred_variant_name": "AA 0.4",
@@ -24,7 +23,16 @@
"1": "ultimaker3_extended_extruder_right"
},
"first_start_actions": [ "DiscoverUM3Action" ],
- "supported_actions": [ "DiscoverUM3Action" ]
+ "supported_actions": [ "DiscoverUM3Action" ],
+ "firmware_update_info": {
+ "id": 9511,
+ "check_urls":
+ [
+ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources",
+ "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt"
+ ],
+ "update_url": "https://ultimaker.com/firmware"
+ }
},
"overrides": {
diff --git a/resources/definitions/ultimaker_original.def.json b/resources/definitions/ultimaker_original.def.json
index c961423504..6a978c47cb 100644
--- a/resources/definitions/ultimaker_original.def.json
+++ b/resources/definitions/ultimaker_original.def.json
@@ -8,17 +8,18 @@
"manufacturer": "Ultimaker B.V.",
"weight": 4,
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker.png",
"platform": "ultimaker_platform.stl",
"has_materials": true,
"has_machine_quality": true,
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
- "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"],
+ "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
"machine_extruder_trains":
{
"0": "ultimaker_original_extruder_0"
- }
+ },
+ "firmware_file": "MarlinUltimaker-{baudrate}.hex",
+ "firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}.hex"
},
"overrides": {
diff --git a/resources/definitions/ultimaker_original_dual.def.json b/resources/definitions/ultimaker_original_dual.def.json
index 55eddba85f..999650aa28 100644
--- a/resources/definitions/ultimaker_original_dual.def.json
+++ b/resources/definitions/ultimaker_original_dual.def.json
@@ -8,7 +8,6 @@
"manufacturer": "Ultimaker B.V.",
"weight": 4,
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker.png",
"platform": "ultimaker_platform.stl",
"has_materials": true,
"has_machine_quality": true,
@@ -19,8 +18,10 @@
"0": "ultimaker_original_dual_1st",
"1": "ultimaker_original_dual_2nd"
},
+ "firmware_file": "MarlinUltimaker-{baudrate}-dual.hex",
+ "firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}-dual.hex",
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
- "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"]
+ "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"]
},
"overrides": {
diff --git a/resources/definitions/ultimaker_original_plus.def.json b/resources/definitions/ultimaker_original_plus.def.json
index 71aa53b2bf..bdb8a3d788 100644
--- a/resources/definitions/ultimaker_original_plus.def.json
+++ b/resources/definitions/ultimaker_original_plus.def.json
@@ -7,16 +7,17 @@
"manufacturer": "Ultimaker B.V.",
"weight": 4,
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker.png",
"platform": "ultimaker2_platform.obj",
"platform_texture": "UltimakerPlusbackplate.png",
"quality_definition": "ultimaker_original",
"first_start_actions": ["UMOCheckup", "BedLevel"],
- "supported_actions": ["UMOCheckup", "BedLevel", "UpgradeFirmware"],
+ "supported_actions": ["UMOCheckup", "BedLevel"],
"machine_extruder_trains":
{
"0": "ultimaker_original_plus_extruder_0"
- }
+ },
+ "firmware_file": "MarlinUltimaker-UMOP-{baudrate}.hex",
+ "firmware_hbk_file": "MarlinUltimaker-UMOP-{baudrate}.hex"
},
"overrides": {
diff --git a/resources/definitions/ultimaker_s5.def.json b/resources/definitions/ultimaker_s5.def.json
index f6971d0da3..310765dbc3 100644
--- a/resources/definitions/ultimaker_s5.def.json
+++ b/resources/definitions/ultimaker_s5.def.json
@@ -15,7 +15,6 @@
"has_machine_quality": true,
"has_materials": true,
"has_machine_materials": true,
- "has_variant_materials": true,
"has_variant_buildplates": true,
"has_variants": true,
"preferred_variant_name": "AA 0.4",
@@ -31,7 +30,12 @@
"first_start_actions": [ "DiscoverUM3Action" ],
"supported_actions": [ "DiscoverUM3Action" ],
"supports_usb_connection": false,
- "weight": -1
+ "weight": -1,
+ "firmware_update_info": {
+ "id": 9051,
+ "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"],
+ "update_url": "https://ultimaker.com/firmware"
+ }
},
"overrides": {
@@ -64,7 +68,7 @@
"machine_end_gcode": { "default_value": "" },
"prime_tower_position_x": { "default_value": 345 },
"prime_tower_position_y": { "default_value": 222.5 },
- "prime_blob_enable": { "enabled": true },
+ "prime_blob_enable": { "enabled": true, "default_value": false },
"speed_travel":
{
@@ -128,6 +132,7 @@
"retraction_min_travel": { "value": "5" },
"retraction_prime_speed": { "value": "15" },
"skin_overlap": { "value": "10" },
+ "speed_equalize_flow_enabled": { "value": "True" },
"speed_layer_0": { "value": "20" },
"speed_prime_tower": { "value": "speed_topbottom" },
"speed_print": { "value": "35" },
@@ -146,6 +151,7 @@
"switch_extruder_prime_speed": { "value": "15" },
"switch_extruder_retraction_amount": { "value": "8" },
"top_bottom_thickness": { "value": "1" },
+ "travel_avoid_supports": { "value": "True" },
"travel_avoid_distance": { "value": "3 if extruders_enabled_count > 1 else machine_nozzle_tip_outer_diameter / 2 * 1.5" },
"wall_0_inset": { "value": "0" },
"wall_line_width_x": { "value": "round(line_width * 0.3 / 0.35, 2)" },
diff --git a/resources/definitions/uni_print_3d.def.json b/resources/definitions/uni_print_3d.def.json
index 1612c1bf80..427177176a 100644
--- a/resources/definitions/uni_print_3d.def.json
+++ b/resources/definitions/uni_print_3d.def.json
@@ -26,11 +26,6 @@
"machine_center_is_zero": { "default_value": true },
"machine_nozzle_heat_up_speed": { "default_value": 2.0 },
"machine_nozzle_cool_down_speed": { "default_value": 2.0 },
- "machine_head_shape_min_x": { "default_value": 75 },
- "machine_head_shape_min_y": { "default_value": 18 },
- "machine_head_shape_max_x": { "default_value": 18 },
- "machine_head_shape_max_y": { "default_value": 35 },
- "machine_nozzle_gantry_distance": { "default_value": 55 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
diff --git a/resources/definitions/uniqbot_one.def.json b/resources/definitions/uniqbot_one.def.json
index 396e9687b8..5a33500b75 100644
--- a/resources/definitions/uniqbot_one.def.json
+++ b/resources/definitions/uniqbot_one.def.json
@@ -6,7 +6,6 @@
"author": "Unimatech",
"manufacturer": "Unimatech",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2.png",
"machine_extruder_trains":
{
"0": "uniqbot_one_extruder_0"
diff --git a/resources/definitions/vertex_delta_k8800.def.json b/resources/definitions/vertex_delta_k8800.def.json
index 7059c2e7f0..df24bd84fb 100644
--- a/resources/definitions/vertex_delta_k8800.def.json
+++ b/resources/definitions/vertex_delta_k8800.def.json
@@ -30,27 +30,9 @@
"machine_shape": {
"default_value": "elliptic"
},
- "machine_head_shape_min_x": {
- "default_value": 0
- },
- "machine_head_shape_min_y": {
- "default_value": 0
- },
- "machine_head_shape_max_x": {
- "default_value": 0
- },
- "machine_head_shape_max_y": {
- "default_value": 0
- },
"gantry_height": {
"default_value": 0
},
- "machine_nozzle_offset_x_1": {
- "default_value": 0
- },
- "machine_nozzle_offset_y_1": {
- "default_value": 0
- },
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
diff --git a/resources/definitions/vertex_k8400.def.json b/resources/definitions/vertex_k8400.def.json
index 0166729951..a3a3777547 100644
--- a/resources/definitions/vertex_k8400.def.json
+++ b/resources/definitions/vertex_k8400.def.json
@@ -6,7 +6,6 @@
"visible": true,
"manufacturer": "Velleman",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "Vertex_build_panel.stl",
"platform_offset": [0, -3, 0],
"supports_usb_connection": true,
diff --git a/resources/definitions/vertex_k8400_dual.def.json b/resources/definitions/vertex_k8400_dual.def.json
index b22dabaa94..c7706135bd 100644
--- a/resources/definitions/vertex_k8400_dual.def.json
+++ b/resources/definitions/vertex_k8400_dual.def.json
@@ -6,7 +6,6 @@
"visible": true,
"manufacturer": "Velleman",
"file_formats": "text/x-gcode",
- "icon": "icon_ultimaker2",
"platform": "Vertex_build_panel.stl",
"platform_offset": [0, -3, 0],
"machine_extruder_trains": {
diff --git a/resources/definitions/wanhao_d4s.def.json b/resources/definitions/wanhao_d4s.def.json
index 1ae16a9d56..c1807923c6 100644
--- a/resources/definitions/wanhao_d4s.def.json
+++ b/resources/definitions/wanhao_d4s.def.json
@@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
- "icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_225_145_platform.obj",
"platform_texture": "Wanhaobackplate.png",
@@ -40,10 +39,10 @@
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
- "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
+ "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..."
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
+ "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
}
}
}
diff --git a/resources/definitions/wanhao_d6.def.json b/resources/definitions/wanhao_d6.def.json
index 6164f4d016..c8a690d02c 100644
--- a/resources/definitions/wanhao_d6.def.json
+++ b/resources/definitions/wanhao_d6.def.json
@@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
- "icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_200_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",
@@ -18,9 +17,6 @@
0,
-28,
0
- ],
- "supported_actions": [
- "UpgradeFirmware"
]
},
"overrides": {
@@ -46,10 +42,10 @@
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
- "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
+ "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..."
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
+ "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
}
}
}
diff --git a/resources/definitions/wanhao_d6_plus.def.json b/resources/definitions/wanhao_d6_plus.def.json
index 04cb6fae9f..b3b5ed9b0a 100644
--- a/resources/definitions/wanhao_d6_plus.def.json
+++ b/resources/definitions/wanhao_d6_plus.def.json
@@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
- "icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_200_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",
@@ -40,10 +39,10 @@
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
- "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
+ "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..."
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
+ "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
}
}
}
diff --git a/resources/definitions/wanhao_d9.def.json b/resources/definitions/wanhao_d9.def.json
new file mode 100644
index 0000000000..4e368f970f
--- /dev/null
+++ b/resources/definitions/wanhao_d9.def.json
@@ -0,0 +1,41 @@
+{
+ "version": 2,
+ "name": "Wanhao Duplicator 9",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "manufacturer": "Wanhao",
+ "file_formats": "text/x-gcode",
+ "has_materials": true,
+ "platform": "wanhao_300_300_platform.obj",
+ "platform_texture": "Wanhaobackplate.png",
+ "machine_extruder_trains": {
+ "0": "wanhao_d9_extruder_0"
+ },
+ "platform_offset": [ 0, -55, 0]
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Wanhao Duplicator 9" },
+ "machine_width": { "default_value": 300 },
+ "machine_height": { "default_value": 400 },
+ "machine_depth": { "default_value": 300 },
+ "machine_heated_bed": { "default_value": true },
+ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
+ "machine_start_gcode": {
+ "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
+ },
+ "machine_end_gcode": {
+ "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
+ },
+ "support_angle": { "default_value": 60 },
+ "support_enable": { "default_value": true },
+ "layer_height_0": { "default_value": 0.15 },
+ "top_thickness": { "default_value": 0.6 },
+ "material_print_temperature": { "default_value": 190 },
+ "layer_height": { "default_value": 0.2 },
+ "speed_print": { "default_value": 30 },
+ "adhesion_type": { "default_value": "raft" },
+ "support_z_distance": { "default_value": 0.22 }
+ }
+}
diff --git a/resources/definitions/wanhao_duplicator5S.def.json b/resources/definitions/wanhao_duplicator5S.def.json
index 1ccc867876..b27a13fda8 100644
--- a/resources/definitions/wanhao_duplicator5S.def.json
+++ b/resources/definitions/wanhao_duplicator5S.def.json
@@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
- "icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_300_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",
@@ -43,10 +42,10 @@
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
- "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
+ "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..."
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
+ "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
}
}
}
diff --git a/resources/definitions/wanhao_duplicator5Smini.def.json b/resources/definitions/wanhao_duplicator5Smini.def.json
index 774360f41e..e3ef0b92fe 100644
--- a/resources/definitions/wanhao_duplicator5Smini.def.json
+++ b/resources/definitions/wanhao_duplicator5Smini.def.json
@@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
- "icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_300_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",
@@ -40,10 +39,10 @@
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
- "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
+ "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..."
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
+ "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
}
}
}
diff --git a/resources/definitions/wanhao_i3.def.json b/resources/definitions/wanhao_i3.def.json
index c349259cad..42b19c8748 100644
--- a/resources/definitions/wanhao_i3.def.json
+++ b/resources/definitions/wanhao_i3.def.json
@@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
- "icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_200_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",
@@ -40,10 +39,10 @@
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
- "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
+ "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..."
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
+ "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
}
}
}
diff --git a/resources/definitions/wanhao_i3mini.def.json b/resources/definitions/wanhao_i3mini.def.json
index 4531483459..0c70391c27 100644
--- a/resources/definitions/wanhao_i3mini.def.json
+++ b/resources/definitions/wanhao_i3mini.def.json
@@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
- "icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_110_110_platform.obj",
"platform_texture": "Wanhaobackplate.png",
@@ -40,10 +39,10 @@
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
- "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
+ "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..."
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
+ "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
}
}
}
diff --git a/resources/definitions/wanhao_i3plus.def.json b/resources/definitions/wanhao_i3plus.def.json
index 5338fbeea2..e454a40ae1 100644
--- a/resources/definitions/wanhao_i3plus.def.json
+++ b/resources/definitions/wanhao_i3plus.def.json
@@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
- "icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_200_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",
@@ -40,10 +39,10 @@
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
- "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
+ "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..."
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
+ "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
}
}
}
diff --git a/resources/definitions/z-bolt_classic.def.json b/resources/definitions/z-bolt_classic.def.json
new file mode 100644
index 0000000000..d294de473a
--- /dev/null
+++ b/resources/definitions/z-bolt_classic.def.json
@@ -0,0 +1,59 @@
+{
+ "version": 2,
+ "name": "Z-Bolt Classic",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Z-Bolt",
+ "manufacturer": "Z-Bolt Co.",
+ "file_formats": "text/x-gcode",
+ "platform": "z-bolt_classic_platform.stl",
+ "machine_extruder_trains":
+ {
+ "0": "z-bolt_extruder_0"
+ }
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Z-Bolt Classic" },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "machine_width": {
+ "default_value": 200
+ },
+ "machine_height": {
+ "default_value": 240
+ },
+ "machine_depth": {
+ "default_value": 200
+ },
+ "machine_center_is_zero": {
+ "default_value": false
+ },
+ "machine_head_with_fans_polygon":
+ {
+ "default_value": [
+ [ -75, 35 ],
+ [ -75, -18 ],
+ [ 18, 35 ],
+ [ 18, -18 ]
+ ]
+ },
+ "gantry_height": {
+ "default_value": 55
+ },
+ "machine_use_extruder_offset_to_offset_coords": {
+ "default_value": true
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "machine_start_gcode": {
+ "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E6 ;extrude 6 mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."
+ },
+ "machine_end_gcode": {
+ "default_value": "'M104 S0 ;extruder heater off' + ('\nM140 S0 ;heated bed heater off' if machine_heated_bed else '') + '\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 Z0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning'"
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/definitions/z-bolt_plus.def.json b/resources/definitions/z-bolt_plus.def.json
new file mode 100644
index 0000000000..57331df4c6
--- /dev/null
+++ b/resources/definitions/z-bolt_plus.def.json
@@ -0,0 +1,59 @@
+{
+ "version": 2,
+ "name": "Z-Bolt Plus",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Z-Bolt",
+ "manufacturer": "Z-Bolt Co.",
+ "file_formats": "text/x-gcode",
+ "platform": "z-bolt_plus_platform.stl",
+ "machine_extruder_trains":
+ {
+ "0": "z-bolt_extruder_0"
+ }
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Z-Bolt Plus" },
+ "machine_heated_bed": {
+ "default_value": true
+ },
+ "machine_width": {
+ "default_value": 300
+ },
+ "machine_height": {
+ "default_value": 335
+ },
+ "machine_depth": {
+ "default_value": 200
+ },
+ "machine_center_is_zero": {
+ "default_value": false
+ },
+ "machine_head_with_fans_polygon":
+ {
+ "default_value": [
+ [ -75, 35 ],
+ [ -75, -18 ],
+ [ 18, 35 ],
+ [ 18, -18 ]
+ ]
+ },
+ "gantry_height": {
+ "default_value": 55
+ },
+ "machine_use_extruder_offset_to_offset_coords": {
+ "default_value": true
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "machine_start_gcode": {
+ "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E6 ;extrude 6 mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."
+ },
+ "machine_end_gcode": {
+ "default_value": "'M104 S0 ;extruder heater off' + ('\nM140 S0 ;heated bed heater off' if machine_heated_bed else '') + '\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 Z0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning'"
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/extruders/alfawise_u20_extruder_0.def.json b/resources/extruders/alfawise_u20_extruder_0.def.json
new file mode 100644
index 0000000000..2fbe3f1772
--- /dev/null
+++ b/resources/extruders/alfawise_u20_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "alfawise_u20_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "alfawise_u20",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
\ No newline at end of file
diff --git a/resources/extruders/alfawise_u30_extruder_0.def.json b/resources/extruders/alfawise_u30_extruder_0.def.json
new file mode 100644
index 0000000000..37f59eb567
--- /dev/null
+++ b/resources/extruders/alfawise_u30_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "alfawise_u30_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "alfawise_u30",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/alya3dp_extruder_0.def.json b/resources/extruders/alya3dp_extruder_0.def.json
index e34db5dfbf..3676f01ad2 100644
--- a/resources/extruders/alya3dp_extruder_0.def.json
+++ b/resources/extruders/alya3dp_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/anycubic_4max_extruder_0.def.json b/resources/extruders/anycubic_4max_extruder_0.def.json
new file mode 100644
index 0000000000..5c2ab8d479
--- /dev/null
+++ b/resources/extruders/anycubic_4max_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "anycubic_4max_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "anycubic_4max",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/bibo2_dual_extruder_0.def.json b/resources/extruders/bibo2_dual_extruder_0.def.json
new file mode 100644
index 0000000000..f83801fa0c
--- /dev/null
+++ b/resources/extruders/bibo2_dual_extruder_0.def.json
@@ -0,0 +1,46 @@
+{
+ "id": "BIBO1",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "BIBO2 dual",
+ "position": "0"
+ },
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 0,
+ "maximum_value": "1"
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "machine_nozzle_size": {
+ "default_value": 0.4
+ },
+ "machine_nozzle_offset_x": {
+ "default_value": 0.0
+ },
+ "machine_nozzle_offset_y": {
+ "default_value": 0.0
+ },
+ "machine_extruder_start_pos_abs": {
+ "default_value": true
+ },
+ "machine_extruder_start_pos_x": {
+ "value": "prime_tower_position_x"
+ },
+ "machine_extruder_start_pos_y": {
+ "value": "prime_tower_position_y"
+ },
+ "machine_extruder_end_pos_abs": {
+ "default_value": true
+ },
+ "machine_extruder_end_pos_x": {
+ "value": "prime_tower_position_x"
+ },
+ "machine_extruder_end_pos_y": {
+ "value": "prime_tower_position_y"
+ }
+ }
+}
diff --git a/resources/extruders/bibo2_dual_extruder_1.def.json b/resources/extruders/bibo2_dual_extruder_1.def.json
new file mode 100644
index 0000000000..5f479ba54b
--- /dev/null
+++ b/resources/extruders/bibo2_dual_extruder_1.def.json
@@ -0,0 +1,46 @@
+{
+ "id": "BIBO2",
+ "version": 2,
+ "name": "Extruder 2",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "BIBO2 dual",
+ "position": "1"
+ },
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 1,
+ "maximum_value": "1"
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "machine_nozzle_size": {
+ "default_value": 0.4
+ },
+ "machine_nozzle_offset_x": {
+ "default_value": 0.0
+ },
+ "machine_nozzle_offset_y": {
+ "default_value": 0.0
+ },
+ "machine_extruder_start_pos_abs": {
+ "default_value": true
+ },
+ "machine_extruder_start_pos_x": {
+ "value": "prime_tower_position_x"
+ },
+ "machine_extruder_start_pos_y": {
+ "value": "prime_tower_position_y"
+ },
+ "machine_extruder_end_pos_abs": {
+ "default_value": true
+ },
+ "machine_extruder_end_pos_x": {
+ "value": "prime_tower_position_x"
+ },
+ "machine_extruder_end_pos_y": {
+ "value": "prime_tower_position_y"
+ }
+ }
+}
diff --git a/resources/extruders/builder_premium_large_front.def.json b/resources/extruders/builder_premium_large_front.def.json
index 059f7ef8a7..4834bc8fd9 100644
--- a/resources/extruders/builder_premium_large_front.def.json
+++ b/resources/extruders/builder_premium_large_front.def.json
@@ -16,13 +16,13 @@
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"material_diameter": { "default_value": 1.75 },
-
- "machine_extruder_start_pos_abs": { "default_value": true },
+
+ "machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" },
- "extruder_prime_pos_abs": { "default_value": true }
+ "extruder_prime_pos_abs": { "default_value": true }
}
}
diff --git a/resources/extruders/builder_premium_large_rear.def.json b/resources/extruders/builder_premium_large_rear.def.json
index 769178a8b4..f257749ea4 100644
--- a/resources/extruders/builder_premium_large_rear.def.json
+++ b/resources/extruders/builder_premium_large_rear.def.json
@@ -20,9 +20,9 @@
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
- "machine_extruder_end_pos_abs": { "default_value": true },
+ "machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" },
- "extruder_prime_pos_abs": { "default_value": true }
+ "extruder_prime_pos_abs": { "default_value": true }
}
}
diff --git a/resources/extruders/builder_premium_medium_front.def.json b/resources/extruders/builder_premium_medium_front.def.json
index bd735fbe25..05dcb3d49f 100644
--- a/resources/extruders/builder_premium_medium_front.def.json
+++ b/resources/extruders/builder_premium_medium_front.def.json
@@ -16,13 +16,13 @@
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"material_diameter": { "default_value": 1.75 },
-
- "machine_extruder_start_pos_abs": { "default_value": true },
+
+ "machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" },
- "extruder_prime_pos_abs": { "default_value": true }
+ "extruder_prime_pos_abs": { "default_value": true }
}
}
diff --git a/resources/extruders/builder_premium_medium_rear.def.json b/resources/extruders/builder_premium_medium_rear.def.json
index 59e688ff71..3461e07f09 100644
--- a/resources/extruders/builder_premium_medium_rear.def.json
+++ b/resources/extruders/builder_premium_medium_rear.def.json
@@ -20,9 +20,9 @@
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
- "machine_extruder_end_pos_abs": { "default_value": true },
+ "machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" },
- "extruder_prime_pos_abs": { "default_value": true }
+ "extruder_prime_pos_abs": { "default_value": true }
}
}
diff --git a/resources/extruders/builder_premium_small_front.def.json b/resources/extruders/builder_premium_small_front.def.json
index 17fb914a42..7a1c352c73 100644
--- a/resources/extruders/builder_premium_small_front.def.json
+++ b/resources/extruders/builder_premium_small_front.def.json
@@ -16,13 +16,13 @@
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"material_diameter": { "default_value": 1.75 },
-
- "machine_extruder_start_pos_abs": { "default_value": true },
+
+ "machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" },
- "extruder_prime_pos_abs": { "default_value": true }
+ "extruder_prime_pos_abs": { "default_value": true }
}
}
diff --git a/resources/extruders/builder_premium_small_rear.def.json b/resources/extruders/builder_premium_small_rear.def.json
index 70a2dbf1aa..7085236a5c 100644
--- a/resources/extruders/builder_premium_small_rear.def.json
+++ b/resources/extruders/builder_premium_small_rear.def.json
@@ -20,9 +20,9 @@
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
- "machine_extruder_end_pos_abs": { "default_value": true },
+ "machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" },
- "extruder_prime_pos_abs": { "default_value": true }
+ "extruder_prime_pos_abs": { "default_value": true }
}
}
diff --git a/resources/extruders/cocoon_create_modelmaker_extruder_0.def.json b/resources/extruders/cocoon_create_modelmaker_extruder_0.def.json
new file mode 100644
index 0000000000..26d847483d
--- /dev/null
+++ b/resources/extruders/cocoon_create_modelmaker_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "cocoon_create_modelmaker_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "cocoon_create_modelmaker",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/cr-x_extruder_0.def.json b/resources/extruders/cr-x_extruder_0.def.json
new file mode 100644
index 0000000000..8135815afb
--- /dev/null
+++ b/resources/extruders/cr-x_extruder_0.def.json
@@ -0,0 +1,27 @@
+{
+ "id": "cr-x_extruder_0",
+ "version": 2,
+ "name": "Left Extruder",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "Creality CR-X",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 0,
+ "maximum_value": "1"
+ },
+ "machine_nozzle_offset_x": { "default_value": 0.0 },
+ "machine_nozzle_offset_y": { "default_value": 0.0 },
+ "material_diameter": { "default_value": 1.75 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "machine_extruder_start_code": {
+ "default_value": "\nT0 ;switch to extruder 1\nG92 E0 ;reset extruder distance\nG1 F2000 E93 ;load filament\nG92 E0 ;reset extruder distance\nM104 S{material_print_temperature}"
+ },
+ "machine_extruder_end_code": {
+ "default_value": "\nG92 E0 ;reset extruder distance\nG1 F800 E-5 ;short retract\nG1 F2400 X295 Y265 ;move near prime tower\nG1 F2000 E-93 ;long retract for filament removal\nG92 E0 ;reset extruder distance\nG90"
+ }
+ }
+}
diff --git a/resources/extruders/cr-x_extruder_1.def.json b/resources/extruders/cr-x_extruder_1.def.json
new file mode 100644
index 0000000000..9313f2ea78
--- /dev/null
+++ b/resources/extruders/cr-x_extruder_1.def.json
@@ -0,0 +1,27 @@
+{
+ "id": "cr-x_extruder_1",
+ "version": 2,
+ "name": "Right Extruder",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "Creality CR-X",
+ "position": "1"
+ },
+
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 1,
+ "maximum_value": "1"
+ },
+ "machine_nozzle_offset_x": { "default_value": 0.0 },
+ "machine_nozzle_offset_y": { "default_value": 0.0 },
+ "material_diameter": { "default_value": 1.75 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "machine_extruder_start_code": {
+ "default_value": "\nT1 ;switch to extruder 2\nG92 E0 ;reset extruder distance\nG1 F2000 E93 ;load filament\nG92 E0 ;reset extruder distance\nM104 S{material_print_temperature}"
+ },
+ "machine_extruder_end_code": {
+ "default_value": "\nG92 E0 ;reset extruder distance\nG1 F800 E-5 ;short retract\nG1 F2400 X295 Y265 ;move near prime tower\nG1 F2000 E-93 ;long retract for filament removal\nG92 E0 ;reset extruder distance\nG90"
+ }
+ }
+}
diff --git a/resources/extruders/creality_cr10s4_extruder_0.def.json b/resources/extruders/creality_cr10s4_extruder_0.def.json
index 9afe1cee35..8a40c6431f 100644
--- a/resources/extruders/creality_cr10s4_extruder_0.def.json
+++ b/resources/extruders/creality_cr10s4_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/creality_cr10s5_extruder_0.def.json b/resources/extruders/creality_cr10s5_extruder_0.def.json
index fed86eb2b5..98b701ae2e 100644
--- a/resources/extruders/creality_cr10s5_extruder_0.def.json
+++ b/resources/extruders/creality_cr10s5_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/creality_ender3_extruder_0.def.json b/resources/extruders/creality_ender3_extruder_0.def.json
new file mode 100644
index 0000000000..431366c777
--- /dev/null
+++ b/resources/extruders/creality_ender3_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "creality_ender3_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "creality_ender3",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/creatable_d3_extruder_0.def.json b/resources/extruders/creatable_d3_extruder_0.def.json
new file mode 100644
index 0000000000..7d45bb8e8a
--- /dev/null
+++ b/resources/extruders/creatable_d3_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "creatable_d3_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "Creatable_D3",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/cubicon_3dp_110f_extruder_0.def.json b/resources/extruders/cubicon_3dp_110f_extruder_0.def.json
new file mode 100644
index 0000000000..9c854fd2a1
--- /dev/null
+++ b/resources/extruders/cubicon_3dp_110f_extruder_0.def.json
@@ -0,0 +1,27 @@
+{
+ "id": "cubicon_3dp_110f_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "cubicon_3dp_110f",
+ "position": "0"
+ },
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 0
+ },
+ "machine_nozzle_size": {
+ "default_value": 0.4
+ },
+ "machine_nozzle_offset_x": {
+ "default_value": -15
+ },
+ "machine_nozzle_offset_y": {
+ "default_value": -5
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/extruders/cubicon_3dp_210f_extruder_0.def.json b/resources/extruders/cubicon_3dp_210f_extruder_0.def.json
new file mode 100644
index 0000000000..8a8573760a
--- /dev/null
+++ b/resources/extruders/cubicon_3dp_210f_extruder_0.def.json
@@ -0,0 +1,27 @@
+{
+ "id": "cubicon_3dp_210f_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "cubicon_3dp_210f",
+ "position": "0"
+ },
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 0
+ },
+ "machine_nozzle_size": {
+ "default_value": 0.4
+ },
+ "machine_nozzle_offset_x": {
+ "default_value": -7.25
+ },
+ "machine_nozzle_offset_y": {
+ "default_value": -5.82
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/extruders/cubicon_3dp_310f_extruder_0.def.json b/resources/extruders/cubicon_3dp_310f_extruder_0.def.json
new file mode 100644
index 0000000000..4edbbd5a6c
--- /dev/null
+++ b/resources/extruders/cubicon_3dp_310f_extruder_0.def.json
@@ -0,0 +1,27 @@
+{
+ "id": "cubicon_3dp_310f_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "cubicon_3dp_310f",
+ "position": "0"
+ },
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 0
+ },
+ "machine_nozzle_size": {
+ "default_value": 0.4
+ },
+ "machine_nozzle_offset_x": {
+ "default_value": -15
+ },
+ "machine_nozzle_offset_y": {
+ "default_value": -5
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/extruders/dagoma_neva_magis_extruder_0.def.json b/resources/extruders/dagoma_magis_extruder_0.def.json
similarity index 90%
rename from resources/extruders/dagoma_neva_magis_extruder_0.def.json
rename to resources/extruders/dagoma_magis_extruder_0.def.json
index 0d5fd3c9b4..0a5850f2ed 100644
--- a/resources/extruders/dagoma_neva_magis_extruder_0.def.json
+++ b/resources/extruders/dagoma_magis_extruder_0.def.json
@@ -3,7 +3,7 @@
"name": "Extruder 1",
"inherits": "fdmextruder",
"metadata": {
- "machine": "dagoma_neva_magis",
+ "machine": "dagoma_magis",
"position": "0"
},
diff --git a/resources/extruders/deltabot_extruder_0.def.json b/resources/extruders/deltabot_extruder_0.def.json
index 43fce74fa5..e13d6a6ee3 100644
--- a/resources/extruders/deltabot_extruder_0.def.json
+++ b/resources/extruders/deltabot_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.5 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/deltacomb_extruder_0.def.json b/resources/extruders/deltacomb_extruder_0.def.json
old mode 100644
new mode 100755
diff --git a/resources/extruders/gmax15plus_dual_extruder_0.def.json b/resources/extruders/gmax15plus_dual_extruder_0.def.json
index b490f4a40e..d3146a0576 100644
--- a/resources/extruders/gmax15plus_dual_extruder_0.def.json
+++ b/resources/extruders/gmax15plus_dual_extruder_0.def.json
@@ -15,10 +15,10 @@
},
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
- "machine_nozzle_size": { "default_value": 0.5 },
+ "machine_nozzle_size": { "default_value": 0.5 },
"material_diameter": { "default_value": 1.75 },
-
- "machine_extruder_start_pos_abs": { "default_value": true },
+
+ "machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": 40 },
"machine_extruder_start_pos_y": { "value": 210 },
"machine_extruder_end_pos_abs": { "default_value": true },
diff --git a/resources/extruders/gmax15plus_dual_extruder_1.def.json b/resources/extruders/gmax15plus_dual_extruder_1.def.json
index ad3c628d6f..7b7354d794 100644
--- a/resources/extruders/gmax15plus_dual_extruder_1.def.json
+++ b/resources/extruders/gmax15plus_dual_extruder_1.def.json
@@ -15,10 +15,10 @@
},
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
- "machine_nozzle_size": { "default_value": 0.5 },
+ "machine_nozzle_size": { "default_value": 0.5 },
"material_diameter": { "default_value": 1.75 },
-
- "machine_extruder_start_pos_abs": { "default_value": true },
+
+ "machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": 40 },
"machine_extruder_start_pos_y": { "value": 210 },
"machine_extruder_end_pos_abs": { "default_value": true },
diff --git a/resources/extruders/grr_neo_extruder_0.def.json b/resources/extruders/grr_neo_extruder_0.def.json
index 9fe86d9eed..6d76c90796 100644
--- a/resources/extruders/grr_neo_extruder_0.def.json
+++ b/resources/extruders/grr_neo_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.5 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/jgaurora_a1_extruder_0.def.json b/resources/extruders/jgaurora_a1_extruder_0.def.json
new file mode 100644
index 0000000000..71742b734a
--- /dev/null
+++ b/resources/extruders/jgaurora_a1_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "jgaurora_a1_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "jgaurora_a1",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/jgaurora_a5_extruder_0.def.json b/resources/extruders/jgaurora_a5_extruder_0.def.json
new file mode 100644
index 0000000000..fbc6ba77e6
--- /dev/null
+++ b/resources/extruders/jgaurora_a5_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "jgaurora_a5_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "jgaurora_a5",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/jgaurora_z_603s_extruder_0.def.json b/resources/extruders/jgaurora_z_603s_extruder_0.def.json
new file mode 100644
index 0000000000..987425b28a
--- /dev/null
+++ b/resources/extruders/jgaurora_z_603s_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "jgaurora_z_603s_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "jgaurora_z_603s",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/kupido_extruder_0.def.json b/resources/extruders/kupido_extruder_0.def.json
index d93395e667..ef988d4fde 100644
--- a/resources/extruders/kupido_extruder_0.def.json
+++ b/resources/extruders/kupido_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/makeR_pegasus_extruder_0.def.json b/resources/extruders/makeR_pegasus_extruder_0.def.json
index 8d2a98340a..e37891abde 100644
--- a/resources/extruders/makeR_pegasus_extruder_0.def.json
+++ b/resources/extruders/makeR_pegasus_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/maker_starter_extruder_0.def.json b/resources/extruders/maker_starter_extruder_0.def.json
index 5c60e536b7..ee94250248 100644
--- a/resources/extruders/maker_starter_extruder_0.def.json
+++ b/resources/extruders/maker_starter_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json b/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json
index eef47c9b6f..e4a899d7af 100644
--- a/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json
+++ b/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json b/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json
index e0899304dd..b727cfce1f 100644
--- a/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json
+++ b/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/nwa3d_a5_extruder_0.def.json b/resources/extruders/nwa3d_a5_extruder_0.def.json
new file mode 100644
index 0000000000..5c3cc6a127
--- /dev/null
+++ b/resources/extruders/nwa3d_a5_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "nwa3d_a5_extruder_0",
+ "version": 2,
+ "name": "Regular 0.4mm Nozzle",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "nwa3d_a5",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/peopoly_moai_extruder_0.def.json b/resources/extruders/peopoly_moai_extruder_0.def.json
index 7940002926..bbffd4ac4d 100644
--- a/resources/extruders/peopoly_moai_extruder_0.def.json
+++ b/resources/extruders/peopoly_moai_extruder_0.def.json
@@ -11,6 +11,9 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.067 },
- "material_diameter": { "default_value": 1.75 }
+ "material_diameter": {
+ "enabled": false,
+ "default_value": 1.75
+ }
}
}
diff --git a/resources/extruders/printrbot_play_heated_extruder_0.def.json b/resources/extruders/printrbot_play_heated_extruder_0.def.json
index ba8bc5c34c..0a3eeb3d06 100644
--- a/resources/extruders/printrbot_play_heated_extruder_0.def.json
+++ b/resources/extruders/printrbot_play_heated_extruder_0.def.json
@@ -11,6 +11,6 @@
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
- "material_diameter": { "default_value": 2.85 }
+ "material_diameter": { "default_value": 1.75 }
}
}
diff --git a/resources/extruders/tizyx_evy_extruder_0.def.JSON b/resources/extruders/tizyx_evy_extruder_0.def.JSON
new file mode 100644
index 0000000000..bd3c4c9792
--- /dev/null
+++ b/resources/extruders/tizyx_evy_extruder_0.def.JSON
@@ -0,0 +1,18 @@
+{
+ "id": "tizyx_evy_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "tizyx_evy",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 0,
+ "maximum_value": "0"
+ },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/ultimaker_s5_extruder_left.def.json b/resources/extruders/ultimaker_s5_extruder_left.def.json
index c92873b987..275f60bb31 100644
--- a/resources/extruders/ultimaker_s5_extruder_left.def.json
+++ b/resources/extruders/ultimaker_s5_extruder_left.def.json
@@ -17,10 +17,10 @@
"machine_nozzle_offset_y": { "default_value": 0 },
"machine_extruder_start_pos_abs": { "default_value": true },
- "machine_extruder_start_pos_x": { "default_value": 310 },
+ "machine_extruder_start_pos_x": { "default_value": 330 },
"machine_extruder_start_pos_y": { "default_value": 237 },
"machine_extruder_end_pos_abs": { "default_value": true },
- "machine_extruder_end_pos_x": { "default_value": 310 },
+ "machine_extruder_end_pos_x": { "default_value": 330 },
"machine_extruder_end_pos_y": { "default_value": 237 },
"machine_nozzle_head_distance": { "default_value": 2.7 },
"extruder_prime_pos_x": { "default_value": -3 },
diff --git a/resources/extruders/ultimaker_s5_extruder_right.def.json b/resources/extruders/ultimaker_s5_extruder_right.def.json
index 89d62b89a4..92e08f5cc5 100644
--- a/resources/extruders/ultimaker_s5_extruder_right.def.json
+++ b/resources/extruders/ultimaker_s5_extruder_right.def.json
@@ -17,10 +17,10 @@
"machine_nozzle_offset_y": { "default_value": 0 },
"machine_extruder_start_pos_abs": { "default_value": true },
- "machine_extruder_start_pos_x": { "default_value": 310 },
+ "machine_extruder_start_pos_x": { "default_value": 330 },
"machine_extruder_start_pos_y": { "default_value": 219 },
"machine_extruder_end_pos_abs": { "default_value": true },
- "machine_extruder_end_pos_x": { "default_value": 310 },
+ "machine_extruder_end_pos_x": { "default_value": 330 },
"machine_extruder_end_pos_y": { "default_value": 219 },
"machine_nozzle_head_distance": { "default_value": 4.2 },
"extruder_prime_pos_x": { "default_value": 333 },
diff --git a/resources/extruders/wanhao_d9_extruder_0.def.json b/resources/extruders/wanhao_d9_extruder_0.def.json
new file mode 100644
index 0000000000..76d501e5a2
--- /dev/null
+++ b/resources/extruders/wanhao_d9_extruder_0.def.json
@@ -0,0 +1,16 @@
+{
+ "id": "wanhao_d9_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "wanhao_d9",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
diff --git a/resources/extruders/z-bolt_extruder_0.def.json b/resources/extruders/z-bolt_extruder_0.def.json
new file mode 100644
index 0000000000..70e9f6177c
--- /dev/null
+++ b/resources/extruders/z-bolt_extruder_0.def.json
@@ -0,0 +1,15 @@
+{
+ "id": "z-bolt_extruder_0",
+ "version": 2,
+ "name": "Extruder 1",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": { "default_value": 0 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 }
+ }
+}
\ No newline at end of file
diff --git a/resources/i18n/cura.pot b/resources/i18n/cura.pot
index 45a85a5849..1874604139 100644
--- a/resources/i18n/cura.pot
+++ b/resources/i18n/cura.pot
@@ -1,20 +1,20 @@
-# Cura
-# Copyright (C) 2018 Ultimaker
-# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2018.
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
#
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
-"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0200\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-10-29 15:01+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
-"Language-Team: TEAM\n"
-"Language: xx_XX\n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
@@ -40,6 +40,17 @@ msgctxt "@item:inlistbox"
msgid "G-code File"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:67
+msgctxt "@error:not supported"
+msgid "GCodeWriter does not support non-text mode."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:73
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:89
+msgctxt "@warning:status"
+msgid "Please prepare G-code before exporting."
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
msgctxt "@info:title"
msgid "3D Model Assistant"
@@ -58,102 +69,52 @@ msgid ""
"guide
"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
-msgctxt "@action:button"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:66
-msgctxt "@properties:tooltip"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:86
-msgctxt "@info:status"
-msgid "Connecting to Doodle3D Connect"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:258
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:204
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:398
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:88
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
-#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
-#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:275
-msgctxt "@action:button"
-msgid "Cancel"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:154
-msgctxt "@info:status"
-msgid "Sending data to Doodle3D Connect"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:161
-msgctxt "@info:status"
-msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:175
-msgctxt "@info:status"
-msgid "Storing data on Doodle3D Connect"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:213
-msgctxt "@info:status"
-msgid "File sent to Doodle3D Connect"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@action:button"
-msgid "Open Connect..."
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@info:tooltip"
-msgid "Open the Doodle3D Connect web interface"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:32
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:20
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py:25
+msgctxt "@action"
+msgid "Update Firmware"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:23
msgctxt "@item:inmenu"
msgid "Flatten active settings"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:32
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:35
msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:40
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:32
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:41
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:33
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:42
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:34
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:83
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:69
msgctxt "@info:status"
msgid "Connected via USB"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:92
+msgctxt "@label"
+msgid ""
+"A USB print is in progress, closing Cura will stop this print. Are you sure?"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/install/X3GWriter/__init__.py:15
#: /home/ruben/Projects/Cura/plugins/X3GWriter/__init__.py:15
msgctxt "X3G Writer File Description"
@@ -176,7 +137,12 @@ msgctxt "@item:inlistbox"
msgid "Compressed G-code File"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/GCodeGzWriter.py:38
+msgctxt "@error:not supported"
+msgid "GCodeGzWriter does not support text mode."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:28
msgctxt "@item:inlistbox"
msgid "Ultimaker Format Package"
msgstr ""
@@ -198,7 +164,7 @@ msgid "Save to Removable Drive {0}"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:64
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:133
msgctxt "@info:status"
msgid "There are no file formats available to write with!"
msgstr ""
@@ -237,7 +203,7 @@ msgstr ""
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:137
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:133
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:140
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1592
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1607
msgctxt "@info:title"
msgid "Error"
msgstr ""
@@ -266,8 +232,8 @@ msgstr ""
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:151
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:163
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1582
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1681
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1597
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1695
msgctxt "@info:title"
msgid "Warning"
msgstr ""
@@ -294,115 +260,110 @@ msgctxt "@item:intext"
msgid "Removable Drive"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:70
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:78
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:88
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:71
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:79
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:74
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:89
msgctxt "@properties:tooltip"
msgid "Print over network"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:84
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:87
msgctxt "@info:status"
msgid "Connected over the network."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:87
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:90
msgctxt "@info:status"
msgid ""
"Connected over the network. Please approve the access request on the printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:89
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:92
msgctxt "@info:status"
msgid "Connected over the network. No access to control the printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:94
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:97
msgctxt "@info:status"
msgid ""
"Access to the printer requested. Please approve the request on the printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:97
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:100
msgctxt "@info:title"
msgid "Authentication status"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:99
-msgctxt "@info:status"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:100
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:106
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:110
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:108
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:112
msgctxt "@info:title"
msgid "Authentication Status"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:101
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:103
msgctxt "@action:button"
msgid "Retry"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:104
msgctxt "@info:tooltip"
msgid "Re-send the access request"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:105
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:107
msgctxt "@info:status"
msgid "Access to the printer accepted"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:109
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:111
msgctxt "@info:status"
msgid "No access to print with this printer. Unable to send print job."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:111
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:29
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:33
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:70
msgctxt "@action:button"
msgid "Request Access"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:113
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:28
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:72
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:115
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:34
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:71
msgctxt "@info:tooltip"
msgid "Send access request to the printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:198
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:200
msgctxt "@label"
msgid "Unable to start a new print job."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:200
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:202
msgctxt "@label"
msgid ""
"There is an issue with the configuration of your Ultimaker, which makes it "
"impossible to start the print. Please resolve this issues before continuing."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:206
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:228
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:208
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:230
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:220
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:222
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:222
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:224
msgctxt "@label"
msgid ""
"There is a mismatch between the configuration or calibration of the printer "
@@ -410,40 +371,55 @@ msgid ""
"that are inserted in your printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:249
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:166
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:251
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:199
msgctxt "@info:status"
msgid ""
"Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:256
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:185
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:202
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:258
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:234
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:257
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:186
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:203
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:259
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:219
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:235
msgctxt "@info:title"
msgid "Sending Data"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:321
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:260
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:236
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:381
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:20
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
+#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:279
+msgctxt "@action:button"
+msgid "Cancel"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:323
#, python-brace-format
msgctxt "@info:status"
msgid "No Printcore loaded in slot {slot_number}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:327
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:329
#, python-brace-format
msgctxt "@info:status"
msgid "No material loaded in slot {slot_number}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:350
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:352
#, python-brace-format
msgctxt "@label"
msgid ""
@@ -451,23 +427,23 @@ msgid ""
"{remote_printcore_name}) selected for extruder {extruder_id}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:359
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:361
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:545
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:547
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:547
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:549
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:549
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:551
msgctxt "@label"
msgid ""
"The PrintCores and/or materials on your printer differ from those within "
@@ -475,54 +451,59 @@ msgid ""
"and materials that are inserted in your printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:81
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:91
msgctxt "@info:status"
msgid "Connected over the network"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:303
msgctxt "@info:status"
msgid "Print job was successfully sent to the printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:264
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:305
msgctxt "@info:title"
msgid "Data Sent"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:265
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:306
msgctxt "@action:button"
msgid "View in Monitor"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:353
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:422
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:355
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:424
#, python-brace-format
msgctxt "@info:status"
msgid "The print job '{job_name}' was finished."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:356
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:425
msgctxt "@info:status"
msgid "Print finished"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:20
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py:26
msgctxt "@action"
msgid "Connect via Network"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:13
msgctxt "@item:inmenu"
msgid "Monitor"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:69
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:119
+msgctxt "@info"
+msgid "Could not access update information."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:17
#, python-brace-format
msgctxt ""
"@info Don't translate {machine_name}, since it gets replaced by a printer "
@@ -532,38 +513,33 @@ msgid ""
"update the firmware on your printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:73
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:21
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:76
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:27
msgctxt "@action:button"
msgid "How to update"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:92
-msgctxt "@info"
-msgid "Could not access update information."
-msgstr ""
-
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Layer view"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:113
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:114
msgctxt "@info:title"
msgid "Simulation View"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:27
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:35
msgid "Modify G-Code"
msgstr ""
@@ -577,32 +553,32 @@ msgctxt "@info:tooltip"
msgid "Create a volume in which supports are not printed."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:44
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
msgctxt "@info"
msgid "Cura collects anonymized usage statistics."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:47
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:55
msgctxt "@info:title"
msgid "Collecting Data"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:57
msgctxt "@action:button"
msgid "More info"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:58
msgctxt "@action:tooltip"
msgid "See more information on what data Cura sends."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:60
msgctxt "@action:button"
msgid "Allow"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:53
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:61
msgctxt "@action:tooltip"
msgid ""
"Allow Cura to send anonymized usage statistics to help prioritize future "
@@ -615,18 +591,6 @@ msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
-msgctxt "@item:inlistbox"
-msgid "Blender file"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
-msgctxt "@info:status"
-msgid ""
-"Could not export using \"{}\" quality!\n"
-"Felt back to \"{}\"."
-msgstr ""
-
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "JPG Image"
@@ -652,23 +616,24 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
msgctxt "@info:status"
msgid ""
"Unable to slice with the current material as it is incompatible with the "
"selected machine or configuration."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:344
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:367
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:376
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:363
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:387
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:396
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:405
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:414
msgctxt "@info:title"
msgid "Unable to slice"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:343
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:362
#, python-brace-format
msgctxt "@info:status"
msgid ""
@@ -676,7 +641,7 @@ msgid ""
"errors: {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
#, python-brace-format
msgctxt "@info:status"
msgid ""
@@ -684,13 +649,21 @@ msgid ""
"errors on one or more models: {error_labels}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:375
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:395
msgctxt "@info:status"
msgid ""
"Unable to slice because the prime tower or prime position(s) are invalid."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:385
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:404
+#, python-format
+msgctxt "@info:status"
+msgid ""
+"Unable to slice because there are objects associated with disabled Extruder "
+"%s."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:413
msgctxt "@info:status"
msgid ""
"Nothing to slice because none of the models fit the build volume. Please "
@@ -698,12 +671,12 @@ msgid ""
msgstr ""
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:status"
msgid "Processing Layers"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:title"
msgid "Information"
msgstr ""
@@ -719,29 +692,43 @@ msgid "Configure Per Model Settings"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:175
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:575
msgctxt "@title:tab"
msgid "Recommended"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:177
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:580
msgctxt "@title:tab"
msgid "Custom"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:32
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:28
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:34
msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:199
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:695
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:714
msgctxt "@label"
msgid "Nozzle"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:468
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid ""
+"Project file {0} contains an unknown machine type "
+"{1}. Cannot import the machine. Models will be imported "
+"instead."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:471
+msgctxt "@info:title"
+msgid "Open Project File"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/SolidView/__init__.py:12
msgctxt "@item:inmenu"
msgid "Solid view"
@@ -752,18 +739,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:322
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:470
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:326
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:474
msgctxt "@info:title"
msgid "G-code Details"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:468
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:472
msgctxt "@info:generic"
msgid ""
"Make sure the g-code is suitable for your printer and printer configuration "
@@ -776,27 +763,27 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:26
msgctxt "@item:inlistbox"
msgid "3MF file"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:34
msgctxt "@item:inlistbox"
msgid "Cura Project 3MF file"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/ThreeMFWriter.py:179
+msgctxt "@error:zip"
+msgid "Error writing 3mf file."
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelection.py:17
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelection.py:18
msgctxt "@action"
msgid "Select upgrades"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py:12
-msgctxt "@action"
-msgid "Upgrade Firmware"
-msgstr ""
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py:14
msgctxt "@action"
msgid "Checkup"
@@ -807,79 +794,79 @@ msgctxt "@action"
msgid "Level build plate"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:82
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:83
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:84
msgctxt "@tooltip"
msgid "Skin"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:85
msgctxt "@tooltip"
msgid "Infill"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:86
msgctxt "@tooltip"
msgid "Support Infill"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:87
msgctxt "@tooltip"
msgid "Support Interface"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:88
msgctxt "@tooltip"
msgid "Support"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
msgctxt "@tooltip"
msgid "Skirt"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
msgctxt "@tooltip"
msgid "Travel"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
msgctxt "@tooltip"
msgid "Retractions"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
msgctxt "@tooltip"
msgid "Other"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:229
-msgctxt "@label unknown material"
-msgid "Unknown"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:313
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:310
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:235
+#: /home/ruben/Projects/Cura/cura/API/Account.py:71
+msgctxt "@info:title"
+msgid "Login failed"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:201
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
msgctxt "@title:window"
msgid "File Already Exists"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:236
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:202
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:122
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
@@ -893,19 +880,19 @@ msgctxt "@menuitem"
msgid "Not overridden"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:119
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
msgctxt "@info:status"
msgid ""
"The selected material is incompatible with the selected machine or "
"configuration."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:120
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
msgctxt "@info:title"
msgid "Incompatible Material"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:842
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:866
#, python-format
msgctxt "@info:generic"
msgid ""
@@ -913,7 +900,7 @@ msgid ""
"[%s]"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:844
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:868
msgctxt "@info:title"
msgid "Settings updated"
msgstr ""
@@ -945,8 +932,6 @@ msgid "Export succeeded"
msgstr ""
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:170
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid ""
@@ -956,14 +941,20 @@ msgstr ""
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:190
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "No custom profile to import in file {0}"
msgstr ""
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags !"
+msgid "Failed to import profile from {0}:"
+msgstr ""
+
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:218
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:228
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid ""
"This profile {0} contains incorrect data, could not "
"import it."
@@ -971,47 +962,53 @@ msgstr ""
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:241
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid ""
"The machine defined in profile {0} ({1}) doesn't match "
"with your current machine ({2}), could not import it."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:316
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:312
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Failed to import profile from {0}:"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:315
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:319
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:318
#, python-brace-format
msgctxt "@info:status"
msgid "File {0} does not contain any valid profile."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:322
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:340
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:339
msgctxt "@label"
msgid "Custom profile"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:356
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:355
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:368
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:369
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:60
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:63
#, python-brace-format
msgctxt "@label"
msgid "Group #{group_nr}"
@@ -1038,44 +1035,44 @@ msgctxt "@item:inlistbox"
msgid "All Files (*)"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:544
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:636
msgctxt "@label"
msgid "Custom Material"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:545
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:637
msgctxt "@label"
msgid "Custom"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:80
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:81
msgctxt "@info:status"
msgid ""
"The build volume height has been reduced due to the value of the \"Print "
"Sequence\" setting to prevent the gantry from colliding with printed models."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:82
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:83
msgctxt "@info:title"
msgid "Build Volume"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:98
msgctxt "@info:backup_failed"
msgid "Could not create archive from user data directory: {}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:103
msgctxt "@info:title"
msgid "Backup"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:113
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup without having proper data or meta data."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:123
msgctxt "@info:backup_failed"
msgid ""
"Tried to restore a Cura backup that does not match your current version."
@@ -1087,32 +1084,32 @@ msgid "Multiplying and placing objects"
msgstr ""
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:28
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
msgctxt "@info:title"
msgid "Placing Object"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:96
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:149
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
msgctxt "@info:status"
msgid "Unable to find a location within the build volume for all objects"
msgstr ""
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:30
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:66
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:67
msgctxt "@info:status"
msgid "Finding new location for objects"
msgstr ""
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:34
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:70
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:71
msgctxt "@info:title"
msgid "Finding Location"
msgstr ""
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:97
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:151
msgctxt "@info:title"
msgid "Can't Find Location"
msgstr ""
@@ -1250,22 +1247,22 @@ msgctxt "@action:button"
msgid "Send report"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:328
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:473
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:756
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:775
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:789
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:811
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1023
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1037
#, python-format
msgctxt ""
"@info 'width', 'depth' and 'height' are variable names that must NOT be "
@@ -1273,97 +1270,97 @@ msgctxt ""
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1581
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1596
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1591
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1606
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1680
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1694
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:59
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:61
msgctxt "@title"
msgid "Machine Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:78
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:80
msgctxt "@title:tab"
msgid "Printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:97
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:99
msgctxt "@label"
msgid "Printer Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:108
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:110
msgctxt "@label"
msgid "X (Width)"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:109
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:384
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:418
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:430
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:855
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:111
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:121
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:131
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:237
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:386
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:402
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:428
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:440
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:896
msgctxt "@label"
msgid "mm"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:118
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:120
msgctxt "@label"
msgid "Y (Depth)"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:128
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:130
msgctxt "@label"
msgid "Z (Height)"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:140
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:142
msgctxt "@label"
msgid "Build plate shape"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:149
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:151
msgctxt "@option:check"
msgid "Origin at center"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:157
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:159
msgctxt "@option:check"
msgid "Heated bed"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:168
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:170
msgctxt "@label"
msgid "G-code flavor"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:181
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:183
msgctxt "@label"
msgid "Printhead Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:191
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:193
msgctxt "@label"
msgid "X min"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:192
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:194
msgctxt "@tooltip"
msgid ""
"Distance from the left of the printhead to the center of the nozzle. Used to "
@@ -1371,12 +1368,12 @@ msgid ""
"\"One at a Time\"."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:201
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:203
msgctxt "@label"
msgid "Y min"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:202
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:204
msgctxt "@tooltip"
msgid ""
"Distance from the front of the printhead to the center of the nozzle. Used "
@@ -1384,12 +1381,12 @@ msgid ""
"printing \"One at a Time\"."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:211
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:213
msgctxt "@label"
msgid "X max"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:212
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:214
msgctxt "@tooltip"
msgid ""
"Distance from the right of the printhead to the center of the nozzle. Used "
@@ -1397,12 +1394,12 @@ msgid ""
"printing \"One at a Time\"."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:221
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:223
msgctxt "@label"
msgid "Y max"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:222
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:224
msgctxt "@tooltip"
msgid ""
"Distance from the rear of the printhead to the center of the nozzle. Used to "
@@ -1410,12 +1407,12 @@ msgid ""
"\"One at a Time\"."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:234
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
msgctxt "@label"
msgid "Gantry height"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:238
msgctxt "@tooltip"
msgid ""
"The height difference between the tip of the nozzle and the gantry system (X "
@@ -1423,69 +1420,79 @@ msgid ""
"gantry when printing \"One at a Time\"."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:257
msgctxt "@label"
msgid "Number of Extruders"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:311
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:313
msgctxt "@label"
msgid "Start G-code"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:321
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:323
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very start."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:330
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:332
msgctxt "@label"
msgid "End G-code"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:340
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:342
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very end."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:371
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:373
msgctxt "@label"
msgid "Nozzle Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:385
msgctxt "@label"
msgid "Nozzle size"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:399
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
msgctxt "@label"
msgid "Compatible material diameter"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:403
msgctxt "@tooltip"
msgid ""
"The nominal diameter of filament supported by the printer. The exact "
"diameter will be overridden by the material and/or the profile."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:417
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:427
msgctxt "@label"
msgid "Nozzle offset X"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:429
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:439
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:450
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+msgctxt "@label"
+msgid "Cooling Fan Number"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:452
+msgctxt "@label"
+msgid ""
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:472
msgctxt "@label"
msgid "Extruder Start G-code"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:468
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:490
msgctxt "@label"
msgid "Extruder End G-code"
msgstr ""
@@ -1506,12 +1513,20 @@ msgid ""
"Could not connect to the Cura Package database. Please check your connection."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:38
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:28
msgctxt "@title:tab"
msgid "Plugins"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:75
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:66
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:551
+msgctxt "@title:tab"
+msgid "Materials"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
msgctxt "@label"
msgid "Version"
@@ -1527,8 +1542,14 @@ msgctxt "@label"
msgid "Author"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:97
+msgctxt "@label"
+msgid "Downloads"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:116
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:158
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:258
msgctxt "@label"
msgid "Unknown"
msgstr ""
@@ -1561,17 +1582,59 @@ msgctxt "@action:button"
msgid "Back"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:20
+msgctxt "@title:window"
+msgid "Confirm uninstall"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:50
+msgctxt "@text:window"
+msgid ""
+"You are uninstalling materials and/or profiles that are still in use. "
+"Confirming will reset the following materials/profiles to their defaults."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:51
+msgctxt "@text:window"
+msgid "Materials"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:52
+msgctxt "@text:window"
+msgid "Profiles"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:89
+msgctxt "@action:button"
+msgid "Confirm"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
msgctxt "@info"
msgid "You will need to restart Cura before changes in packages have effect."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:34
msgctxt "@info:button"
msgid "Quit Cura"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Contributions"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Plugins"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:43
+msgctxt "@label"
+msgid "Generic Materials"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:56
msgctxt "@title:tab"
msgid "Installed"
msgstr ""
@@ -1614,12 +1677,12 @@ msgctxt "@action:button"
msgid "Decline"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:23
msgctxt "@label"
msgid "Featured"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:31
msgctxt "@label"
msgid "Compatibility"
msgstr ""
@@ -1629,9 +1692,14 @@ msgctxt "@info"
msgid "Fetching packages..."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:88
msgctxt "@label"
-msgid "Contact"
+msgid "Website"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:94
+msgctxt "@label"
+msgid "Email"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
@@ -1647,48 +1715,96 @@ msgid "Changelog"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:84
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:56
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:464
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:509
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:185
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:53
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:467
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:514
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:121
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:166
#: /home/ruben/Projects/Cura/resources/qml/EngineLog.qml:38
msgctxt "@action:button"
msgid "Close"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:22
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:31
+msgctxt "@title"
+msgid "Update Firmware"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:39
+msgctxt "@label"
+msgid ""
+"Firmware is the piece of software running directly on your 3D printer. This "
+"firmware controls the step motors, regulates the temperature and ultimately "
+"makes your printer work."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:46
+msgctxt "@label"
+msgid ""
+"The firmware shipping with new printers works, but new versions tend to have "
+"more features and improvements."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:58
+msgctxt "@action:button"
+msgid "Automatically upgrade Firmware"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:69
+msgctxt "@action:button"
+msgid "Upload custom Firmware"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:83
+msgctxt "@label"
+msgid ""
+"Firmware can not be updated because there is no connection with the printer."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:91
+msgctxt "@label"
+msgid ""
+"Firmware can not be updated because the connection with the printer does not "
+"support upgrading firmware."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:98
+msgctxt "@title:window"
+msgid "Select custom firmware"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:119
msgctxt "@title:window"
msgid "Firmware Update"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:42
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:143
msgctxt "@label"
msgid "Updating firmware."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:44
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:145
msgctxt "@label"
msgid "Firmware update completed."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:46
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:147
msgctxt "@label"
msgid "Firmware update failed due to an unknown error."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:48
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:149
msgctxt "@label"
msgid "Firmware update failed due to an communication error."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:50
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:151
msgctxt "@label"
msgid "Firmware update failed due to an input/output error."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:52
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:153
msgctxt "@label"
msgid "Firmware update failed due to missing firmware."
msgstr ""
@@ -1698,24 +1814,24 @@ msgctxt "@title:window"
msgid "User Agreement"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:57
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:46
msgctxt "@window:title"
msgid "Existing Connection"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:59
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:48
msgctxt "@message:text"
msgid ""
"This printer/group is already added to Cura. Please select another printer/"
"group."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:76
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:65
msgctxt "@title:window"
msgid "Connect to Networked Printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:86
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:75
msgctxt "@label"
msgid ""
"To print directly to your printer over the network, please make sure your "
@@ -1727,335 +1843,402 @@ msgid ""
"Select your printer from the list below:"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:85
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
msgctxt "@action:button"
msgid "Add"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:95
msgctxt "@action:button"
msgid "Edit"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:128
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:48
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:117
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:132
msgctxt "@action:button"
msgid "Remove"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:125
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:114
msgctxt "@action:button"
msgid "Refresh"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:207
msgctxt "@label"
msgid ""
"If your printer is not listed, read the network printing "
"troubleshooting guide"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:245
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:234
msgctxt "@label"
msgid "Type"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:282
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:271
msgctxt "@label"
msgid "Firmware version"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:294
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:283
msgctxt "@label"
msgid "Address"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:316
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:305
msgctxt "@label"
-msgid "This printer is not set up to host a group of Ultimaker 3 printers."
+msgid "This printer is not set up to host a group of printers."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:320
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:309
msgctxt "@label"
-msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
+msgid "This printer is the host for a group of %1 printers."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:330
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:319
msgctxt "@label"
msgid "The printer at this address has not yet responded."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:335
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:39
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:324
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:42
msgctxt "@action:button"
msgid "Connect"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:349
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:338
msgctxt "@title:window"
msgid "Printer Address"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:377
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:361
msgctxt "@alabel"
msgid "Enter the IP address or hostname of your printer on the network."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:407
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:390
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:132
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:30
-msgctxt "@title:window"
-msgid "Print over network"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:61
-msgctxt "@label"
-msgid "Printer selection"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:100
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:44
msgctxt "@action:button"
msgid "Print"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:36
-msgctxt "@label: arg 1 is group name"
-msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:47
+msgctxt "@title:window"
+msgid "Print over network"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
-msgctxt "@label link to connect manager"
-msgid "Add/Remove printers"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:79
+msgctxt "@label"
+msgid "Printer selection"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
-msgctxt "@info:tooltip"
-msgid "Opens the print jobs page with your default web browser."
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:173
+msgctxt "@label"
+msgid "Not available"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:15
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:130
-msgctxt "@action:button"
-msgid "View print jobs"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:175
+msgctxt "@label"
+msgid "Unreachable"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:37
-msgctxt "@label:status"
-msgid "Preparing to print"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:39
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:263
-msgctxt "@label:status"
-msgid "Printing"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:41
-msgctxt "@label:status"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:180
+msgctxt "@label"
msgid "Available"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:37
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:44
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:46
msgctxt "@label:status"
-msgid "Lost connection with the printer"
+msgid "Aborted"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:45
-msgctxt "@label:status"
-msgid "Unavailable"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label:status"
-msgid "Unknown"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:249
-msgctxt "@label:status"
-msgid "Disabled"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:265
-msgctxt "@label:status"
-msgid "Reserved"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:268
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:39
msgctxt "@label:status"
msgid "Finished"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:271
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:392
-msgctxt "@label"
-msgid "Preparing to print"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:273
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:42
msgctxt "@label:status"
-msgid "Action required"
+msgid "Preparing"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:276
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:48
msgctxt "@label:status"
-msgid "Paused"
+msgid "Pausing"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:52
msgctxt "@label:status"
msgid "Resuming"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:280
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:54
msgctxt "@label:status"
-msgid "Print aborted"
+msgid "Action required"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:373
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:394
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:213
msgctxt "@label"
-msgid "Not accepting print jobs"
+msgid "Waiting for: Unavailable printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:387
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:215
msgctxt "@label"
-msgid "Finishes at: "
+msgid "Waiting for: First available"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:389
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:217
msgctxt "@label"
-msgid "Clear build plate"
+msgid "Waiting for: "
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:396
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:299
msgctxt "@label"
-msgid "Waiting for configuration change"
+msgid "Configuration change"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:63
-msgctxt "@title"
-msgid "Print jobs"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:93
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:365
msgctxt "@label"
-msgid "Printing"
+msgid ""
+"The assigned printer, %1, requires the following configuration change(s):"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:111
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:367
+msgctxt "@label"
+msgid ""
+"The printer %1 is assigned, but the job contains an unknown material "
+"configuration."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:375
+msgctxt "@label"
+msgid "Change material %1 from %2 to %3."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:378
+msgctxt "@label"
+msgid "Load %3 as material %1 (This cannot be overridden)."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:381
+msgctxt "@label"
+msgid "Change print core %1 from %2 to %3."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:384
+msgctxt "@label"
+msgid "Change build plate to %1 (This cannot be overridden)."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:404
+msgctxt "@label"
+msgid "Override"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:432
+msgctxt "@label"
+msgid ""
+"Starting a print job with an incompatible configuration could damage your 3D "
+"printer. Are you sure you want to override the configuration and print %1?"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:435
+msgctxt "@window:title"
+msgid "Override configuration configuration and start print"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:466
+msgctxt "@label"
+msgid "Glass"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:469
+msgctxt "@label"
+msgid "Aluminum"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:39
+msgctxt "@label link to connect manager"
+msgid "Manage queue"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:60
msgctxt "@label"
msgid "Queued"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:170
-msgctxt "@label:title"
-msgid "Printers"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:36
+msgctxt "@label"
+msgid "Printing"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:224
-msgctxt "@action:button"
-msgid "View printers"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:49
+msgctxt "@label link to connect manager"
+msgid "Manage printers"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:38
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:115
+msgctxt "@label"
+msgid "Move to top"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:124
+msgctxt "@label"
+msgid "Delete"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
+msgctxt "@label"
+msgid "Resume"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
+msgctxt "@label"
+msgid "Pause"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:146
+msgctxt "@label"
+msgid "Abort"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:178
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to move %1 to the top of the queue?"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:179
+msgctxt "@window:title"
+msgid "Move print job to top"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:188
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to delete %1?"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:189
+msgctxt "@window:title"
+msgid "Delete print job"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:198
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to abort %1?"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
+msgctxt "@window:title"
+msgid "Abort print"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:43
msgctxt "@info:tooltip"
msgid "Connect to a printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:117
-msgctxt "@info:tooltip"
-msgid "Load the configuration of the printer into Cura"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:118
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:121
msgctxt "@action:button"
msgid "Activate Configuration"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:122
+msgctxt "@info:tooltip"
+msgid "Load the configuration of the printer into Cura"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:130
msgctxt "@label"
msgid "Color scheme"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:145
msgctxt "@label:listbox"
msgid "Material Color"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:149
msgctxt "@label:listbox"
msgid "Line Type"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:153
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:157
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:198
msgctxt "@label"
msgid "Compatibility Mode"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:284
msgctxt "@label"
msgid "Show Travels"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:290
msgctxt "@label"
msgid "Show Helpers"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:296
msgctxt "@label"
msgid "Show Shell"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:302
msgctxt "@label"
msgid "Show Infill"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:355
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:366
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:379
msgctxt "@label"
msgid "Top / Bottom"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:383
msgctxt "@label"
msgid "Inner Wall"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:448
msgctxt "@label"
msgid "min"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:500
msgctxt "@label"
msgid "max"
msgstr ""
@@ -2070,17 +2253,17 @@ msgctxt "@label"
msgid "Post Processing Scripts"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:225
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:227
msgctxt "@action"
msgid "Add a script"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:271
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:273
msgctxt "@label"
msgid "Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:474
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:477
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr ""
@@ -2181,53 +2364,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:37
msgctxt "@label"
msgid "Mesh Type"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:68
msgctxt "@label"
msgid "Normal model"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:75
msgctxt "@label"
msgid "Print as support"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:83
msgctxt "@label"
msgid "Don't support overlap with other models"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:91
msgctxt "@label"
msgid "Modify settings for overlap with other models"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:99
msgctxt "@label"
msgid "Modify settings for infill of other models"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:342
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:347
msgctxt "@action:button"
msgid "Select settings"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:384
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:389
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:432
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:437
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:98
msgctxt "@label:textbox"
msgid "Filter..."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:446
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:451
msgctxt "@label:checkbox"
msgid "Show all"
msgstr ""
@@ -2249,13 +2432,13 @@ msgid "Create new"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:70
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:68
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:72
msgctxt "@action:title"
msgid "Summary - Cura Project"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:92
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:96
msgctxt "@action:label"
msgid "Printer settings"
msgstr ""
@@ -2272,18 +2455,19 @@ msgid "Update"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:143
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:105
msgctxt "@action:label"
msgid "Type"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:159
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
msgctxt "@action:label"
msgid "Printer Group"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:180
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:192
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:196
msgctxt "@action:label"
msgid "Profile settings"
msgstr ""
@@ -2295,19 +2479,20 @@ msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:216
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:308
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:216
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:220
msgctxt "@action:label"
msgid "Name"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:231
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:200
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:204
msgctxt "@action:label"
msgid "Not in profile"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:236
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:209
msgctxt "@action:label"
msgid "%1 override"
msgid_plural "%1 overrides"
@@ -2337,7 +2522,7 @@ msgid "How should the conflict in the material be resolved?"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:327
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:233
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:237
msgctxt "@action:label"
msgid "Setting visibility"
msgstr ""
@@ -2348,13 +2533,13 @@ msgid "Mode"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:352
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:246
msgctxt "@action:label"
msgid "Visible settings:"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:357
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:247
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:251
msgctxt "@action:label"
msgid "%1 out of %2"
msgstr ""
@@ -2416,41 +2601,6 @@ msgctxt "@action:button"
msgid "Move to Next Position"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:30
-msgctxt "@title"
-msgid "Upgrade Firmware"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:41
-msgctxt "@label"
-msgid ""
-"Firmware is the piece of software running directly on your 3D printer. This "
-"firmware controls the step motors, regulates the temperature and ultimately "
-"makes your printer work."
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:51
-msgctxt "@label"
-msgid ""
-"The firmware shipping with new printers works, but new versions tend to have "
-"more features and improvements."
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:65
-msgctxt "@action:button"
-msgid "Automatically upgrade Firmware"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:75
-msgctxt "@action:button"
-msgid "Upload custom Firmware"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:87
-msgctxt "@title:window"
-msgid "Select custom firmware"
-msgstr ""
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:37
msgctxt "@label"
msgid "Please select any upgrades made to this Ultimaker Original"
@@ -2600,27 +2750,11 @@ msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
-msgctxt "@label:"
-msgid "Pause"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
-msgctxt "@label:"
-msgid "Resume"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:325
-msgctxt "@label:"
+msgctxt "@label"
msgid "Abort Print"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
-msgctxt "@window:title"
-msgid "Abort print"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:337
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
@@ -2654,19 +2788,17 @@ msgid "Customized"
msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:634
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:637
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:635
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr ""
@@ -2686,103 +2818,183 @@ msgctxt "@action:button"
msgid "Create New Profile"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:65
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:71
msgctxt "@title"
msgid "Information"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:94
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:100
msgctxt "@title:window"
msgid "Confirm Diameter Change"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:101
msgctxt "@label (%1 is a number)"
msgid ""
"The new filament diameter is set to %1 mm, which is not compatible with the "
"current extruder. Do you wish to continue?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:133
msgctxt "@label"
msgid "Display Name"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:143
msgctxt "@label"
msgid "Brand"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:153
msgctxt "@label"
msgid "Material Type"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:162
msgctxt "@label"
msgid "Color"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:212
msgctxt "@label"
msgid "Properties"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:214
msgctxt "@label"
msgid "Density"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:229
msgctxt "@label"
msgid "Diameter"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:253
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:263
msgctxt "@label"
msgid "Filament Cost"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:269
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:280
msgctxt "@label"
msgid "Filament weight"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:298
msgctxt "@label"
msgid "Filament length"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:295
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:307
msgctxt "@label"
msgid "Cost per Meter"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:309
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:321
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:316
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:328
msgctxt "@label"
msgid "Unlink Material"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:327
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:339
msgctxt "@label"
msgid "Description"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:352
msgctxt "@label"
msgid "Adhesion Information"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:366
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:378
msgctxt "@label"
msgid "Print settings"
msgstr ""
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:84
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
+msgctxt "@action:button"
+msgid "Activate"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:101
+msgctxt "@action:button"
+msgid "Create"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:114
+msgctxt "@action:button"
+msgid "Duplicate"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
+msgctxt "@action:button"
+msgid "Import"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
+msgctxt "@action:button"
+msgid "Export"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:203
+msgctxt "@action:label"
+msgid "Printer"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:262
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
+msgctxt "@title:window"
+msgid "Confirm Remove"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:263
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
+msgctxt "@label (%1 is object name)"
+msgid "Are you sure you wish to remove %1? This cannot be undone!"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:285
+msgctxt "@title:window"
+msgid "Import Material"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:286
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid ""
+"Could not import material %1: %2"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:290
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully imported material %1"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:308
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:316
+msgctxt "@title:window"
+msgid "Export Material"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:320
+msgctxt "@info:status Don't translate the XML tags and !"
+msgid ""
+"Failed to export material to %1: %2"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:326
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully exported material to %1"
+msgstr ""
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:14
msgctxt "@title:tab"
msgid "Setting Visibility"
@@ -2819,222 +3031,222 @@ msgid "Unit"
msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:544
msgctxt "@title:tab"
msgid "General"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:142
msgctxt "@label"
msgid "Interface"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:153
msgctxt "@label"
msgid "Language:"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:220
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Currency:"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:235
msgctxt "@label"
msgid "Theme:"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:294
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:292
msgctxt "@label"
msgid ""
"You will need to restart the application for these changes to have effect."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:311
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:309
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:317
msgctxt "@option:check"
msgid "Slice automatically"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:333
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:331
msgctxt "@label"
msgid "Viewport behavior"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:339
msgctxt "@info:tooltip"
msgid ""
"Highlight unsupported areas of the model in red. Without support these areas "
"will not print properly."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:348
msgctxt "@option:check"
msgid "Display overhang"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:357
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:355
msgctxt "@info:tooltip"
msgid ""
"Moves the camera so the model is in the center of the view when a model is "
"selected"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:360
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:371
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:369
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:376
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:374
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:401
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid ""
"Should models on the platform be moved so that they no longer intersect?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:406
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:413
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:420
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:418
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
msgctxt "@info:tooltip"
msgid "Show caution message in g-code reader."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:439
msgctxt "@option:check"
msgid "Caution message in g-code reader"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:449
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:447
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:452
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:470
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:468
msgctxt "@label"
msgid "Opening and saving files"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:477
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:475
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:482
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:480
msgctxt "@option:check"
msgid "Scale large models"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
msgctxt "@info:tooltip"
msgid ""
"An model may appear extremely small if its unit is for example in meters "
"rather than millimeters. Should these models be scaled up?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:497
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:495
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@info:tooltip"
msgid "Should models be selected after they are loaded?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:510
msgctxt "@option:check"
msgid "Select models when loaded"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
msgctxt "@info:tooltip"
msgid ""
"Should a prefix based on the printer name be added to the print job name "
"automatically?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:527
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:525
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:541
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:539
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:551
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:549
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:559
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:557
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
msgctxt "@option:openProject"
-msgid "Always ask"
+msgid "Always ask me this"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:574
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:572
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:575
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@option:openProject"
msgid "Always import models"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:611
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:609
msgctxt "@info:tooltip"
msgid ""
"When you have made changes to a profile and switched to a different one, a "
@@ -3042,27 +3254,44 @@ msgid ""
"not, or you can choose a default behaviour and never show that dialog again."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:618
msgctxt "@label"
-msgid "Override Profile"
+msgid "Profiles"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:670
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:623
+msgctxt "@window:text"
+msgid ""
+"Default behavior for changed setting values when switching to a different "
+"profile: "
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
+msgctxt "@option:discardOrKeep"
+msgid "Always discard changed settings"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:639
+msgctxt "@option:discardOrKeep"
+msgid "Always transfer changed settings to new profile"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:673
msgctxt "@label"
msgid "Privacy"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:678
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:681
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:683
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:686
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:697
msgctxt "@info:tooltip"
msgid ""
"Should anonymous data about your print be sent to Ultimaker? Note, no "
@@ -3070,56 +3299,37 @@ msgid ""
"stored."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:702
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:711
msgctxt "@action:button"
msgid "More information"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:729
msgctxt "@label"
msgid "Experimental"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:733
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:736
msgctxt "@info:tooltip"
msgid "Use multi build plate functionality"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:738
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:741
msgctxt "@option:check"
msgid "Use multi build plate functionality (restart required)"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:748
-msgctxt "@info:tooltip"
-msgid ""
-"Should newly loaded models be arranged on the build plate? Used in "
-"conjunction with multi build plate (EXPERIMENTAL)"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:753
-msgctxt "@option:check"
-msgid "Do not arrange objects on load"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:16
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:536
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:549
msgctxt "@title:tab"
msgid "Printers"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:90
-msgctxt "@action:button"
-msgid "Activate"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:55
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:129
msgctxt "@action:button"
@@ -3137,7 +3347,7 @@ msgid "Connection:"
msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:162
-#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:47
+#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:55
msgctxt "@info:status"
msgid "The printer is not connected."
msgstr ""
@@ -3163,7 +3373,7 @@ msgid "Aborting print..."
msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:540
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:553
msgctxt "@title:tab"
msgid "Profiles"
msgstr ""
@@ -3178,18 +3388,6 @@ msgctxt "@label"
msgid "Duplicate"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:145
-msgctxt "@action:button"
-msgid "Import"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:158
-msgctxt "@action:button"
-msgid "Export"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:174
msgctxt "@title:window"
msgid "Create Profile"
@@ -3200,18 +3398,6 @@ msgctxt "@title:window"
msgid "Duplicate Profile"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:221
-msgctxt "@title:window"
-msgid "Confirm Remove"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:222
-msgctxt "@label (%1 is object name)"
-msgid "Are you sure you wish to remove %1? This cannot be undone!"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:256
msgctxt "@title:window"
msgid "Rename Profile"
@@ -3232,232 +3418,202 @@ msgctxt "@label %1 is printer name"
msgid "Printer: %1"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Protected profiles"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Custom profiles"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:468
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:480
msgctxt "@action:button"
msgid "Update profile with current settings/overrides"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:475
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:487
msgctxt "@action:button"
msgid "Discard current changes"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:504
msgctxt "@action:label"
msgid ""
"This profile uses the defaults specified by the printer, so it has no "
"settings/overrides in the list below."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:511
msgctxt "@action:label"
msgid "Your current settings match the selected profile."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:518
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:530
msgctxt "@title:tab"
msgid "Global Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:40
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:538
-msgctxt "@title:tab"
-msgid "Materials"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:105
-msgctxt "@action:button"
-msgid "Create"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:118
-msgctxt "@action:button"
-msgid "Duplicate"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:235
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:243
-msgctxt "@title:window"
-msgid "Import Material"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:244
-msgctxt "@info:status Don't translate the XML tags or !"
-msgid ""
-"Could not import material %1: %2"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:248
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully imported material %1"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:266
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:274
-msgctxt "@title:window"
-msgid "Export Material"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:278
-msgctxt "@info:status Don't translate the XML tags and !"
-msgid ""
-"Failed to export material to %1: %2"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:284
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully exported material to %1"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:337
-msgctxt "@action:label"
-msgid "Printer"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:896
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:953
msgctxt "@title:window"
msgid "Add Printer"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:195
msgctxt "@label"
msgid "Printer Name:"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:219
msgctxt "@action:button"
msgid "Add Printer"
msgstr ""
+#: /home/ruben/Projects/Cura/resources/qml/JobSpecs.qml:84
+msgctxt "@text Print job name"
+msgid "Untitled"
+msgstr ""
+
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:15
msgctxt "@title:window"
msgid "About Cura"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:55
msgctxt "@label"
msgid "version: %1"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:56
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
msgctxt "@label"
msgid "End-to-end solution for fused filament 3D printing."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:82
msgctxt "@info:credit"
msgid ""
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
"Cura proudly uses the following open source projects:"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
msgctxt "@label"
msgid "Graphical user interface"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
msgctxt "@label"
msgid "Application framework"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
msgctxt "@label"
msgid "G-code generator"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
msgctxt "@label"
msgid "Interprocess communication library"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
msgctxt "@label"
msgid "Programming language"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:124
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
msgctxt "@label"
msgid "GUI framework"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:125
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
msgctxt "@label"
msgid "GUI framework bindings"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:126
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:140
msgctxt "@label"
msgid "C/C++ Binding library"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:141
msgctxt "@label"
msgid "Data interchange format"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:142
msgctxt "@label"
msgid "Support library for scientific computing"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:129
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:143
msgctxt "@label"
msgid "Support library for faster math"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:130
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:144
msgctxt "@label"
msgid "Support library for handling STL files"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:145
+msgctxt "@label"
+msgid "Support library for handling planar objects"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:146
+msgctxt "@label"
+msgid "Support library for handling triangular meshes"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
+msgctxt "@label"
+msgid "Support library for analysis of complex networks"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
msgctxt "@label"
msgid "Support library for handling 3MF files"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:149
+msgctxt "@label"
+msgid "Support library for file metadata and streaming"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:150
msgctxt "@label"
msgid "Serial communication library"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:151
msgctxt "@label"
msgid "ZeroConf discovery library"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:152
msgctxt "@label"
msgid "Polygon clipping library"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:153
msgctxt "@Label"
msgid "Python HTTP library"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:155
msgctxt "@label"
msgid "Font"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:156
msgctxt "@label"
msgid "SVG icons"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:157
msgctxt "@label"
msgid "Linux cross-distribution application deployment"
msgstr ""
@@ -3467,7 +3623,7 @@ msgctxt "@label"
msgid "Profile:"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the "
@@ -3476,53 +3632,53 @@ msgid ""
"Click to open the profile manager."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:200
msgctxt "@label:textbox"
msgid "Search..."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:544
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:545
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:553
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:554
msgctxt "@action:menu"
msgid "Copy all changed values to all extruders"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:568
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:591
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:609
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:590
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:613
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:614
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:426
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:637
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:644
msgctxt "@action:inmenu"
msgid "Collapse All"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:649
msgctxt "@action:inmenu"
msgid "Expand All"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:253
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated "
@@ -3541,19 +3697,19 @@ msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:155
msgctxt "@label"
msgid ""
"This setting is always shared between all extruders. Changing it here will "
"change the value for all extruders."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:158
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:189
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
@@ -3561,7 +3717,7 @@ msgid ""
"Click to restore the value of the profile."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:281
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value "
@@ -3608,7 +3764,7 @@ msgid ""
msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ExtruderBox.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:268
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:272
msgctxt "@label"
msgid "Extruder"
msgstr ""
@@ -3666,7 +3822,7 @@ msgid "The nozzle inserted in this extruder."
msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/HeatedBedBox.qml:25
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:489
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:493
msgctxt "@label"
msgid "Build plate"
msgstr ""
@@ -3696,6 +3852,21 @@ msgid ""
"when you're ready to print."
msgstr ""
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:13
+msgctxt "@label:category menu label"
+msgid "Material"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:37
+msgctxt "@label:category menu label"
+msgid "Favorites"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:61
+msgctxt "@label:category menu label"
+msgid "Generic"
+msgstr ""
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/PrinterMenu.qml:25
msgctxt "@label:category menu label"
msgid "Network enabled printers"
@@ -3711,12 +3882,12 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:39
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:42
msgctxt "@action:inmenu menubar:view"
msgid "&Camera position"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:58
msgctxt "@action:inmenu menubar:view"
msgid "&Build plate"
msgstr ""
@@ -3726,12 +3897,12 @@ msgctxt "@action:inmenu"
msgid "Visible Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:42
msgctxt "@action:inmenu"
msgid "Show All Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:53
msgctxt "@action:inmenu"
msgid "Manage Setting Visibility..."
msgstr ""
@@ -3792,351 +3963,350 @@ msgid ""
"G-code files cannot be modified"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:340
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:359
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:358
msgctxt "@tooltip"
msgid "Time specification"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:440
msgctxt "@label"
msgid "Cost specification"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
msgctxt "@label m for meter"
msgid "%1m"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:447
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:456
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
msgctxt "@label g for grams"
msgid "%1g"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:453
msgctxt "@label"
msgid "Total:"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:577
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
msgctxt "@tooltip"
msgid ""
"Recommended Print Setup
Print with the recommended settings "
"for the selected printer, material and quality."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:582
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
msgctxt "@tooltip"
msgid ""
"Custom Print Setup
"
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
-msgctxt "@action:button"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Mit Doodle3D WLAN-Box drucken"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:66
-msgctxt "@properties:tooltip"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Mit Doodle3D WLAN-Box drucken"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:86
-msgctxt "@info:status"
-msgid "Connecting to Doodle3D Connect"
-msgstr "Zu Doodle3D Connect verbinden"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:258
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:204
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:398
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:88
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
-#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
-#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:275
-msgctxt "@action:button"
-msgid "Cancel"
-msgstr "Abbrechen"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:154
-msgctxt "@info:status"
-msgid "Sending data to Doodle3D Connect"
-msgstr "Daten werden zu Doodle3D Connect gesendet"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:161
-msgctxt "@info:status"
-msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
-msgstr "Daten können nicht zu Doodle3D Connect gesendet werden. Ist noch ein weiterer Auftrag in Bearbeitung?"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:175
-msgctxt "@info:status"
-msgid "Storing data on Doodle3D Connect"
-msgstr "Daten werden auf Doodle3D Connect gespeichert"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:213
-msgctxt "@info:status"
-msgid "File sent to Doodle3D Connect"
-msgstr "Datei wurde zu Doodle3D Connect gesendet"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@action:button"
-msgid "Open Connect..."
-msgstr "Connect wird geöffnet ..."
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@info:tooltip"
-msgid "Open the Doodle3D Connect web interface"
-msgstr "Doodle3D Connect Web-Schnittstelle öffnen"
-
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:32
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Änderungsprotokoll anzeigen"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:20
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py:25
+msgctxt "@action"
+msgid "Update Firmware"
+msgstr "Firmware aktualisieren"
+
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:23
msgctxt "@item:inmenu"
msgid "Flatten active settings"
msgstr "Einstellungen Glätten aktiv"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:32
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:35
msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr "Das Profil wurde geglättet und aktiviert."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:40
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:32
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "USB-Drucken"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:41
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:33
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "Über USB drucken"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:42
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:34
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "Über USB drucken"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:83
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:69
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Über USB verbunden"
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:92
+msgctxt "@label"
+msgid "A USB print is in progress, closing Cura will stop this print. Are you sure?"
+msgstr "Ein USB-Druck wird ausgeführt. Das Schließen von Cura beendet diesen Druck. Sind Sie sicher?"
+
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/install/X3GWriter/__init__.py:15
#: /home/ruben/Projects/Cura/plugins/X3GWriter/__init__.py:15
msgctxt "X3G Writer File Description"
@@ -173,7 +133,12 @@ msgctxt "@item:inlistbox"
msgid "Compressed G-code File"
msgstr "Komprimierte G-Code-Datei"
-#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/GCodeGzWriter.py:38
+msgctxt "@error:not supported"
+msgid "GCodeGzWriter does not support text mode."
+msgstr "GCodeWriter unterstützt keinen Textmodus."
+
+#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:28
msgctxt "@item:inlistbox"
msgid "Ultimaker Format Package"
msgstr "Ultimaker Format Package"
@@ -195,7 +160,7 @@ msgid "Save to Removable Drive {0}"
msgstr "Auf Wechseldatenträger speichern {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:64
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:133
msgctxt "@info:status"
msgid "There are no file formats available to write with!"
msgstr "Es sind keine Dateiformate zum Schreiben vorhanden!"
@@ -234,7 +199,7 @@ msgstr "Konnte nicht auf dem Wechseldatenträger gespeichert werden {0}: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:137
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:133
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:140
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1592
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1607
msgctxt "@info:title"
msgid "Error"
msgstr "Fehler"
@@ -263,8 +228,8 @@ msgstr "Wechseldatenträger auswerfen {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:151
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:163
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1582
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1681
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1597
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1695
msgctxt "@info:title"
msgid "Warning"
msgstr "Warnhinweis"
@@ -291,259 +256,269 @@ msgctxt "@item:intext"
msgid "Removable Drive"
msgstr "Wechseldatenträger"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:70
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:78
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:88
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "Drucken über Netzwerk"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:71
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:79
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:74
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:89
msgctxt "@properties:tooltip"
msgid "Print over network"
msgstr "Drücken über Netzwerk"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:84
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:87
msgctxt "@info:status"
msgid "Connected over the network."
msgstr "Über Netzwerk verbunden."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:87
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:90
msgctxt "@info:status"
msgid "Connected over the network. Please approve the access request on the printer."
msgstr "Über Netzwerk verbunden. Geben Sie die Zugriffsanforderung für den Drucker frei."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:89
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:92
msgctxt "@info:status"
msgid "Connected over the network. No access to control the printer."
msgstr "Über Netzwerk verbunden. Kein Zugriff auf die Druckerverwaltung."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:94
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:97
msgctxt "@info:status"
msgid "Access to the printer requested. Please approve the request on the printer"
msgstr "Zugriff auf Drucker erforderlich. Bestätigen Sie den Zugriff auf den Drucker"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:97
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:100
msgctxt "@info:title"
msgid "Authentication status"
msgstr "Authentifizierungsstatus"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:99
-msgctxt "@info:status"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:100
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:106
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:110
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:108
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:112
msgctxt "@info:title"
msgid "Authentication Status"
msgstr "Authentifizierungsstatus"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:101
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:103
msgctxt "@action:button"
msgid "Retry"
msgstr "Erneut versuchen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:104
msgctxt "@info:tooltip"
msgid "Re-send the access request"
msgstr "Zugriffanforderung erneut senden"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:105
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:107
msgctxt "@info:status"
msgid "Access to the printer accepted"
msgstr "Zugriff auf den Drucker genehmigt"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:109
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:111
msgctxt "@info:status"
msgid "No access to print with this printer. Unable to send print job."
msgstr "Kein Zugriff auf das Drucken mit diesem Drucker. Druckauftrag kann nicht gesendet werden."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:111
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:29
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:33
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:70
msgctxt "@action:button"
msgid "Request Access"
msgstr "Zugriff anfordern"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:113
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:28
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:72
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:115
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:34
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:71
msgctxt "@info:tooltip"
msgid "Send access request to the printer"
msgstr "Zugriffsanforderung für den Drucker senden"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:198
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:200
msgctxt "@label"
msgid "Unable to start a new print job."
msgstr "Es kann kein neuer Druckauftrag gestartet werden."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:200
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:202
msgctxt "@label"
msgid "There is an issue with the configuration of your Ultimaker, which makes it impossible to start the print. Please resolve this issues before continuing."
msgstr "Es liegt ein Problem mit der Konfiguration Ihres Ultimaker vor, das den Druckstart verhindert. Lösen Sie dieses Problem bitte, bevor Sie fortfahren."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:206
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:228
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:208
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:230
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "Konfiguration nicht übereinstimmend"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:220
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:222
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "Möchten Sie wirklich mit der gewählten Konfiguration drucken?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:222
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:224
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Anforderungen zwischen der Druckerkonfiguration oder -kalibrierung und Cura stimmen nicht überein. Für optimale Ergebnisse schneiden Sie stets für die PrintCores und Materialien, die in Ihren Drucker eingelegt wurden."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:249
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:166
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:251
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:199
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "Das Senden neuer Aufträge ist (vorübergehend) blockiert; der vorherige Druckauftrag wird noch gesendet."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:256
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:185
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:202
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:258
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:234
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "Daten werden zum Drucker gesendet"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:257
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:186
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:203
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:259
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:219
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:235
msgctxt "@info:title"
msgid "Sending Data"
msgstr "Daten werden gesendet"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:321
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:260
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:236
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:381
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:20
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
+#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:279
+msgctxt "@action:button"
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:323
#, python-brace-format
msgctxt "@info:status"
msgid "No Printcore loaded in slot {slot_number}"
msgstr "Kein PrintCore geladen in Steckplatz {slot_number}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:327
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:329
#, python-brace-format
msgctxt "@info:status"
msgid "No material loaded in slot {slot_number}"
msgstr "Kein Material geladen in Steckplatz {slot_number}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:350
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:352
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {cura_printcore_name}, Printer: {remote_printcore_name}) selected for extruder {extruder_id}"
msgstr "Abweichender PrintCore (Cura: {cura_printcore_name}, Printer: {remote_printcore_name}) für Extruder gewählt {extruder_id}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:359
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:361
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "Abweichendes Material (Cura: {0}, Drucker: {1}) für Extruder {2} gewählt"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:545
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:547
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "Synchronisieren Ihres Druckers"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:547
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:549
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "Möchten Sie Ihre aktuelle Druckerkonfiguration in Cura verwenden?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:549
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:551
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Die PrintCores und/oder Materialien auf Ihrem Drucker unterscheiden sich von denen Ihres aktuellen Projekts. Für optimale Ergebnisse schneiden Sie stets für die PrintCores und Materialien, die in Ihren Drucker eingelegt wurden."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:81
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:91
msgctxt "@info:status"
msgid "Connected over the network"
-msgstr "Über Netzwerk verbunden."
+msgstr "Über Netzwerk verbunden"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:303
msgctxt "@info:status"
msgid "Print job was successfully sent to the printer."
msgstr "Der Druckauftrag wurde erfolgreich an den Drucker gesendet."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:264
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:305
msgctxt "@info:title"
msgid "Data Sent"
msgstr "Daten gesendet"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:265
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:306
msgctxt "@action:button"
msgid "View in Monitor"
msgstr "In Monitor überwachen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:353
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:422
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "Drucker '{printer_name}' hat '{job_name}' vollständig gedrückt."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:355
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:424
#, python-brace-format
msgctxt "@info:status"
msgid "The print job '{job_name}' was finished."
msgstr "Der Druckauftrag '{job_name}' wurde ausgeführt."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:356
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:425
msgctxt "@info:status"
msgid "Print finished"
msgstr "Druck vollendet"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:20
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py:26
msgctxt "@action"
msgid "Connect via Network"
msgstr "Anschluss über Netzwerk"
-#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:13
msgctxt "@item:inmenu"
msgid "Monitor"
msgstr "Überwachen"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:69
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:119
+msgctxt "@info"
+msgid "Could not access update information."
+msgstr "Zugriff auf Update-Informationen nicht möglich."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:17
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr "Für Ihren {machine_name} sind neue Funktionen verfügbar! Es wird empfohlen, ein Firmware-Update für Ihren Drucker auszuführen."
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:73
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:21
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "Neue Firmware für %s verfügbar"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:76
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:27
msgctxt "@action:button"
msgid "How to update"
msgstr "Anleitung für die Aktualisierung"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:92
-msgctxt "@info"
-msgid "Could not access update information."
-msgstr "Zugriff auf Update-Informationen nicht möglich."
-
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Layer view"
msgstr "Schichtenansicht"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:113
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
-msgstr "Cura zeigt die Schichten nicht akkurat an, wenn Wire Printing aktiviert ist."
+msgstr "Cura zeigt die Schichten nicht akkurat an, wenn Wire Printing aktiviert ist"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:114
msgctxt "@info:title"
msgid "Simulation View"
msgstr "Simulationsansicht"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:27
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:35
msgid "Modify G-Code"
msgstr "G-Code ändern"
@@ -557,32 +532,32 @@ msgctxt "@info:tooltip"
msgid "Create a volume in which supports are not printed."
msgstr "Erstellt ein Volumen, in dem keine Stützstrukturen gedruckt werden."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:44
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
msgctxt "@info"
msgid "Cura collects anonymized usage statistics."
msgstr "Cura erfasst anonymisierte Nutzungsstatistiken."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:47
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:55
msgctxt "@info:title"
msgid "Collecting Data"
msgstr "Daten werden erfasst"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:57
msgctxt "@action:button"
msgid "More info"
msgstr "Mehr Infos"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:58
msgctxt "@action:tooltip"
msgid "See more information on what data Cura sends."
msgstr "Siehe mehr Informationen dazu, was Cura sendet."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:60
msgctxt "@action:button"
msgid "Allow"
msgstr "Zulassen"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:53
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:61
msgctxt "@action:tooltip"
msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
msgstr "Damit lassen Sie zu, dass Cura anonymisierte Nutzungsstatistiken sendet, um zukünftige Verbesserungen für Cura zu definieren. Einige Ihrer Präferenzen und Einstellungen, die Cura-Version und ein Hash der Modelle, die Sie slicen, werden gesendet."
@@ -592,18 +567,6 @@ msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Cura 15.04-Profile"
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
-msgctxt "@item:inlistbox"
-msgid "Blender file"
-msgstr "Blender-Datei"
-
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
-msgctxt "@info:status"
-msgid ""
-"Could not export using \"{}\" quality!\n"
-"Felt back to \"{}\"."
-msgstr "Exportieren in \"{}\" Qualität nicht möglich!\nZurückgeschaltet auf \"{}\"."
-
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "JPG Image"
@@ -629,49 +592,56 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "GIF-Bilddatei"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "Slicing mit dem aktuellen Material nicht möglich, da es mit der gewählten Maschine oder Konfiguration nicht kompatibel ist."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:344
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:367
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:376
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:363
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:387
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:396
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:405
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:414
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "Slicing nicht möglich"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:343
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:362
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "Die aktuellen Einstellungen lassen kein Schneiden (Slicing) zu. Die folgenden Einstellungen sind fehlerhaft:{0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr "Aufgrund der Pro-Modell-Einstellungen ist kein Schneiden (Slicing) möglich. Die folgenden Einstellungen sind für ein oder mehrere Modelle fehlerhaft: {error_labels}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:375
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:395
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "Schneiden (Slicing) ist nicht möglich, da der Einzugsturm oder die Einzugsposition(en) ungültig ist (sind)."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:385
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:404
+#, python-format
+msgctxt "@info:status"
+msgid "Unable to slice because there are objects associated with disabled Extruder %s."
+msgstr "Schneiden (Slicing) ist nicht möglich, da Objekte vorhanden sind, die mit dem deaktivierten Extruder %s verbunden sind."
+
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:413
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "Es ist kein Objekt zum Schneiden vorhanden, da keines der Modelle der Druckabmessung entspricht. Bitte die Modelle passend skalieren oder drehen."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "Schichten werden verarbeitet"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:title"
msgid "Information"
msgstr "Informationen"
@@ -687,29 +657,40 @@ msgid "Configure Per Model Settings"
msgstr "Pro Objekteinstellungen konfigurieren"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:175
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:575
msgctxt "@title:tab"
msgid "Recommended"
msgstr "Empfohlen"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:177
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:580
msgctxt "@title:tab"
msgid "Custom"
msgstr "Benutzerdefiniert"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:32
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:28
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:34
msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "3MF-Datei"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:199
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:695
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:714
msgctxt "@label"
msgid "Nozzle"
msgstr "Düse"
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:468
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Project file {0} contains an unknown machine type {1}. Cannot import the machine. Models will be imported instead."
+msgstr "Projektdatei {0} enthält einen unbekannten Maschinentyp {1}. Importieren der Maschine ist nicht möglich. Stattdessen werden die Modelle importiert."
+
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:471
+msgctxt "@info:title"
+msgid "Open Project File"
+msgstr "Projektdatei öffnen"
+
#: /home/ruben/Projects/Cura/plugins/SolidView/__init__.py:12
msgctxt "@item:inmenu"
msgid "Solid view"
@@ -720,18 +701,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "G-Datei"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:322
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "G-Code parsen"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:470
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:326
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:474
msgctxt "@info:title"
msgid "G-code Details"
msgstr "G-Code-Details"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:468
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:472
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "Stellen Sie sicher, dass der G-Code für Ihren Drucker und Ihre Druckerkonfiguration geeignet ist, bevor Sie die Datei senden. Der Darstellung des G-Codes ist möglicherweise nicht korrekt."
@@ -742,27 +723,27 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Cura-Profil"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:26
msgctxt "@item:inlistbox"
msgid "3MF file"
msgstr "3MF-Datei"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:34
msgctxt "@item:inlistbox"
msgid "Cura Project 3MF file"
msgstr "Cura-Projekt 3MF-Datei"
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/ThreeMFWriter.py:179
+msgctxt "@error:zip"
+msgid "Error writing 3mf file."
+msgstr "Fehler beim Schreiben von 3MF-Datei."
+
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelection.py:17
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelection.py:18
msgctxt "@action"
msgid "Select upgrades"
msgstr "Upgrades wählen"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py:12
-msgctxt "@action"
-msgid "Upgrade Firmware"
-msgstr "Firmware aktualisieren"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py:14
msgctxt "@action"
msgid "Checkup"
@@ -773,79 +754,79 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "Druckbett nivellieren"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:82
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "Außenwand"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:83
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "Innenwände"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:84
msgctxt "@tooltip"
msgid "Skin"
msgstr "Außenhaut"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:85
msgctxt "@tooltip"
msgid "Infill"
msgstr "Füllung"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:86
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "Stützstruktur-Füllung"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:87
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "Stützstruktur-Schnittstelle"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:88
msgctxt "@tooltip"
msgid "Support"
msgstr "Stützstruktur"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
msgctxt "@tooltip"
msgid "Skirt"
msgstr "Skirt"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
msgctxt "@tooltip"
msgid "Travel"
msgstr "Bewegungen"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
msgctxt "@tooltip"
msgid "Retractions"
msgstr "Einzüge"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
msgctxt "@tooltip"
msgid "Other"
msgstr "Sonstige"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:229
-msgctxt "@label unknown material"
-msgid "Unknown"
-msgstr "Unbekannt"
-
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:313
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:310
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "Vorgeschnittene Datei {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:235
+#: /home/ruben/Projects/Cura/cura/API/Account.py:71
+msgctxt "@info:title"
+msgid "Login failed"
+msgstr "Login fehlgeschlagen"
+
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:201
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "Datei bereits vorhanden"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:236
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:202
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:122
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
@@ -857,23 +838,23 @@ msgctxt "@menuitem"
msgid "Not overridden"
msgstr "Nicht überschrieben"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:119
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "Das gewählte Material ist mit der gewählten Maschine oder Konfiguration nicht kompatibel."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:120
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
msgctxt "@info:title"
msgid "Incompatible Material"
msgstr "Material nicht kompatibel"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:842
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:866
#, python-format
msgctxt "@info:generic"
msgid "Settings have been changed to match the current availability of extruders: [%s]"
msgstr "Die Einstellungen wurden passend für die aktuelle Verfügbarkeit der Extruder geändert: [%s]"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:844
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:868
msgctxt "@info:title"
msgid "Settings updated"
msgstr "Einstellungen aktualisiert"
@@ -888,7 +869,7 @@ msgstr "Export des Profils nach {0} fehlgeschlagen: !"
msgid "Failed to export profile to {0}: Writer plugin reported failure."
-msgstr "Export des Profils nach {0} fehlgeschlagen: Fehlermeldung von Writer-Plugin"
+msgstr "Export des Profils nach {0} fehlgeschlagen: Fehlermeldung von Writer-Plugin."
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:143
#, python-brace-format
@@ -902,8 +883,6 @@ msgid "Export succeeded"
msgstr "Export erfolgreich ausgeführt"
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:170
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
@@ -911,58 +890,70 @@ msgstr "Import des Profils aus Datei {0} fehlgeschlagen: or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "No custom profile to import in file {0}"
msgstr "Kein benutzerdefiniertes Profil für das Importieren in Datei {0}"
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags !"
+msgid "Failed to import profile from {0}:"
+msgstr "Import des Profils aus Datei {0} fehlgeschlagen:"
+
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:218
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:228
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "This profile {0} contains incorrect data, could not import it."
msgstr "Dieses Profil {0} enthält falsche Daten, Importieren nicht möglich."
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:241
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it."
msgstr "Die Maschine, die im Profil {0} ({1}) definiert wurde, entspricht nicht Ihrer derzeitigen Maschine ({2}). Importieren nicht möglich."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:316
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:312
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Failed to import profile from {0}:"
+msgstr "Import des Profils aus Datei {0} fehlgeschlagen:"
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:315
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "Profil erfolgreich importiert {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:319
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:318
#, python-brace-format
msgctxt "@info:status"
msgid "File {0} does not contain any valid profile."
msgstr "Datei {0} enthält kein gültiges Profil."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:322
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "Profil {0} hat einen unbekannten Dateityp oder ist beschädigt."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:340
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:339
msgctxt "@label"
msgid "Custom profile"
msgstr "Benutzerdefiniertes Profil"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:356
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:355
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "Für das Profil fehlt eine Qualitätsangabe."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:368
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:369
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "Es konnte keine Qualitätsangabe {0} für die vorliegende Konfiguration gefunden werden."
-#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:60
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:63
#, python-brace-format
msgctxt "@label"
msgid "Group #{group_nr}"
@@ -989,42 +980,42 @@ msgctxt "@item:inlistbox"
msgid "All Files (*)"
msgstr "Alle Dateien (*)"
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:544
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:636
msgctxt "@label"
msgid "Custom Material"
msgstr "Benutzerdefiniertes Material"
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:545
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:637
msgctxt "@label"
msgid "Custom"
msgstr "Benutzerdefiniert"
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:80
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:81
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
msgstr "Die Höhe der Druckabmessung wurde aufgrund des Wertes der Einstellung „Druckreihenfolge“ reduziert, um eine Kollision der Brücke mit den gedruckten Modellen zu verhindern."
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:82
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:83
msgctxt "@info:title"
msgid "Build Volume"
msgstr "Produktabmessungen"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:98
msgctxt "@info:backup_failed"
msgid "Could not create archive from user data directory: {}"
msgstr "Konnte kein Archiv von Benutzer-Datenverzeichnis {} erstellen"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:103
msgctxt "@info:title"
msgid "Backup"
msgstr "Backup"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:113
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup without having proper data or meta data."
msgstr "Versucht, ein Cura-Backup-Verzeichnis ohne entsprechende Daten oder Metadaten wiederherzustellen."
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:123
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup that does not match your current version."
msgstr "Versucht, ein Cura-Backup zu erstellen, das nicht Ihrer aktuellen Version entspricht."
@@ -1035,32 +1026,32 @@ msgid "Multiplying and placing objects"
msgstr "Objekte vervielfältigen und platzieren"
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:28
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
msgctxt "@info:title"
msgid "Placing Object"
msgstr "Objekt-Platzierung"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:96
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:149
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
msgctxt "@info:status"
msgid "Unable to find a location within the build volume for all objects"
msgstr "Innerhalb der Druckabmessung für alle Objekte konnte keine Position gefunden werden"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:30
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:66
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:67
msgctxt "@info:status"
msgid "Finding new location for objects"
msgstr "Neue Position für Objekte finden"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:34
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:70
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:71
msgctxt "@info:title"
msgid "Finding Location"
msgstr "Position finden"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:97
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:151
msgctxt "@info:title"
msgid "Can't Find Location"
msgstr "Kann Position nicht finden"
@@ -1191,223 +1182,233 @@ msgctxt "@action:button"
msgid "Send report"
msgstr "Bericht senden"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:328
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:473
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Geräte werden geladen..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:756
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:775
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "Die Szene wird eingerichtet..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:789
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:811
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Die Benutzeroberfläche wird geladen..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1023
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1037
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1581
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1596
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "Es kann nur jeweils ein G-Code gleichzeitig geladen werden. Wichtige {0} werden übersprungen."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1591
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1606
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "Wenn G-Code geladen wird, kann keine weitere Datei geöffnet werden. Wichtige {0} werden übersprungen."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1680
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1694
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr "Das gewählte Modell war zu klein zum Laden."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:59
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:61
msgctxt "@title"
msgid "Machine Settings"
msgstr "Geräteeinstellungen"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:78
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:80
msgctxt "@title:tab"
msgid "Printer"
msgstr "Drucker"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:97
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:99
msgctxt "@label"
msgid "Printer Settings"
msgstr "Druckereinstellungen"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:108
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:110
msgctxt "@label"
msgid "X (Width)"
msgstr "X (Breite)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:109
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:384
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:418
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:430
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:855
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:111
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:121
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:131
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:237
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:386
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:402
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:428
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:440
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:896
msgctxt "@label"
msgid "mm"
msgstr "mm"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:118
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:120
msgctxt "@label"
msgid "Y (Depth)"
msgstr "Y (Tiefe)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:128
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:130
msgctxt "@label"
msgid "Z (Height)"
msgstr "Z (Höhe)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:140
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:142
msgctxt "@label"
msgid "Build plate shape"
msgstr "Druckbettform"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:149
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:151
msgctxt "@option:check"
msgid "Origin at center"
msgstr "Ausgang in Mitte"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:157
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:159
msgctxt "@option:check"
msgid "Heated bed"
msgstr "Heizbares Bett"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:168
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:170
msgctxt "@label"
msgid "G-code flavor"
msgstr "G-Code-Variante"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:181
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:183
msgctxt "@label"
msgid "Printhead Settings"
msgstr "Druckkopfeinstellungen"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:191
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:193
msgctxt "@label"
msgid "X min"
msgstr "X min."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:192
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:194
msgctxt "@tooltip"
msgid "Distance from the left of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Abstand von der linken Seite des Druckkopfes zur Düsenmitte. Wird verwendet, um Kollisionen zwischen vorherigen Drucken und dem Druckkopf während des Druckmodus „Nacheinander“ zu vermeiden."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:201
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:203
msgctxt "@label"
msgid "Y min"
msgstr "Y min."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:202
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:204
msgctxt "@tooltip"
msgid "Distance from the front of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Abstand von der Vorderseite des Druckkopfes zur Düsenmitte. Wird verwendet, um Kollisionen zwischen vorherigen Drucken und dem Druckkopf während des Druckmodus „Nacheinander“ zu vermeiden."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:211
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:213
msgctxt "@label"
msgid "X max"
msgstr "X max."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:212
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:214
msgctxt "@tooltip"
msgid "Distance from the right of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Abstand von der rechten Seite des Druckkopfes zur Düsenmitte. Wird verwendet, um Kollisionen zwischen vorherigen Drucken und dem Druckkopf während des Druckmodus „Nacheinander“ zu vermeiden."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:221
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:223
msgctxt "@label"
msgid "Y max"
msgstr "Y max."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:222
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:224
msgctxt "@tooltip"
msgid "Distance from the rear of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Abstand von der Rückseite des Druckkopfes zur Düsenmitte. Wird verwendet, um Kollisionen zwischen vorherigen Drucken und dem Druckkopf während des Druckmodus „Nacheinander“ zu vermeiden."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:234
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
msgctxt "@label"
msgid "Gantry height"
msgstr "Brückenhöhe"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:238
msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "Der Höhenunterschied zwischen der Düsenspitze und dem Brückensystem (X- und Y-Achsen). Wird verwendet, um Kollisionen zwischen vorherigen Drucken und der Brücke zu verhindern, wenn im Modus „Nacheinander“ gedruckt wird."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:257
msgctxt "@label"
msgid "Number of Extruders"
msgstr "Anzahl Extruder"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:311
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:313
msgctxt "@label"
msgid "Start G-code"
msgstr "Start G-code"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:321
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:323
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very start."
msgstr "G-Code-Befehle, die zum Start ausgeführt werden sollen."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:330
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:332
msgctxt "@label"
msgid "End G-code"
msgstr "Ende G-code"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:340
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:342
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very end."
msgstr "G-Code-Befehle, die am Ende ausgeführt werden sollen."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:371
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:373
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Düseneinstellungen"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:385
msgctxt "@label"
msgid "Nozzle size"
msgstr "Düsengröße"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:399
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
msgctxt "@label"
msgid "Compatible material diameter"
msgstr "Kompatibler Materialdurchmesser"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:403
msgctxt "@tooltip"
msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
msgstr "Der Nenndurchmesser des durch den Drucker unterstützten Filaments. Der exakte Durchmesser wird durch das Material und/oder das Profil überschrieben."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:417
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:427
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "X-Versatz Düse"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:429
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:439
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Y-Versatz Düse"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:450
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+msgctxt "@label"
+msgid "Cooling Fan Number"
+msgstr "Kühllüfter-Nr."
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:452
+msgctxt "@label"
+msgid ""
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:472
msgctxt "@label"
msgid "Extruder Start G-code"
msgstr "G-Code Extruder-Start"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:468
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:490
msgctxt "@label"
msgid "Extruder End G-code"
msgstr "G-Code Extruder-Ende"
@@ -1427,12 +1428,20 @@ msgctxt "@info"
msgid "Could not connect to the Cura Package database. Please check your connection."
msgstr "Verbindung zur Cura Paket-Datenbank konnte nicht hergestellt werden. Bitte überprüfen Sie Ihre Verbindung."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:38
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:28
msgctxt "@title:tab"
msgid "Plugins"
msgstr "Plugins"
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:75
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:66
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:551
+msgctxt "@title:tab"
+msgid "Materials"
+msgstr "Materialien"
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
msgctxt "@label"
msgid "Version"
@@ -1448,8 +1457,14 @@ msgctxt "@label"
msgid "Author"
msgstr "Autor"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:97
+msgctxt "@label"
+msgid "Downloads"
+msgstr "Downloads"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:116
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:158
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:258
msgctxt "@label"
msgid "Unknown"
msgstr "Unbekannt"
@@ -1482,17 +1497,57 @@ msgctxt "@action:button"
msgid "Back"
msgstr "Zurück"
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:20
+msgctxt "@title:window"
+msgid "Confirm uninstall"
+msgstr "Deinstallieren bestätigen"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:50
+msgctxt "@text:window"
+msgid "You are uninstalling materials and/or profiles that are still in use. Confirming will reset the following materials/profiles to their defaults."
+msgstr "Sie sind dabei, Materialien und/oder Profile zu deinstallieren, die noch verwendet werden. Durch Bestätigen werden die folgenden Materialien/Profile auf ihre Standardeinstellungen zurückgesetzt."
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:51
+msgctxt "@text:window"
+msgid "Materials"
+msgstr "Materialien"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:52
+msgctxt "@text:window"
+msgid "Profiles"
+msgstr "Profile"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:89
+msgctxt "@action:button"
+msgid "Confirm"
+msgstr "Bestätigen"
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
msgctxt "@info"
msgid "You will need to restart Cura before changes in packages have effect."
msgstr "Cura muss neu gestartet werden, um die Änderungen der Pakete zu übernehmen."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:34
msgctxt "@info:button"
msgid "Quit Cura"
msgstr "Quit Cura"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Contributions"
+msgstr "Community-Beiträge"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Plugins"
+msgstr "Community-Plugins"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:43
+msgctxt "@label"
+msgid "Generic Materials"
+msgstr "Generische Materialien"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:56
msgctxt "@title:tab"
msgid "Installed"
msgstr "Installiert"
@@ -1535,12 +1590,12 @@ msgctxt "@action:button"
msgid "Decline"
msgstr "Ablehnen"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:23
msgctxt "@label"
msgid "Featured"
msgstr "Unterstützter"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:31
msgctxt "@label"
msgid "Compatibility"
msgstr "Kompatibilität"
@@ -1550,10 +1605,15 @@ msgctxt "@info"
msgid "Fetching packages..."
msgstr "Pakete werden abgeholt..."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:88
msgctxt "@label"
-msgid "Contact"
-msgstr "Kontakt"
+msgid "Website"
+msgstr "Website"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:94
+msgctxt "@label"
+msgid "Email"
+msgstr "E-Mail"
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
msgctxt "@info:tooltip"
@@ -1566,48 +1626,88 @@ msgid "Changelog"
msgstr "Änderungsprotokoll"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:84
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:56
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:464
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:509
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:185
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:53
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:467
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:514
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:121
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:166
#: /home/ruben/Projects/Cura/resources/qml/EngineLog.qml:38
msgctxt "@action:button"
msgid "Close"
msgstr "Schließen"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:22
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:31
+msgctxt "@title"
+msgid "Update Firmware"
+msgstr "Firmware aktualisieren"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:39
+msgctxt "@label"
+msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
+msgstr "Die Firmware ist der Teil der Software, der direkt auf Ihrem 3D-Drucker läuft. Diese Firmware kontrolliert die Schrittmotoren, reguliert die Temperatur und sorgt letztlich dafür, dass Ihr Drucker funktioniert."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:46
+msgctxt "@label"
+msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
+msgstr "Die mit neuen Druckern gelieferte Firmware funktioniert, allerdings enthalten neue Versionen üblicherweise mehr Funktionen und Verbesserungen."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:58
+msgctxt "@action:button"
+msgid "Automatically upgrade Firmware"
+msgstr "Firmware automatisch aktualisieren"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:69
+msgctxt "@action:button"
+msgid "Upload custom Firmware"
+msgstr "Benutzerdefinierte Firmware hochladen"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:83
+msgctxt "@label"
+msgid "Firmware can not be updated because there is no connection with the printer."
+msgstr "Firmware kann nicht aktualisiert werden, da keine Verbindung zum Drucker besteht."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:91
+msgctxt "@label"
+msgid "Firmware can not be updated because the connection with the printer does not support upgrading firmware."
+msgstr "Firmware kann nicht aktualisiert werden, da die Verbindung zum Drucker die Firmware-Aktualisierung nicht unterstützt."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:98
+msgctxt "@title:window"
+msgid "Select custom firmware"
+msgstr "Benutzerdefinierte Firmware wählen"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:119
msgctxt "@title:window"
msgid "Firmware Update"
msgstr "Firmware-Aktualisierung"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:42
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:143
msgctxt "@label"
msgid "Updating firmware."
msgstr "Die Firmware wird aktualisiert."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:44
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:145
msgctxt "@label"
msgid "Firmware update completed."
msgstr "Firmware-Aktualisierung abgeschlossen."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:46
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:147
msgctxt "@label"
msgid "Firmware update failed due to an unknown error."
msgstr "Die Firmware-Aktualisierung ist aufgrund eines unbekannten Fehlers fehlgeschlagen."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:48
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:149
msgctxt "@label"
msgid "Firmware update failed due to an communication error."
msgstr "Die Firmware-Aktualisierung ist aufgrund eines Kommunikationsfehlers fehlgeschlagen."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:50
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:151
msgctxt "@label"
msgid "Firmware update failed due to an input/output error."
msgstr "Die Firmware-Aktualisierung ist aufgrund eines Eingabe-/Ausgabefehlers fehlgeschlagen."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:52
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:153
msgctxt "@label"
msgid "Firmware update failed due to missing firmware."
msgstr "Die Firmware-Aktualisierung ist aufgrund von fehlender Firmware fehlgeschlagen."
@@ -1617,22 +1717,22 @@ msgctxt "@title:window"
msgid "User Agreement"
msgstr "Benutzervereinbarung"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:57
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:46
msgctxt "@window:title"
msgid "Existing Connection"
msgstr "Vorhandene Verbindung"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:59
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:48
msgctxt "@message:text"
msgid "This printer/group is already added to Cura. Please select another printer/group."
-msgstr "Diese/r Drucker/Gruppe wurde bereits zu Cura hinzugefügt. Wählen Sie bitte eine/n andere/n Drucker/Gruppe"
+msgstr "Diese/r Drucker/Gruppe wurde bereits zu Cura hinzugefügt. Wählen Sie bitte eine/n andere/n Drucker/Gruppe."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:76
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:65
msgctxt "@title:window"
msgid "Connect to Networked Printer"
msgstr "Anschluss an vernetzten Drucker"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:86
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:75
msgctxt "@label"
msgid ""
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
@@ -1640,333 +1740,395 @@ msgid ""
"Select your printer from the list below:"
msgstr "Um über das Netzwerk direkt auf Ihrem Drucker zu drucken, stellen Sie bitte sicher, dass der Drucker mit dem Netzwerkkabel verbunden ist oder verbinden Sie Ihren Drucker mit Ihrem WLAN-Netzwerk. Wenn Sie Cura nicht mit Ihrem Drucker verbinden, können Sie dennoch ein USB-Laufwerk für die Übertragung von G-Code-Dateien auf Ihren Drucker verwenden.\n\nWählen Sie Ihren Drucker aus der folgenden Liste:"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:85
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
msgctxt "@action:button"
msgid "Add"
msgstr "Hinzufügen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:95
msgctxt "@action:button"
msgid "Edit"
msgstr "Bearbeiten"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:128
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:48
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:117
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:132
msgctxt "@action:button"
msgid "Remove"
msgstr "Entfernen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:125
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:114
msgctxt "@action:button"
msgid "Refresh"
msgstr "Aktualisieren"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:207
msgctxt "@label"
msgid "If your printer is not listed, read the network printing troubleshooting guide"
msgstr "Wenn Ihr Drucker nicht aufgeführt ist, lesen Sie die Anleitung für Fehlerbehebung für Netzwerkdruck"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:245
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:234
msgctxt "@label"
msgid "Type"
msgstr "Typ"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:282
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:271
msgctxt "@label"
msgid "Firmware version"
msgstr "Firmware-Version"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:294
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:283
msgctxt "@label"
msgid "Address"
msgstr "Adresse"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:316
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:305
msgctxt "@label"
-msgid "This printer is not set up to host a group of Ultimaker 3 printers."
-msgstr "Dieser Drucker ist nicht eingerichtet um eine Gruppe von Ultimaker 3 Druckern anzusteuern."
+msgid "This printer is not set up to host a group of printers."
+msgstr "Dieser Drucker ist nicht eingerichtet um eine Gruppe von Druckern anzusteuern."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:320
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:309
msgctxt "@label"
-msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
-msgstr "Dieser Drucker steuert eine Gruppe von %1 Ultimaker 3 Druckern an."
+msgid "This printer is the host for a group of %1 printers."
+msgstr "Dieser Drucker steuert eine Gruppe von %1 Druckern an."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:330
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:319
msgctxt "@label"
msgid "The printer at this address has not yet responded."
msgstr "Der Drucker unter dieser Adresse hat nicht reagiert."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:335
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:39
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:324
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:42
msgctxt "@action:button"
msgid "Connect"
msgstr "Verbinden"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:349
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:338
msgctxt "@title:window"
msgid "Printer Address"
msgstr "Druckeradresse"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:377
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:361
msgctxt "@alabel"
msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Geben Sie die IP-Adresse oder den Hostnamen Ihres Druckers auf dem Netzwerk ein."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:407
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:390
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:132
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
msgstr "OK"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:30
-msgctxt "@title:window"
-msgid "Print over network"
-msgstr "Drucken über Netzwerk"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:61
-msgctxt "@label"
-msgid "Printer selection"
-msgstr "Druckerauswahl"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:100
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:44
msgctxt "@action:button"
msgid "Print"
msgstr "Drucken"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:36
-msgctxt "@label: arg 1 is group name"
-msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
-msgstr "%1 ist nicht für das Hosten einer Gruppe verbundener Ultimaker 3-Drucker eingerichtet"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:47
+msgctxt "@title:window"
+msgid "Print over network"
+msgstr "Drucken über Netzwerk"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
-msgctxt "@label link to connect manager"
-msgid "Add/Remove printers"
-msgstr "Drucker hinzufügen/entfernen"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:79
+msgctxt "@label"
+msgid "Printer selection"
+msgstr "Druckerauswahl"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
-msgctxt "@info:tooltip"
-msgid "Opens the print jobs page with your default web browser."
-msgstr "Öffnet die Seite für Druckaufträge mit Ihrem Standard-Webbrowser."
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:173
+msgctxt "@label"
+msgid "Not available"
+msgstr "Nicht verfügbar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:15
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:130
-msgctxt "@action:button"
-msgid "View print jobs"
-msgstr "Druckaufträge anzeigen"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:175
+msgctxt "@label"
+msgid "Unreachable"
+msgstr "Nicht erreichbar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:37
-msgctxt "@label:status"
-msgid "Preparing to print"
-msgstr "Vorb. für den Druck"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:39
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:263
-msgctxt "@label:status"
-msgid "Printing"
-msgstr "Drucken"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:41
-msgctxt "@label:status"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:180
+msgctxt "@label"
msgid "Available"
msgstr "Verfügbar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:37
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:44
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:46
msgctxt "@label:status"
-msgid "Lost connection with the printer"
-msgstr "Verbindung zum Drucker wurde unterbrochen"
+msgid "Aborted"
+msgstr "Abgebrochen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:45
-msgctxt "@label:status"
-msgid "Unavailable"
-msgstr "Nicht verfügbar"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label:status"
-msgid "Unknown"
-msgstr "Unbekannt"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:249
-msgctxt "@label:status"
-msgid "Disabled"
-msgstr "Deaktiviert"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:265
-msgctxt "@label:status"
-msgid "Reserved"
-msgstr "Reserviert"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:268
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:39
msgctxt "@label:status"
msgid "Finished"
msgstr "Beendet"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:271
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:392
-msgctxt "@label"
-msgid "Preparing to print"
-msgstr "Vorbereitung für den Druck"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:42
+msgctxt "@label:status"
+msgid "Preparing"
+msgstr "Vorbereitung"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:273
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:48
+msgctxt "@label:status"
+msgid "Pausing"
+msgstr "Wird pausiert"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:52
+msgctxt "@label:status"
+msgid "Resuming"
+msgstr "Wird fortgesetzt"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:54
msgctxt "@label:status"
msgid "Action required"
msgstr "Handlung erforderlich"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:276
-msgctxt "@label:status"
-msgid "Paused"
-msgstr "Pausiert"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:278
-msgctxt "@label:status"
-msgid "Resuming"
-msgstr "Wird fortgesetzt ..."
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:280
-msgctxt "@label:status"
-msgid "Print aborted"
-msgstr "Drucken wurde abgebrochen"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:373
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:394
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:213
msgctxt "@label"
-msgid "Not accepting print jobs"
-msgstr "Akzeptiert keine Druckaufträge"
+msgid "Waiting for: Unavailable printer"
+msgstr "Warten auf: Drucker nicht verfügbar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:387
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:215
msgctxt "@label"
-msgid "Finishes at: "
-msgstr "Endet um: "
+msgid "Waiting for: First available"
+msgstr "Warten auf: Ersten verfügbaren"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:389
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:217
msgctxt "@label"
-msgid "Clear build plate"
-msgstr "Druckplatte räumen"
+msgid "Waiting for: "
+msgstr "Warten auf: "
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:396
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:299
msgctxt "@label"
-msgid "Waiting for configuration change"
-msgstr "Warten auf eine Konfigurationsänderung"
+msgid "Configuration change"
+msgstr "Konfigurationsänderung"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:63
-msgctxt "@title"
-msgid "Print jobs"
-msgstr "Druckaufträge"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:93
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:365
msgctxt "@label"
-msgid "Printing"
-msgstr "Drucken"
+msgid "The assigned printer, %1, requires the following configuration change(s):"
+msgstr "Der zugewiesene Drucker %1 erfordert die folgende(n) Konfigurationsänderung(en):"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:111
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:367
+msgctxt "@label"
+msgid "The printer %1 is assigned, but the job contains an unknown material configuration."
+msgstr "Der Drucker %1 wurde zugewiesen, allerdings enthält der Auftrag eine unbekannte Materialkonfiguration."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:375
+msgctxt "@label"
+msgid "Change material %1 from %2 to %3."
+msgstr "Material %1 von %2 auf %3 wechseln."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:378
+msgctxt "@label"
+msgid "Load %3 as material %1 (This cannot be overridden)."
+msgstr "%3 als Material %1 laden (Dies kann nicht übergangen werden)."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:381
+msgctxt "@label"
+msgid "Change print core %1 from %2 to %3."
+msgstr "Print Core %1 von %2 auf %3 wechseln."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:384
+msgctxt "@label"
+msgid "Change build plate to %1 (This cannot be overridden)."
+msgstr "Druckplatte auf %1 wechseln (Dies kann nicht übergangen werden)."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:404
+msgctxt "@label"
+msgid "Override"
+msgstr "Überschreiben"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:432
+msgctxt "@label"
+msgid "Starting a print job with an incompatible configuration could damage your 3D printer. Are you sure you want to override the configuration and print %1?"
+msgstr "Das Starten eines Druckauftrags mit einer inkompatiblen Konfiguration kann Ihren 3D-Drucker beschädigen. Möchten Sie die Konfiguration wirklich überschreiben und %1 drucken?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:435
+msgctxt "@window:title"
+msgid "Override configuration configuration and start print"
+msgstr "Konfiguration überschreiben und Druck starten"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:466
+msgctxt "@label"
+msgid "Glass"
+msgstr "Glas"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:469
+msgctxt "@label"
+msgid "Aluminum"
+msgstr "Aluminium"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:39
+msgctxt "@label link to connect manager"
+msgid "Manage queue"
+msgstr "Warteschlange verwalten"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:60
msgctxt "@label"
msgid "Queued"
msgstr "In Warteschlange"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:170
-msgctxt "@label:title"
-msgid "Printers"
-msgstr "Drucker"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:36
+msgctxt "@label"
+msgid "Printing"
+msgstr "Drucken"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:224
-msgctxt "@action:button"
-msgid "View printers"
-msgstr "Drucker anzeigen"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:49
+msgctxt "@label link to connect manager"
+msgid "Manage printers"
+msgstr "Drucker verwalten"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:38
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:115
+msgctxt "@label"
+msgid "Move to top"
+msgstr "Vorziehen"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:124
+msgctxt "@label"
+msgid "Delete"
+msgstr "Löschen"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
+msgctxt "@label"
+msgid "Resume"
+msgstr "Zurückkehren"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
+msgctxt "@label"
+msgid "Pause"
+msgstr "Pausieren"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:146
+msgctxt "@label"
+msgid "Abort"
+msgstr "Abbrechen"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:178
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to move %1 to the top of the queue?"
+msgstr "Soll dieser %1 wirklich an den Anfang der Warteschlange vorgezogen werden?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:179
+msgctxt "@window:title"
+msgid "Move print job to top"
+msgstr "Druckauftrag vorziehen"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:188
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to delete %1?"
+msgstr "Soll %1 wirklich gelöscht werden?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:189
+msgctxt "@window:title"
+msgid "Delete print job"
+msgstr "Druckauftrag löschen"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:198
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to abort %1?"
+msgstr "Möchten Sie %1 wirklich abbrechen?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
+msgctxt "@window:title"
+msgid "Abort print"
+msgstr "Drucken abbrechen"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:43
msgctxt "@info:tooltip"
msgid "Connect to a printer"
msgstr "Mit einem Drucker verbinden"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:117
-msgctxt "@info:tooltip"
-msgid "Load the configuration of the printer into Cura"
-msgstr "Die Druckerkonfiguration in Cura laden"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:118
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:121
msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Konfiguration aktivieren"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:122
+msgctxt "@info:tooltip"
+msgid "Load the configuration of the printer into Cura"
+msgstr "Die Druckerkonfiguration in Cura laden"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:130
msgctxt "@label"
msgid "Color scheme"
msgstr "Farbschema"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:145
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Materialfarbe"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:149
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Linientyp"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:153
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr "Vorschub"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:157
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr "Schichtdicke"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:198
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Kompatibilitätsmodus"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:284
msgctxt "@label"
msgid "Show Travels"
msgstr "Bewegungen anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:290
msgctxt "@label"
msgid "Show Helpers"
msgstr "Helfer anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:296
msgctxt "@label"
msgid "Show Shell"
msgstr "Gehäuse anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:302
msgctxt "@label"
msgid "Show Infill"
msgstr "Füllung anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:355
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Nur obere Schichten anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:366
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "5 detaillierte Schichten oben anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:379
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Oben/Unten"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:383
msgctxt "@label"
msgid "Inner Wall"
msgstr "Innenwand"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:448
msgctxt "@label"
msgid "min"
msgstr "min."
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:500
msgctxt "@label"
msgid "max"
msgstr "max."
@@ -1981,17 +2143,17 @@ msgctxt "@label"
msgid "Post Processing Scripts"
msgstr "Skripts Nachbearbeitung"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:225
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:227
msgctxt "@action"
msgid "Add a script"
msgstr "Ein Skript hinzufügen"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:271
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:273
msgctxt "@label"
msgid "Settings"
msgstr "Einstellungen"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:474
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:477
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "Aktive Skripts Nachbearbeitung ändern"
@@ -2009,7 +2171,7 @@ msgstr "Cura sendet anonyme Daten an Ultimaker, um die Druckqualität und Benutz
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:101
msgctxt "@text:window"
msgid "I don't want to send these data"
-msgstr "Ich möchte diese Daten nicht senden."
+msgstr "Ich möchte diese Daten nicht senden"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:111
msgctxt "@text:window"
@@ -2054,7 +2216,7 @@ msgstr "Breite (mm)"
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:103
msgctxt "@info:tooltip"
msgid "The depth in millimeters on the build plate"
-msgstr "Die Tiefe der Druckplatte in Millimetern."
+msgstr "Die Tiefe der Druckplatte in Millimetern"
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:108
msgctxt "@action:label"
@@ -2086,53 +2248,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Glättung"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:37
msgctxt "@label"
msgid "Mesh Type"
msgstr "Mesh-Typ"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:68
msgctxt "@label"
msgid "Normal model"
msgstr "Normales Modell"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:75
msgctxt "@label"
msgid "Print as support"
msgstr "Als Stützstruktur drucken"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:83
msgctxt "@label"
msgid "Don't support overlap with other models"
msgstr "Keine Überlappung mit anderen Modellen unterstützen"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:91
msgctxt "@label"
msgid "Modify settings for overlap with other models"
msgstr "Einstellungen für Überlappung mit anderen Modellen bearbeiten"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:99
msgctxt "@label"
msgid "Modify settings for infill of other models"
msgstr "Einstellungen für Füllung von anderen Modellen bearbeiten"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:342
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:347
msgctxt "@action:button"
msgid "Select settings"
msgstr "Einstellungen wählen"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:384
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:389
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Einstellungen für die benutzerdefinierte Anpassung dieses Modells wählen"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:432
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:437
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:98
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Filtern..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:446
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:451
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Alle anzeigen"
@@ -2154,13 +2316,13 @@ msgid "Create new"
msgstr "Neu erstellen"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:70
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:68
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:72
msgctxt "@action:title"
msgid "Summary - Cura Project"
msgstr "Zusammenfassung – Cura-Projekt"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:92
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:96
msgctxt "@action:label"
msgid "Printer settings"
msgstr "Druckereinstellungen"
@@ -2177,18 +2339,19 @@ msgid "Update"
msgstr "Aktualisierung"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:143
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:105
msgctxt "@action:label"
msgid "Type"
msgstr "Typ"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:159
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
msgctxt "@action:label"
msgid "Printer Group"
msgstr "Druckergruppe"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:180
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:192
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:196
msgctxt "@action:label"
msgid "Profile settings"
msgstr "Profileinstellungen"
@@ -2200,19 +2363,20 @@ msgstr "Wie soll der Konflikt im Profil gelöst werden?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:216
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:308
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:216
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:220
msgctxt "@action:label"
msgid "Name"
msgstr "Name"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:231
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:200
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:204
msgctxt "@action:label"
msgid "Not in profile"
msgstr "Nicht im Profil"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:236
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:209
msgctxt "@action:label"
msgid "%1 override"
msgid_plural "%1 overrides"
@@ -2242,7 +2406,7 @@ msgid "How should the conflict in the material be resolved?"
msgstr "Wie soll der Konflikt im Material gelöst werden?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:327
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:233
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:237
msgctxt "@action:label"
msgid "Setting visibility"
msgstr "Sichtbarkeit einstellen"
@@ -2253,13 +2417,13 @@ msgid "Mode"
msgstr "Modus"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:352
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:246
msgctxt "@action:label"
msgid "Visible settings:"
msgstr "Sichtbare Einstellungen:"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:357
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:247
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:251
msgctxt "@action:label"
msgid "%1 out of %2"
msgstr "%1 von %2"
@@ -2315,40 +2479,10 @@ msgctxt "@action:button"
msgid "Move to Next Position"
msgstr "Gehe zur nächsten Position"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:30
-msgctxt "@title"
-msgid "Upgrade Firmware"
-msgstr "Firmware aktualisieren"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:41
-msgctxt "@label"
-msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
-msgstr "Die Firmware ist der Teil der Software, der direkt auf Ihrem 3D-Drucker läuft. Diese Firmware kontrolliert die Schrittmotoren, reguliert die Temperatur und sorgt letztlich dafür, dass Ihr Drucker funktioniert."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:51
-msgctxt "@label"
-msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
-msgstr "Die mit neuen Druckern gelieferte Firmware funktioniert, allerdings enthalten neue Versionen üblicherweise mehr Funktionen und Verbesserungen."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:65
-msgctxt "@action:button"
-msgid "Automatically upgrade Firmware"
-msgstr "Firmware automatisch aktualisieren"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:75
-msgctxt "@action:button"
-msgid "Upload custom Firmware"
-msgstr "Benutzerdefinierte Firmware hochladen"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:87
-msgctxt "@title:window"
-msgid "Select custom firmware"
-msgstr "Benutzerdefinierte Firmware wählen"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:37
msgctxt "@label"
msgid "Please select any upgrades made to this Ultimaker Original"
-msgstr "Wählen Sie bitte alle Upgrades für dieses Ultimaker-Original."
+msgstr "Wählen Sie bitte alle Upgrades für dieses Ultimaker-Original"
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:45
msgctxt "@label"
@@ -2363,7 +2497,7 @@ msgstr "Drucker prüfen"
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:39
msgctxt "@label"
msgid "It's a good idea to do a few sanity checks on your Ultimaker. You can skip this step if you know your machine is functional"
-msgstr "Sie sollten einige Sanity Checks bei Ihrem Ultimaker durchführen. Sie können diesen Schritt überspringen, wenn Sie wissen, dass Ihr Gerät funktionsfähig ist."
+msgstr "Sie sollten einige Sanity Checks bei Ihrem Ultimaker durchführen. Sie können diesen Schritt überspringen, wenn Sie wissen, dass Ihr Gerät funktionsfähig ist"
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:53
msgctxt "@action:button"
@@ -2492,27 +2626,11 @@ msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr "Bitte den Ausdruck entfernen"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
-msgctxt "@label:"
-msgid "Pause"
-msgstr "Pausieren"
-
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
-msgctxt "@label:"
-msgid "Resume"
-msgstr "Zurückkehren"
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:325
-msgctxt "@label:"
+msgctxt "@label"
msgid "Abort Print"
msgstr "Drucken abbrechen"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
-msgctxt "@window:title"
-msgid "Abort print"
-msgstr "Drucken abbrechen"
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:337
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
@@ -2546,19 +2664,17 @@ msgid "Customized"
msgstr "Angepasst"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:634
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:637
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Stets nachfragen"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:635
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Verwerfen und zukünftig nicht mehr nachfragen"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Übernehmen und zukünftig nicht mehr nachfragen"
@@ -2578,101 +2694,179 @@ msgctxt "@action:button"
msgid "Create New Profile"
msgstr "Neues Profil erstellen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:65
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:71
msgctxt "@title"
msgid "Information"
msgstr "Informationen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:94
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:100
msgctxt "@title:window"
msgid "Confirm Diameter Change"
msgstr "Änderung Durchmesser bestätigen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:101
msgctxt "@label (%1 is a number)"
msgid "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?"
msgstr "Der neue Filament-Durchmesser wurde auf %1 mm eingestellt, was nicht kompatibel mit dem aktuellen Extruder ist. Möchten Sie fortfahren?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:133
msgctxt "@label"
msgid "Display Name"
msgstr "Namen anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:143
msgctxt "@label"
msgid "Brand"
msgstr "Marke"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:153
msgctxt "@label"
msgid "Material Type"
msgstr "Materialtyp"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:162
msgctxt "@label"
msgid "Color"
msgstr "Farbe"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:212
msgctxt "@label"
msgid "Properties"
msgstr "Eigenschaften"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:214
msgctxt "@label"
msgid "Density"
msgstr "Dichte"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:229
msgctxt "@label"
msgid "Diameter"
msgstr "Durchmesser"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:253
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:263
msgctxt "@label"
msgid "Filament Cost"
msgstr "Filamentkosten"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:269
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:280
msgctxt "@label"
msgid "Filament weight"
msgstr "Filamentgewicht"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:298
msgctxt "@label"
msgid "Filament length"
msgstr "Filamentlänge"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:295
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:307
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Kosten pro Meter"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:309
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:321
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
-msgstr "Dieses Material ist mit %1 verknüpft und teilt sich damit einige seiner Eigenschaften"
+msgstr "Dieses Material ist mit %1 verknüpft und teilt sich damit einige seiner Eigenschaften."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:316
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:328
msgctxt "@label"
msgid "Unlink Material"
msgstr "Material trennen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:327
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:339
msgctxt "@label"
msgid "Description"
msgstr "Beschreibung"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:352
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Haftungsinformationen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:366
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:378
msgctxt "@label"
msgid "Print settings"
msgstr "Druckeinstellungen"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:84
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
+msgctxt "@action:button"
+msgid "Activate"
+msgstr "Aktivieren"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:101
+msgctxt "@action:button"
+msgid "Create"
+msgstr "Erstellen"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:114
+msgctxt "@action:button"
+msgid "Duplicate"
+msgstr "Duplizieren"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
+msgctxt "@action:button"
+msgid "Import"
+msgstr "Import"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
+msgctxt "@action:button"
+msgid "Export"
+msgstr "Export"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:203
+msgctxt "@action:label"
+msgid "Printer"
+msgstr "Drucker"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:262
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
+msgctxt "@title:window"
+msgid "Confirm Remove"
+msgstr "Entfernen bestätigen"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:263
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
+msgctxt "@label (%1 is object name)"
+msgid "Are you sure you wish to remove %1? This cannot be undone!"
+msgstr "Möchten Sie %1 wirklich entfernen? Dies kann nicht rückgängig gemacht werden!"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:285
+msgctxt "@title:window"
+msgid "Import Material"
+msgstr "Material importieren"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:286
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Could not import material %1: %2"
+msgstr "Material konnte nicht importiert werden %1: %2"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:290
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully imported material %1"
+msgstr "Material wurde erfolgreich importiert %1"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:308
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:316
+msgctxt "@title:window"
+msgid "Export Material"
+msgstr "Material exportieren"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:320
+msgctxt "@info:status Don't translate the XML tags and !"
+msgid "Failed to export material to %1: %2"
+msgstr "Exportieren des Materials nach %1: %2 schlug fehl"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:326
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully exported material to %1"
+msgstr "Material erfolgreich nach %1 exportiert"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:14
msgctxt "@title:tab"
msgid "Setting Visibility"
@@ -2709,289 +2903,287 @@ msgid "Unit"
msgstr "Einheit"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:544
msgctxt "@title:tab"
msgid "General"
msgstr "Allgemein"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:142
msgctxt "@label"
msgid "Interface"
msgstr "Schnittstelle"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:153
msgctxt "@label"
msgid "Language:"
msgstr "Sprache:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:220
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Currency:"
msgstr "Währung:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:235
msgctxt "@label"
msgid "Theme:"
msgstr "Thema:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:294
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:292
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Die Anwendung muss neu gestartet werden, um die Änderungen zu übernehmen."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:311
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:309
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Bei Änderung der Einstellungen automatisch schneiden."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:317
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Automatisch schneiden"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:333
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:331
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Viewport-Verhalten"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:339
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Nicht gestützte Bereiche des Modells in rot hervorheben. Ohne Support werden diese Bereiche nicht korrekt gedruckt."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:348
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Überhang anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:357
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:355
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
msgstr "Bewegt die Kamera, bis sich das Modell im Mittelpunkt der Ansicht befindet, wenn ein Modell ausgewählt wurde"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:360
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "Zentrieren Sie die Kamera, wenn das Element ausgewählt wurde"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:371
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:369
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "Soll das standardmäßige Zoom-Verhalten von Cura umgekehrt werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:376
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:374
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "Kehren Sie die Richtung des Kamera-Zooms um."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "Soll das Zoomen in Richtung der Maus erfolgen?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "In Mausrichtung zoomen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:401
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "Sollen Modelle auf der Plattform so verschoben werden, dass sie sich nicht länger überschneiden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:406
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "Stellen Sie sicher, dass die Modelle getrennt gehalten werden"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:413
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "Sollen Modelle auf der Plattform so nach unten verschoben werden, dass sie die Druckplatte berühren?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:420
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:418
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Setzt Modelle automatisch auf der Druckplatte ab"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
msgctxt "@info:tooltip"
msgid "Show caution message in g-code reader."
msgstr "Warnmeldung im G-Code-Reader anzeigen."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:439
msgctxt "@option:check"
msgid "Caution message in g-code reader"
msgstr "Warnmeldung in G-Code-Reader"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:449
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:447
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "Soll die Schicht in den Kompatibilitätsmodus gezwungen werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:452
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "Schichtenansicht Kompatibilitätsmodus erzwingen (Neustart erforderlich)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:470
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:468
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Dateien öffnen und speichern"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:477
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:475
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "Sollen Modelle an das Erstellungsvolumen angepasst werden, wenn sie zu groß sind?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:482
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:480
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Große Modelle anpassen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Ein Modell kann extrem klein erscheinen, wenn seine Maßeinheit z. B. in Metern anstelle von Millimetern angegeben ist. Sollen diese Modelle hoch skaliert werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:497
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:495
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Extrem kleine Modelle skalieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@info:tooltip"
msgid "Should models be selected after they are loaded?"
msgstr "Sollten Modelle gewählt werden, nachdem sie geladen wurden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:510
msgctxt "@option:check"
msgid "Select models when loaded"
msgstr "Modelle wählen, nachdem sie geladen wurden"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "Soll ein Präfix anhand des Druckernamens automatisch zum Namen des Druckauftrags hinzugefügt werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:527
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:525
msgctxt "@option:check"
msgid "Add machine prefix to job name"
-msgstr "Geräte-Präfix zu Auftragsnamen hinzufügen."
+msgstr "Geräte-Präfix zu Auftragsnamen hinzufügen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "Soll beim Speichern einer Projektdatei eine Zusammenfassung angezeigt werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:541
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:539
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Dialog Zusammenfassung beim Speichern eines Projekts anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:551
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:549
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Standardverhalten beim Öffnen einer Projektdatei"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:559
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:557
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Standardverhalten beim Öffnen einer Projektdatei: "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
msgctxt "@option:openProject"
-msgid "Always ask"
-msgstr "Immer nachfragen"
+msgid "Always ask me this"
+msgstr "Stets nachfragen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:574
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:572
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Immer als Projekt öffnen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:575
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Modelle immer importieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:611
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:609
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Wenn Sie Änderungen für ein Profil vorgenommen haben und zu einem anderen Profil gewechselt sind, wird ein Dialog angezeigt, der hinterfragt, ob Sie Ihre Änderungen beibehalten möchten oder nicht; optional können Sie ein Standardverhalten wählen, sodass dieser Dialog nicht erneut angezeigt wird."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:618
msgctxt "@label"
-msgid "Override Profile"
-msgstr "Profil überschreiben"
+msgid "Profiles"
+msgstr "Profile"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:670
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:623
+msgctxt "@window:text"
+msgid "Default behavior for changed setting values when switching to a different profile: "
+msgstr "Standardverhalten für geänderte Einstellungswerte beim Wechsel zu einem anderen Profil: "
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
+msgctxt "@option:discardOrKeep"
+msgid "Always discard changed settings"
+msgstr "Geänderte Einstellungen immer verwerfen"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:639
+msgctxt "@option:discardOrKeep"
+msgid "Always transfer changed settings to new profile"
+msgstr "Geänderte Einstellungen immer auf neues Profil übertragen"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:673
msgctxt "@label"
msgid "Privacy"
msgstr "Privatsphäre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:678
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:681
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "Soll Cura bei Programmstart nach Updates suchen?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:683
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:686
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Bei Start nach Updates suchen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:697
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "Sollen anonyme Daten über Ihren Druck an Ultimaker gesendet werden? Beachten Sie, dass keine Modelle, IP-Adressen oder andere personenbezogene Daten gesendet oder gespeichert werden."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:702
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "(Anonyme) Druckinformationen senden"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:711
msgctxt "@action:button"
msgid "More information"
msgstr "Mehr Informationen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:729
msgctxt "@label"
msgid "Experimental"
msgstr "Experimentell"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:733
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:736
msgctxt "@info:tooltip"
msgid "Use multi build plate functionality"
msgstr "Mehrfach-Druckplattenfunktion verwenden"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:738
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:741
msgctxt "@option:check"
msgid "Use multi build plate functionality (restart required)"
msgstr "Mehrfach-Druckplattenfunktion verwenden (Neustart erforderlich)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:748
-msgctxt "@info:tooltip"
-msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
-msgstr "Sollen neu geladene Modelle auf der Druckplatte angeordnet werden? In Verbindung mit Mehrfach-Druckplatte verwenden (EXPERIMENTELL)"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:753
-msgctxt "@option:check"
-msgid "Do not arrange objects on load"
-msgstr "Keine Objekte beim Laden anordnen"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:16
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:536
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:549
msgctxt "@title:tab"
msgid "Printers"
msgstr "Drucker"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:90
-msgctxt "@action:button"
-msgid "Activate"
-msgstr "Aktivieren"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:55
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:129
msgctxt "@action:button"
@@ -3009,7 +3201,7 @@ msgid "Connection:"
msgstr "Verbindung:"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:162
-#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:47
+#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:55
msgctxt "@info:status"
msgid "The printer is not connected."
msgstr "Der Drucker ist nicht verbunden."
@@ -3035,7 +3227,7 @@ msgid "Aborting print..."
msgstr "Drucken wird abgebrochen..."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:540
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:553
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Profile"
@@ -3050,18 +3242,6 @@ msgctxt "@label"
msgid "Duplicate"
msgstr "Duplizieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:145
-msgctxt "@action:button"
-msgid "Import"
-msgstr "Import"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:158
-msgctxt "@action:button"
-msgid "Export"
-msgstr "Export"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:174
msgctxt "@title:window"
msgid "Create Profile"
@@ -3072,18 +3252,6 @@ msgctxt "@title:window"
msgid "Duplicate Profile"
msgstr "Profil duplizieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:221
-msgctxt "@title:window"
-msgid "Confirm Remove"
-msgstr "Entfernen bestätigen"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:222
-msgctxt "@label (%1 is object name)"
-msgid "Are you sure you wish to remove %1? This cannot be undone!"
-msgstr "Möchten Sie %1 wirklich entfernen? Dies kann nicht rückgängig gemacht werden!"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:256
msgctxt "@title:window"
msgid "Rename Profile"
@@ -3104,228 +3272,200 @@ msgctxt "@label %1 is printer name"
msgid "Printer: %1"
msgstr "Drucker: %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Protected profiles"
msgstr "Geschützte Profile"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Custom profiles"
msgstr "Benutzerdefinierte Profile"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:468
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:480
msgctxt "@action:button"
msgid "Update profile with current settings/overrides"
msgstr "Profil mit aktuellen Einstellungen/Überschreibungen aktualisieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:475
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:487
msgctxt "@action:button"
msgid "Discard current changes"
msgstr "Aktuelle Änderungen verwerfen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:504
msgctxt "@action:label"
msgid "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below."
msgstr "Dieses Profil verwendet die vom Drucker festgelegten Standardeinstellungen, deshalb sind in der folgenden Liste keine Einstellungen/Überschreibungen enthalten."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:511
msgctxt "@action:label"
msgid "Your current settings match the selected profile."
msgstr "Ihre aktuellen Einstellungen stimmen mit dem gewählten Profil überein."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:518
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:530
msgctxt "@title:tab"
msgid "Global Settings"
msgstr "Globale Einstellungen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:40
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:538
-msgctxt "@title:tab"
-msgid "Materials"
-msgstr "Materialien"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:105
-msgctxt "@action:button"
-msgid "Create"
-msgstr "Erstellen"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:118
-msgctxt "@action:button"
-msgid "Duplicate"
-msgstr "Duplizieren"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:235
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:243
-msgctxt "@title:window"
-msgid "Import Material"
-msgstr "Material importieren"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:244
-msgctxt "@info:status Don't translate the XML tags or !"
-msgid "Could not import material %1: %2"
-msgstr "Material konnte nicht importiert werden %1: %2"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:248
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully imported material %1"
-msgstr "Material wurde erfolgreich importiert %1"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:266
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:274
-msgctxt "@title:window"
-msgid "Export Material"
-msgstr "Material exportieren"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:278
-msgctxt "@info:status Don't translate the XML tags and !"
-msgid "Failed to export material to %1: %2"
-msgstr "Exportieren des Materials nach %1: %2 schlug fehl"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:284
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully exported material to %1"
-msgstr "Material erfolgreich nach %1 exportiert"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:337
-msgctxt "@action:label"
-msgid "Printer"
-msgstr "Drucker"
-
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:896
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:953
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Drucker hinzufügen"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:195
msgctxt "@label"
msgid "Printer Name:"
msgstr "Druckername:"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:219
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Drucker hinzufügen"
+#: /home/ruben/Projects/Cura/resources/qml/JobSpecs.qml:84
+msgctxt "@text Print job name"
+msgid "Untitled"
+msgstr "Unbenannt"
+
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:15
msgctxt "@title:window"
msgid "About Cura"
msgstr "Über Cura"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:55
msgctxt "@label"
msgid "version: %1"
msgstr "Version: %1"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:56
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
msgctxt "@label"
msgid "End-to-end solution for fused filament 3D printing."
msgstr "Komplettlösung für den 3D-Druck mit geschmolzenem Filament."
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:82
msgctxt "@info:credit"
msgid ""
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
"Cura proudly uses the following open source projects:"
msgstr "Cura wurde von Ultimaker B.V. in Zusammenarbeit mit der Community entwickelt.\nCura verwendet mit Stolz die folgenden Open Source-Projekte:"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
msgctxt "@label"
msgid "Graphical user interface"
msgstr "Grafische Benutzerschnittstelle"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
msgctxt "@label"
msgid "Application framework"
msgstr "Anwendungsrahmenwerk"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
msgctxt "@label"
msgid "G-code generator"
msgstr "G-Code-Generator"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
msgctxt "@label"
msgid "Interprocess communication library"
msgstr "Bibliothek Interprozess-Kommunikation"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
msgctxt "@label"
msgid "Programming language"
msgstr "Programmiersprache"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:124
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
msgctxt "@label"
msgid "GUI framework"
msgstr "GUI-Rahmenwerk"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:125
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
msgctxt "@label"
msgid "GUI framework bindings"
msgstr "GUI-Rahmenwerk Einbindungen"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:126
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:140
msgctxt "@label"
msgid "C/C++ Binding library"
msgstr "C/C++ Einbindungsbibliothek"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:141
msgctxt "@label"
msgid "Data interchange format"
msgstr "Format Datenaustausch"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:142
msgctxt "@label"
msgid "Support library for scientific computing"
msgstr "Support-Bibliothek für wissenschaftliche Berechnung"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:129
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:143
msgctxt "@label"
msgid "Support library for faster math"
msgstr "Support-Bibliothek für schnelleres Rechnen"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:130
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:144
msgctxt "@label"
msgid "Support library for handling STL files"
msgstr "Support-Bibliothek für die Handhabung von STL-Dateien"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:145
+msgctxt "@label"
+msgid "Support library for handling planar objects"
+msgstr "Support-Bibliothek für die Handhabung von ebenen Objekten"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:146
+msgctxt "@label"
+msgid "Support library for handling triangular meshes"
+msgstr "Support-Bibliothek für die Handhabung von dreieckigen Netzen"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
+msgctxt "@label"
+msgid "Support library for analysis of complex networks"
+msgstr "Support-Bibliothek für die Analyse von komplexen Netzwerken"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
msgctxt "@label"
msgid "Support library for handling 3MF files"
msgstr "Support-Bibliothek für die Handhabung von 3MF-Dateien"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:149
+msgctxt "@label"
+msgid "Support library for file metadata and streaming"
+msgstr "Support-Bibliothek für Datei-Metadaten und Streaming"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:150
msgctxt "@label"
msgid "Serial communication library"
msgstr "Bibliothek für serielle Kommunikation"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:151
msgctxt "@label"
msgid "ZeroConf discovery library"
msgstr "Bibliothek für ZeroConf-Erkennung"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:152
msgctxt "@label"
msgid "Polygon clipping library"
msgstr "Bibliothek für Polygon-Beschneidung"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:153
msgctxt "@Label"
msgid "Python HTTP library"
msgstr "Bibliothek für Python HTTP"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:155
msgctxt "@label"
msgid "Font"
msgstr "Schriftart"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:156
msgctxt "@label"
msgid "SVG icons"
msgstr "SVG-Symbole"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:157
msgctxt "@label"
msgid "Linux cross-distribution application deployment"
msgstr "Distributionsunabhängiges Format für Linux-Anwendungen"
@@ -3335,7 +3475,7 @@ msgctxt "@label"
msgid "Profile:"
msgstr "Profil:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
@@ -3343,53 +3483,53 @@ msgid ""
"Click to open the profile manager."
msgstr "Einige Einstellungs-/Überschreibungswerte unterscheiden sich von den im Profil gespeicherten Werten.\n\nKlicken Sie, um den Profilmanager zu öffnen."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:200
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Suchen..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:544
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:545
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Werte für alle Extruder kopieren"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:553
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:554
msgctxt "@action:menu"
msgid "Copy all changed values to all extruders"
msgstr "Alle geänderten Werte für alle Extruder kopieren"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:568
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:591
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Diese Einstellung ausblenden"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:609
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "Diese Einstellung ausblenden"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:590
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:613
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Diese Einstellung weiterhin anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:614
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:426
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:637
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Sichtbarkeit einstellen wird konfiguriert..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:644
msgctxt "@action:inmenu"
msgid "Collapse All"
msgstr "Alle verkleinern"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:649
msgctxt "@action:inmenu"
msgid "Expand All"
msgstr "Alle vergrößern"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:253
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
@@ -3407,17 +3547,17 @@ msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Wird beeinflusst von"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:155
msgctxt "@label"
msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
msgstr "Diese Einstellung wird stets zwischen allen Extrudern geteilt. Eine Änderung ändert den Wert für alle Extruder."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:158
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "Der Wert wird von Pro-Extruder-Werten gelöst "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:189
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
@@ -3425,7 +3565,7 @@ msgid ""
"Click to restore the value of the profile."
msgstr "Diese Einstellung hat einen vom Profil abweichenden Wert.\n\nKlicken Sie, um den Wert des Profils wiederherzustellen."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:281
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
@@ -3469,7 +3609,7 @@ msgid "Send a custom G-code command to the connected printer. Press 'enter' to s
msgstr "Einen benutzerdefinierten G-Code-Befehl an den verbundenen Drucker senden. „Eingabe“ drücken, um den Befehl zu senden."
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ExtruderBox.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:268
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:272
msgctxt "@label"
msgid "Extruder"
msgstr "Extruder"
@@ -3522,7 +3662,7 @@ msgid "The nozzle inserted in this extruder."
msgstr "Die in diesem Extruder eingesetzte Düse."
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/HeatedBedBox.qml:25
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:489
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:493
msgctxt "@label"
msgid "Build plate"
msgstr "Druckbett"
@@ -3547,6 +3687,21 @@ msgctxt "@tooltip of pre-heat"
msgid "Heat the bed in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the bed to heat up when you're ready to print."
msgstr "Heizen Sie das Bett vor Druckbeginn auf. Sie können Ihren Druck während des Aufheizens weiter anpassen und müssen nicht warten, bis das Bett aufgeheizt ist, wenn Sie druckbereit sind."
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:13
+msgctxt "@label:category menu label"
+msgid "Material"
+msgstr "Material"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:37
+msgctxt "@label:category menu label"
+msgid "Favorites"
+msgstr "Favoriten"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:61
+msgctxt "@label:category menu label"
+msgid "Generic"
+msgstr "Generisch"
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/PrinterMenu.qml:25
msgctxt "@label:category menu label"
msgid "Network enabled printers"
@@ -3562,12 +3717,12 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Ansicht"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:39
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:42
msgctxt "@action:inmenu menubar:view"
msgid "&Camera position"
msgstr "&Kameraposition"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:58
msgctxt "@action:inmenu menubar:view"
msgid "&Build plate"
msgstr "&Druckplatte"
@@ -3577,12 +3732,12 @@ msgctxt "@action:inmenu"
msgid "Visible Settings"
msgstr "Sichtbare Einstellungen"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:42
msgctxt "@action:inmenu"
msgid "Show All Settings"
msgstr "Alle Einstellungen anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:53
msgctxt "@action:inmenu"
msgid "Manage Setting Visibility..."
msgstr "Sichtbarkeit einstellen verwalten..."
@@ -3643,347 +3798,346 @@ msgid ""
"G-code files cannot be modified"
msgstr "Druckeinrichtung deaktiviert\nG-Code-Dateien können nicht geändert werden"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:340
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr "00 Stunden 00 Minuten"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:359
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:358
msgctxt "@tooltip"
msgid "Time specification"
msgstr "Zeitangabe"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:440
msgctxt "@label"
msgid "Cost specification"
msgstr "Kostenangabe"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
msgctxt "@label m for meter"
msgid "%1m"
msgstr "%1 m"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:447
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:456
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
msgctxt "@label g for grams"
msgid "%1g"
msgstr "%1 g"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:453
msgctxt "@label"
msgid "Total:"
msgstr "Insgesamt:"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:577
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Empfohlene Druckeinrichtung
Drucken mit den empfohlenen Einstellungen für den gewählten Drucker, das gewählte Material und die gewählte Qualität."
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:582
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "Benutzerdefinierte Druckeinrichtung
Druck mit Feineinstellung über jedem einzelnen Bereich des Schneidvorgangs."
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:106
msgctxt "@label"
msgid "Active print"
msgstr "Aktiver Druck"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:115
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:114
msgctxt "@label"
msgid "Job Name"
msgstr "Name des Auftrags"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:122
msgctxt "@label"
msgid "Printing Time"
msgstr "Druckzeit"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:130
msgctxt "@label"
msgid "Estimated time left"
msgstr "Geschätzte verbleibende Zeit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:78
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
-msgid "Toggle Fu&ll Screen"
-msgstr "Umschalten auf Vo&llbild-Modus"
+msgid "Toggle Full Screen"
+msgstr "Umschalten auf Vollbild-Modus"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:85
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "&Rückgängig machen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "&Wiederholen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "&Beenden"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:113
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
msgctxt "@action:inmenu menubar:view"
-msgid "&3D View"
-msgstr "&3D-Ansicht"
+msgid "3D View"
+msgstr "3D-Ansicht"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
msgctxt "@action:inmenu menubar:view"
-msgid "&Front View"
-msgstr "&Vorderansicht"
+msgid "Front View"
+msgstr "Vorderansicht"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
msgctxt "@action:inmenu menubar:view"
-msgid "&Top View"
-msgstr "&Draufsicht"
+msgid "Top View"
+msgstr "Draufsicht"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
msgctxt "@action:inmenu menubar:view"
-msgid "&Left Side View"
-msgstr "&Ansicht von links"
+msgid "Left Side View"
+msgstr "Ansicht von links"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
msgctxt "@action:inmenu menubar:view"
-msgid "&Right Side View"
-msgstr "&Ansicht von rechts"
+msgid "Right Side View"
+msgstr "Ansicht von rechts"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Cura konfigurieren..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:155
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "&Drucker hinzufügen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:161
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
msgstr "Dr&ucker verwalten..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Materialien werden verwaltet..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:176
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Profil mit aktuellen Einstellungen/Überschreibungen aktualisieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Aktuelle Änderungen verwerfen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
-msgstr "&Profil von aktuellen Einstellungen/Überschreibungen erstellen..."
+msgstr "P&rofil von aktuellen Einstellungen/Überschreibungen erstellen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:202
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Profile verwalten..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:209
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Online-&Dokumentation anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "&Fehler melden"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:225
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
-msgid "&About..."
-msgstr "&Über..."
+msgid "About..."
+msgstr "Über..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:232
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
-msgid "Delete &Selected Model"
-msgid_plural "Delete &Selected Models"
-msgstr[0] "&Ausgewähltes Modell löschen"
-msgstr[1] "&Ausgewählte Modelle löschen"
+msgid "Delete Selected Model"
+msgid_plural "Delete Selected Models"
+msgstr[0] "Ausgewähltes Modell löschen"
+msgstr[1] "Ausgewählte Modelle löschen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Ausgewähltes Modell zentrieren"
msgstr[1] "Ausgewählte Modelle zentrieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Ausgewähltes Modell multiplizieren"
msgstr[1] "Ausgewählte Modelle multiplizieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Modell löschen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:278
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "Modell auf Druckplatte ze&ntrieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "Modelle &gruppieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Gruppierung für Modelle aufheben"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "Modelle &zusammenführen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
-msgstr "Modell &multiplizieren"
+msgstr "Modell &multiplizieren..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:331
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
-msgid "&Select All Models"
-msgstr "Alle Modelle &wählen"
+msgid "Select All Models"
+msgstr "Alle Modelle wählen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
-msgid "&Clear Build Plate"
-msgstr "Druckplatte &reinigen"
+msgid "Clear Build Plate"
+msgstr "Druckplatte reinigen"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
+msgctxt "@action:inmenu menubar:file"
+msgid "Reload All Models"
+msgstr "Alle Modelle neu laden"
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
-msgctxt "@action:inmenu menubar:file"
-msgid "Re&load All Models"
-msgstr "Alle Modelle neu &laden"
-
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:360
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models To All Build Plates"
msgstr "Alle Modelle an allen Druckplatten anordnen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:367
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Alle Modelle anordnen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Anordnung auswählen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:382
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Alle Modellpositionen zurücksetzen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
-msgid "Reset All Model &Transformations"
-msgstr "Alle Modell&transformationen zurücksetzen"
+msgid "Reset All Model Transformations"
+msgstr "Alle Modelltransformationen zurücksetzen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:396
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Datei(en) öffnen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:404
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Neues Projekt..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:411
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
msgstr "Engine-&Protokoll anzeigen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:419
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Konfigurationsordner anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:433
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse packages..."
msgstr "Pakete durchsuchen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:440
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:inmenu menubar:view"
msgid "Expand/Collapse Sidebar"
msgstr "Seitenleiste vergrößern/verkleinern"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:27
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Bitte laden Sie ein 3D-Modell"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:37
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Bereit zum Slicen (Schneiden)"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:39
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "Das Slicing läuft..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:41
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Bereit zum %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:43
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
msgstr "Slicing nicht möglich"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:45
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "Slicing ist nicht verfügbar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Slice current printjob"
msgstr "Aktuellen Druckauftrag slicen"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Cancel slicing process"
msgstr "Slicing-Vorgang abbrechen"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Vorbereiten"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Abbrechen"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:320
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "Wählen Sie das aktive Ausgabegerät"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:713
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:767
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Datei(en) öffnen"
@@ -4003,129 +4157,145 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr "Ultimaker Cura"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:103
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "&Datei"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:121
+msgctxt "@title:menu menubar:file"
+msgid "&Save..."
+msgstr "&Speichern..."
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:142
+msgctxt "@title:menu menubar:file"
+msgid "&Export..."
+msgstr "&Exportieren..."
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:153
msgctxt "@action:inmenu menubar:file"
-msgid "&Save Selection to File"
-msgstr "Auswahl als Datei &speichern"
+msgid "Export Selection..."
+msgstr "Auswahl exportieren..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
-msgctxt "@title:menu menubar:file"
-msgid "Save &As..."
-msgstr "Speichern &Als"
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
-msgctxt "@title:menu menubar:file"
-msgid "Save &Project..."
-msgstr "&Projekt speichern..."
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:174
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Bearbeiten"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:191
msgctxt "@title:menu"
msgid "&View"
msgstr "&Ansicht"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
msgctxt "@title:menu"
msgid "&Settings"
msgstr "&Konfiguration"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
-msgctxt "@title:menu menubar:toplevel"
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+msgctxt "@title:menu menubar:settings"
msgid "&Printer"
msgstr "Dr&ucker"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:195
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:207
msgctxt "@title:menu"
msgid "&Material"
msgstr "&Material"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:204
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:216
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Als aktiven Extruder festlegen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:210
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:222
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:188
msgctxt "@action:inmenu"
msgid "Enable Extruder"
msgstr "Extruder aktivieren"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:217
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:229
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:194
msgctxt "@action:inmenu"
msgid "Disable Extruder"
msgstr "Extruder deaktivieren"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:230
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:241
msgctxt "@title:menu"
+msgid "&Build plate"
+msgstr "&Druckplatte"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:242
+msgctxt "@title:settings"
msgid "&Profile"
msgstr "&Profil"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:252
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "Er&weiterungen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:274
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:286
msgctxt "@title:menu menubar:toplevel"
msgid "&Toolbox"
msgstr "&Toolbox"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:281
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:294
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "E&instellungen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:289
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:302
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "&Hilfe"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:348
msgctxt "@label"
msgid "This package will be installed after restarting."
msgstr "Dieses Paket wird nach einem Neustart installiert."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:364
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:377
msgctxt "@action:button"
msgid "Open File"
msgstr "Datei öffnen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:547
msgctxt "@title:tab"
msgid "Settings"
msgstr "Einstellungen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:579
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:593
msgctxt "@title:window"
msgid "New project"
msgstr "Neues Projekt"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:594
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "Möchten Sie wirklich ein neues Projekt beginnen? Damit werden das Druckbett und alle nicht gespeicherten Einstellungen gelöscht."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:814
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:722
+msgctxt "@title:window"
+msgid "Closing Cura"
+msgstr "Cura wird geschlossen"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:723
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:735
+msgctxt "@label"
+msgid "Are you sure you want to exit Cura?"
+msgstr "Möchten Sie Cura wirklich beenden?"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:868
msgctxt "@window:title"
msgid "Install Package"
msgstr "Paket installieren"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:821
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:875
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Datei(en) öffnen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:824
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:878
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "Es wurden eine oder mehrere G-Code-Datei(en) innerhalb der von Ihnen gewählten Dateien gefunden. Sie können nur eine G-Code-Datei auf einmal öffnen. Wenn Sie eine G-Code-Datei öffnen möchten wählen Sie bitte nur eine Datei."
@@ -4135,112 +4305,107 @@ msgctxt "@title:window"
msgid "Save Project"
msgstr "Projekt speichern"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:116
-msgctxt "@action:label"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:137
msgctxt "@action:label"
msgid "Build plate"
msgstr "Druckplatte"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:169
msgctxt "@action:label"
msgid "Extruder %1"
msgstr "Extruder %1"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:179
msgctxt "@action:label"
msgid "%1 & material"
msgstr "%1 & Material"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:268
msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "Projektzusammenfassung beim Speichern nicht erneut anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:283
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:287
msgctxt "@action:button"
msgid "Save"
msgstr "Speichern"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:192
msgctxt "@label"
msgid "Layer Height"
msgstr "Schichtdicke"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:277
msgctxt "@tooltip"
msgid "This quality profile is not available for you current material and nozzle configuration. Please change these to enable this quality profile"
-msgstr "Dieses Qualitätsprofil ist für Ihr aktuelles Material und Ihre derzeitige Düsenkonfiguration nicht verfügbar. Bitte ändern Sie diese, um dieses Qualitätsprofil zu aktivieren."
+msgstr "Dieses Qualitätsprofil ist für Ihr aktuelles Material und Ihre derzeitige Düsenkonfiguration nicht verfügbar. Bitte ändern Sie diese, um dieses Qualitätsprofil zu aktivieren"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:450
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
-msgstr "Ein benutzerdefiniertes Profil ist derzeit aktiv. Wählen Sie ein voreingestelltes Qualitätsprofil aus der Registerkarte „Benutzerdefiniert“, um den Schieberegler für Qualität zu aktivieren."
+msgstr "Ein benutzerdefiniertes Profil ist derzeit aktiv. Wählen Sie ein voreingestelltes Qualitätsprofil aus der Registerkarte „Benutzerdefiniert“, um den Schieberegler für Qualität zu aktivieren"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:467
msgctxt "@label"
msgid "Print Speed"
msgstr "Druckgeschwindigkeit"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:479
msgctxt "@label"
msgid "Slower"
msgstr "Langsamer"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:490
msgctxt "@label"
msgid "Faster"
msgstr "Schneller"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:518
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr "Sie haben einige Profileinstellungen geändert. Wenn Sie diese ändern möchten, wechseln Sie in den Modus „Benutzerdefiniert“."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:506
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:541
msgctxt "@label"
msgid "Infill"
msgstr "Füllung"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:740
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:777
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Die graduelle Füllung steigert die Menge der Füllung nach oben hin schrittweise."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:752
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:791
msgctxt "@label"
msgid "Enable gradual"
msgstr "Graduell aktivieren"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:819
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:858
msgctxt "@label"
msgid "Generate Support"
msgstr "Stützstruktur generieren"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:853
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:892
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "Damit werden Strukturen zur Unterstützung von Modellteilen mit Überhängen generiert. Ohne diese Strukturen würden solche Teile während des Druckvorgangs zusammenfallen."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:925
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:964
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Wählen Sie, welcher Extruder für die Unterstützung verwendet wird. Dient zum Konstruieren von Stützstrukturen unter dem Modell, damit dieses nicht absinkt oder frei schwebend gedruckt wird."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:948
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:987
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Druckplattenhaftung"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1003
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1042
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
-msgstr "Drucken eines Brim- oder Raft-Elements aktivieren. Es wird ein flacher Bereich rund um oder unter Ihrem Objekt hinzugefügt, das im Anschluss leicht abgeschnitten werden kann. "
+msgstr "Drucken eines Brim- oder Raft-Elements aktivieren. Es wird ein flacher Bereich rund um oder unter Ihrem Objekt hinzugefügt, das im Anschluss leicht abgeschnitten werden kann."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1043
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1082
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "Sie benötigen Hilfe für Ihre Drucke? Lesen Sie die Ultimaker Anleitungen für Fehlerbehebung>"
@@ -4287,23 +4452,22 @@ msgctxt "@label"
msgid "Printer type"
msgstr "Druckertyp"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:376
msgctxt "@label"
msgid "Material"
msgstr "Material"
-# Added after the string freeze.
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:538
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:543
msgctxt "@label"
-msgid "Use adhesion sheet or glue with this material combination"
-msgstr "Verwenden Sie eiene Klebefolie oder Klebstoff mit dieser Materialcombination"
+msgid "Use glue with this material combination"
+msgstr "Für diese Materialkombination Kleber verwenden"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:570
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:575
msgctxt "@label"
msgid "Check compatibility"
msgstr "Kompatibilität prüfen"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:588
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:593
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Klicken Sie, um die Materialkompatibilität auf Ultimaker.com zu prüfen."
@@ -4393,16 +4557,6 @@ msgctxt "name"
msgid "God Mode"
msgstr "Gott-Modus"
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "description"
-msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
-msgstr "Akzeptiert den G-Code und sendet diesen über WLAN an eine Doodle3D WLAN-Box."
-
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "name"
-msgid "Doodle3D WiFi-Box"
-msgstr "Doodle3D WLAN-Box"
-
#: ChangeLogPlugin/plugin.json
msgctxt "description"
msgid "Shows changes since latest checked version."
@@ -4413,6 +4567,16 @@ msgctxt "name"
msgid "Changelog"
msgstr "Änderungsprotokoll"
+#: FirmwareUpdater/plugin.json
+msgctxt "description"
+msgid "Provides a machine actions for updating firmware."
+msgstr "Ermöglicht Gerätemaßnahmen für die Aktualisierung der Firmware."
+
+#: FirmwareUpdater/plugin.json
+msgctxt "name"
+msgid "Firmware Updater"
+msgstr "Firmware-Aktualisierungsfunktion"
+
#: ProfileFlattener/plugin.json
msgctxt "description"
msgid "Create a flattend quality changes profile."
@@ -4483,16 +4647,6 @@ msgctxt "name"
msgid "Prepare Stage"
msgstr "Vorbereitungsstufe"
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "description"
-msgid "Provides an edit window for direct script editing."
-msgstr "Bietet ein Bearbeitungsfenster für direkte Skriptbearbeitung."
-
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "name"
-msgid "Live scripting tool"
-msgstr "Live-Scripting-Tool"
-
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4506,7 +4660,7 @@ msgstr "Ausgabegerät-Plugin für Wechseldatenträger"
#: UM3NetworkPrinting/plugin.json
msgctxt "description"
msgid "Manages network connections to Ultimaker 3 printers."
-msgstr "Verwaltet Netzwerkverbindungen zu Ultimaker 3-Druckern"
+msgstr "Verwaltet Netzwerkverbindungen zu Ultimaker 3-Druckern."
#: UM3NetworkPrinting/plugin.json
msgctxt "name"
@@ -4566,7 +4720,7 @@ msgstr "Nachbearbeitung"
#: SupportEraser/plugin.json
msgctxt "description"
msgid "Creates an eraser mesh to block the printing of support in certain places"
-msgstr "Erstellt ein Radierernetz, um den Druck von Stützstrukturen in bestimmten Positionen zu blockieren."
+msgstr "Erstellt ein Radierernetz, um den Druck von Stützstrukturen in bestimmten Positionen zu blockieren"
#: SupportEraser/plugin.json
msgctxt "name"
@@ -4603,16 +4757,6 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Cura-Vorgängerprofil-Reader"
-#: CuraBlenderPlugin/plugin.json
-msgctxt "description"
-msgid "Helps to open Blender files directly in Cura."
-msgstr "Unterstützt das Öffnen der Blender-Dateien direkt in Cura."
-
-#: CuraBlenderPlugin/plugin.json
-msgctxt "name"
-msgid "Blender Integration (experimental)"
-msgstr "Blender-Integration (experimentell)"
-
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4663,6 +4807,16 @@ msgctxt "name"
msgid "Version Upgrade 2.7 to 3.0"
msgstr "Upgrade von Version 2.7 auf 3.0"
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "description"
+msgid "Upgrades configurations from Cura 3.4 to Cura 3.5."
+msgstr "Aktualisiert Konfigurationen von Cura 3.4 auf Cura 3.5."
+
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "name"
+msgid "Version Upgrade 3.4 to 3.5"
+msgstr "Upgrade von Version 3.4 auf 3.5"
+
#: VersionUpgrade/VersionUpgrade30to31/plugin.json
msgctxt "description"
msgid "Upgrades configurations from Cura 3.0 to Cura 3.1."
@@ -4803,6 +4957,299 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Cura-Profil-Reader"
+#~ msgctxt "@warning:status"
+#~ msgid "Please generate G-code before saving."
+#~ msgstr "Generieren Sie vor dem Speichern bitte einen G-Code."
+
+#~ msgctxt "@item:inmenu"
+#~ msgid "Profile Assistant"
+#~ msgstr "Profilassistent"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Profile Assistant"
+#~ msgstr "Profilassistent"
+
+#~ msgctxt "@action"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Firmware aktualisieren"
+
+#~ msgctxt "@label unknown material"
+#~ msgid "Unknown"
+#~ msgstr "Unbekannt"
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "No custom profile to import in file {0}"
+#~ msgstr "Kein benutzerdefiniertes Profil für das Importieren in Datei {0}"
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "This profile {0} contains incorrect data, could not import it."
+#~ msgstr "Dieses Profil {0} enthält falsche Daten, Importieren nicht möglich."
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it."
+#~ msgstr "Die Maschine, die im Profil {0} ({1}) definiert wurde, entspricht nicht Ihrer derzeitigen Maschine ({2}). Importieren nicht möglich."
+
+#~ msgctxt "@title:window"
+#~ msgid "Confirm uninstall "
+#~ msgstr "Deinstallieren bestätigen "
+
+#~ msgctxt "@label:status"
+#~ msgid "Paused"
+#~ msgstr "Pausiert"
+
+#~ msgctxt "@action:button"
+#~ msgid "Previous"
+#~ msgstr "Zurück"
+
+#~ msgctxt "@action:button"
+#~ msgid "Next"
+#~ msgstr "Weiter"
+
+#~ msgctxt "@label"
+#~ msgid "Tip"
+#~ msgstr "Tipp"
+
+#~ msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
+#~ msgid "%1m / ~ %2g / ~ %4 %3"
+#~ msgstr "%1m / ~ %2g / ~ %4 %3"
+
+#~ msgctxt "@label Print estimates: m for meters, g for grams"
+#~ msgid "%1m / ~ %2g"
+#~ msgstr "%1m / ~ %2g"
+
+#~ msgctxt "@label"
+#~ msgid "Print experiment"
+#~ msgstr "Druckexperiment"
+
+#~ msgctxt "@label"
+#~ msgid "Checklist"
+#~ msgstr "Checkliste"
+
+#~ msgctxt "@title"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Firmware aktualisieren"
+
+#~ msgctxt "description"
+#~ msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+#~ msgstr "Ermöglichen Sie Materialherstellern die Erstellung neuer Material- und Qualitätsprofile, indem Sie eine Drop-In-Benutzerschnittstelle verwenden."
+
+#~ msgctxt "name"
+#~ msgid "Print Profile Assistant"
+#~ msgstr "Druckprofil-Assistent"
+
+#~ msgctxt "@action:button"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Mit Doodle3D WLAN-Box drucken"
+
+#~ msgctxt "@properties:tooltip"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Mit Doodle3D WLAN-Box drucken"
+
+#~ msgctxt "@info:status"
+#~ msgid "Connecting to Doodle3D Connect"
+#~ msgstr "Zu Doodle3D Connect verbinden"
+
+#~ msgctxt "@info:status"
+#~ msgid "Sending data to Doodle3D Connect"
+#~ msgstr "Daten werden zu Doodle3D Connect gesendet"
+
+#~ msgctxt "@info:status"
+#~ msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
+#~ msgstr "Daten können nicht zu Doodle3D Connect gesendet werden. Ist noch ein weiterer Auftrag in Bearbeitung?"
+
+#~ msgctxt "@info:status"
+#~ msgid "Storing data on Doodle3D Connect"
+#~ msgstr "Daten werden auf Doodle3D Connect gespeichert"
+
+#~ msgctxt "@info:status"
+#~ msgid "File sent to Doodle3D Connect"
+#~ msgstr "Datei wurde zu Doodle3D Connect gesendet"
+
+#~ msgctxt "@action:button"
+#~ msgid "Open Connect..."
+#~ msgstr "Connect wird geöffnet ..."
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Open the Doodle3D Connect web interface"
+#~ msgstr "Doodle3D Connect Web-Schnittstelle öffnen"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Blender file"
+#~ msgstr "Blender-Datei"
+
+#~ msgctxt "@info:status"
+#~ msgid ""
+#~ "Could not export using \"{}\" quality!\n"
+#~ "Felt back to \"{}\"."
+#~ msgstr ""
+#~ "Exportieren in \"{}\" Qualität nicht möglich!\n"
+#~ "Zurückgeschaltet auf \"{}\"."
+
+#~ msgctxt "@label"
+#~ msgid "Contact"
+#~ msgstr "Kontakt"
+
+#~ msgctxt "@label"
+#~ msgid "This printer is not set up to host a group of Ultimaker 3 printers."
+#~ msgstr "Dieser Drucker ist nicht eingerichtet um eine Gruppe von Ultimaker 3 Druckern anzusteuern."
+
+#~ msgctxt "@label"
+#~ msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
+#~ msgstr "Dieser Drucker steuert eine Gruppe von %1 Ultimaker 3 Druckern an."
+
+#~ msgctxt "@label: arg 1 is group name"
+#~ msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
+#~ msgstr "%1 ist nicht für das Hosten einer Gruppe verbundener Ultimaker 3-Drucker eingerichtet"
+
+#~ msgctxt "@label link to connect manager"
+#~ msgid "Add/Remove printers"
+#~ msgstr "Drucker hinzufügen/entfernen"
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Opens the print jobs page with your default web browser."
+#~ msgstr "Öffnet die Seite für Druckaufträge mit Ihrem Standard-Webbrowser."
+
+#~ msgctxt "@action:button"
+#~ msgid "View print jobs"
+#~ msgstr "Druckaufträge anzeigen"
+
+#~ msgctxt "@label:status"
+#~ msgid "Preparing to print"
+#~ msgstr "Vorb. für den Druck"
+
+#~ msgctxt "@label:status"
+#~ msgid "Printing"
+#~ msgstr "Drucken"
+
+#~ msgctxt "@label:status"
+#~ msgid "Available"
+#~ msgstr "Verfügbar"
+
+#~ msgctxt "@label:status"
+#~ msgid "Lost connection with the printer"
+#~ msgstr "Verbindung zum Drucker wurde unterbrochen"
+
+#~ msgctxt "@label:status"
+#~ msgid "Unavailable"
+#~ msgstr "Nicht verfügbar"
+
+#~ msgctxt "@label:status"
+#~ msgid "Unknown"
+#~ msgstr "Unbekannt"
+
+#~ msgctxt "@label:status"
+#~ msgid "Disabled"
+#~ msgstr "Deaktiviert"
+
+#~ msgctxt "@label:status"
+#~ msgid "Reserved"
+#~ msgstr "Reserviert"
+
+#~ msgctxt "@label"
+#~ msgid "Preparing to print"
+#~ msgstr "Vorbereitung für den Druck"
+
+#~ msgctxt "@label:status"
+#~ msgid "Print aborted"
+#~ msgstr "Drucken wurde abgebrochen"
+
+#~ msgctxt "@label"
+#~ msgid "Not accepting print jobs"
+#~ msgstr "Akzeptiert keine Druckaufträge"
+
+#~ msgctxt "@label"
+#~ msgid "Finishes at: "
+#~ msgstr "Endet um: "
+
+#~ msgctxt "@label"
+#~ msgid "Clear build plate"
+#~ msgstr "Druckplatte räumen"
+
+#~ msgctxt "@label"
+#~ msgid "Waiting for configuration change"
+#~ msgstr "Warten auf eine Konfigurationsänderung"
+
+#~ msgctxt "@title"
+#~ msgid "Print jobs"
+#~ msgstr "Druckaufträge"
+
+#~ msgctxt "@label:title"
+#~ msgid "Printers"
+#~ msgstr "Drucker"
+
+#~ msgctxt "@action:button"
+#~ msgid "View printers"
+#~ msgstr "Drucker anzeigen"
+
+#~ msgctxt "@label:"
+#~ msgid "Pause"
+#~ msgstr "Pausieren"
+
+#~ msgctxt "@label:"
+#~ msgid "Resume"
+#~ msgstr "Zurückkehren"
+
+#~ msgctxt "@label:"
+#~ msgid "Abort Print"
+#~ msgstr "Drucken abbrechen"
+
+#~ msgctxt "@option:openProject"
+#~ msgid "Always ask"
+#~ msgstr "Immer nachfragen"
+
+#~ msgctxt "@label"
+#~ msgid "Override Profile"
+#~ msgstr "Profil überschreiben"
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+#~ msgstr "Sollen neu geladene Modelle auf der Druckplatte angeordnet werden? In Verbindung mit Mehrfach-Druckplatte verwenden (EXPERIMENTELL)"
+
+#~ msgctxt "@option:check"
+#~ msgid "Do not arrange objects on load"
+#~ msgstr "Keine Objekte beim Laden anordnen"
+
+#~ msgctxt "@action:inmenu menubar:file"
+#~ msgid "&Save Selection to File"
+#~ msgstr "Auswahl als Datei &speichern"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save &As..."
+#~ msgstr "Speichern &Als"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save &Project..."
+#~ msgstr "&Projekt speichern..."
+
+# Added after the string freeze.
+#~ msgctxt "@label"
+#~ msgid "Use adhesion sheet or glue with this material combination"
+#~ msgstr "Verwenden Sie eiene Klebefolie oder Klebstoff mit dieser Materialcombination"
+
+#~ msgctxt "description"
+#~ msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
+#~ msgstr "Akzeptiert den G-Code und sendet diesen über WLAN an eine Doodle3D WLAN-Box."
+
+#~ msgctxt "name"
+#~ msgid "Doodle3D WiFi-Box"
+#~ msgstr "Doodle3D WLAN-Box"
+
+#~ msgctxt "description"
+#~ msgid "Provides an edit window for direct script editing."
+#~ msgstr "Bietet ein Bearbeitungsfenster für direkte Skriptbearbeitung."
+
+#~ msgctxt "name"
+#~ msgid "Live scripting tool"
+#~ msgstr "Live-Scripting-Tool"
+
+#~ msgctxt "description"
+#~ msgid "Helps to open Blender files directly in Cura."
+#~ msgstr "Unterstützt das Öffnen der Blender-Dateien direkt in Cura."
+
+#~ msgctxt "name"
+#~ msgid "Blender Integration (experimental)"
+#~ msgstr "Blender-Integration (experimentell)"
+
#~ msgctxt "@info:title"
#~ msgid "Model Checker Warning"
#~ msgstr "Warnhinweis Modell-Prüfer"
@@ -5070,10 +5517,6 @@ msgstr "Cura-Profil-Reader"
#~ msgid "Browse plugins..."
#~ msgstr "Plugins durchsuchen..."
-#~ msgctxt "@title:menu"
-#~ msgid "&Build plate"
-#~ msgstr "&Druckplatte"
-
#~ msgctxt "@title:menu menubar:toplevel"
#~ msgid "P&lugins"
#~ msgstr "&Plugins"
@@ -5299,14 +5742,6 @@ msgstr "Cura-Profil-Reader"
#~ "\n"
#~ "Es tut uns leid!"
-#~ msgctxt "@item:inmenu"
-#~ msgid "Profile Assistant"
-#~ msgstr "Profilassistent"
-
-#~ msgctxt "@item:inlistbox"
-#~ msgid "Profile Assistant"
-#~ msgstr "Profilassistent"
-
#~ msgctxt "@item:material"
#~ msgid "No material loaded"
#~ msgstr "Kein Material geladen"
@@ -5437,14 +5872,6 @@ msgstr "Cura-Profil-Reader"
#~ msgid "Configure setting visiblity..."
#~ msgstr "Sichtbarkeit der Einstellung wird konfiguriert..."
-#~ msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
-#~ msgid "%1m / ~ %2g / ~ %4 %3"
-#~ msgstr "%1m / ~ %2g / ~ %4 %3"
-
-#~ msgctxt "@label Print estimates: m for meters, g for grams"
-#~ msgid "%1m / ~ %2g"
-#~ msgstr "%1m / ~ %2g"
-
#~ msgctxt "@title:menuitem %1 is the automatically selected material"
#~ msgid "Automatic: %1"
#~ msgstr "Automatisch: %1"
@@ -5481,14 +5908,6 @@ msgstr "Cura-Profil-Reader"
#~ msgid "GCode Profile Reader"
#~ msgstr "G-Code-Profil-Reader"
-#~ msgctxt "description"
-#~ msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
-#~ msgstr "Ermöglichen Sie Materialherstellern die Erstellung neuer Material- und Qualitätsprofile, indem Sie eine Drop-In-Benutzerschnittstelle verwenden."
-
-#~ msgctxt "name"
-#~ msgid "Print Profile Assistant"
-#~ msgstr "Druckprofil-Assistent"
-
#~ msgctxt "@info:status"
#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
#~ msgstr "Beim Öffnen Ihrer SolidWorks Datei trat ein Fehler auf! Überprüfen Sie bitte, ob sich Ihre Datei in SolidWorks ohne Probleme öffnen lässt!"
@@ -5685,10 +6104,6 @@ msgstr "Cura-Profil-Reader"
#~ msgid "This printer is the host for a group of %1 connected Ultimaker 3 printers"
#~ msgstr "Dieser Drucker ist der Host für eine Gruppe von %1 verbundenen Ultimaker 3-Druckern"
-#~ msgctxt "@label:status"
-#~ msgid "Preparing"
-#~ msgstr "Vorbereitung"
-
#~ msgctxt "@label"
#~ msgid "Completed on: "
#~ msgstr "Fertiggestellt am: "
diff --git a/resources/i18n/de_DE/fdmextruder.def.json.po b/resources/i18n/de_DE/fdmextruder.def.json.po
index 697560017c..77ffa5631d 100644
--- a/resources/i18n/de_DE/fdmextruder.def.json.po
+++ b/resources/i18n/de_DE/fdmextruder.def.json.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0000\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
+"PO-Revision-Date: 2018-09-28 14:25+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -166,6 +166,16 @@ msgctxt "extruder_prime_pos_z description"
msgid "The Z coordinate of the position where the nozzle primes at the start of printing."
msgstr "Die Z-Koordinate der Position, an der die Düse am Druckbeginn einzieht."
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number label"
+msgid "Extruder Print Cooling Fan"
+msgstr "Drucklüfter Extruder"
+
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number description"
+msgid "The number of the print cooling fan associated with this extruder. Only change this from the default value of 0 when you have a different print cooling fan for each extruder."
+msgstr "Die Anzahl der Drucklüfter für diesen Extruder. Nur vom Standardwert 0 ändern, wenn Sie für jeden Extruder einen anderen Drucklüfter verwenden."
+
#: fdmextruder.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
diff --git a/resources/i18n/de_DE/fdmprinter.def.json.po b/resources/i18n/de_DE/fdmprinter.def.json.po
index d518735b72..383a7a3886 100644
--- a/resources/i18n/de_DE/fdmprinter.def.json.po
+++ b/resources/i18n/de_DE/fdmprinter.def.json.po
@@ -5,16 +5,17 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-03-29 08:36+0200\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
+"PO-Revision-Date: 2018-09-28 14:57+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: German\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.6\n"
#: fdmprinter.def.json
msgctxt "machine_settings label"
@@ -80,6 +81,16 @@ msgctxt "material_guid description"
msgid "GUID of the material. This is set automatically. "
msgstr "GUID des Materials. Dies wird automatisch eingestellt. "
+#: fdmprinter.def.json
+msgctxt "material_diameter label"
+msgid "Diameter"
+msgstr "Durchmesser"
+
+#: fdmprinter.def.json
+msgctxt "material_diameter description"
+msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
+msgstr "Der Durchmesser des verwendeten Filaments wird angepasst. Stellen Sie hier den Durchmesser des verwendeten Filaments ein."
+
#: fdmprinter.def.json
msgctxt "material_bed_temp_wait label"
msgid "Wait for Build Plate Heatup"
@@ -533,7 +544,7 @@ msgstr "Maximale Beschleunigung X"
#: fdmprinter.def.json
msgctxt "machine_max_acceleration_x description"
msgid "Maximum acceleration for the motor of the X-direction"
-msgstr "Die maximale Beschleunigung für den Motor der X-Richtung."
+msgstr "Die maximale Beschleunigung für den Motor der X-Richtung"
#: fdmprinter.def.json
msgctxt "machine_max_acceleration_y label"
@@ -1055,6 +1066,16 @@ msgctxt "top_bottom_pattern_0 option zigzag"
msgid "Zig Zag"
msgstr "Zickzack"
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons label"
+msgid "Connect Top/Bottom Polygons"
+msgstr "Polygone oben/unten verbinden"
+
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons description"
+msgid "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happen midway over infill this feature can reduce the top surface quality."
+msgstr "Außenhaut-Pfade oben/unten verbinden, wenn sie nebeneinander laufen. Bei konzentrischen Mustern reduziert die Aktivierung dieser Einstellung die Durchlaufzeit erheblich. Da die Verbindungen jedoch auf halbem Weg über der Füllung erfolgen können, kann diese Funktion die Oberflächenqualität reduzieren."
+
#: fdmprinter.def.json
msgctxt "skin_angles label"
msgid "Top/Bottom Line Directions"
@@ -1135,6 +1156,26 @@ msgctxt "travel_compensate_overlapping_walls_x_enabled description"
msgid "Compensate the flow for parts of an inner wall being printed where there is already a wall in place."
msgstr "Der Fluss für Teile einer Innenwand wird ausgeglichen, die dort gedruckt werden, wo sich bereits eine Wand befindet."
+#: fdmprinter.def.json
+msgctxt "wall_min_flow label"
+msgid "Minimum Wall Flow"
+msgstr "Mindestwandfluss"
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow description"
+msgid "Minimum allowed percentage flow for a wall line. The wall overlap compensation reduces a wall's flow when it lies close to an existing wall. Walls whose flow is less than this value will be replaced with a travel move. When using this setting, you must enable the wall overlap compensation and print the outer wall before inner walls."
+msgstr "Minimal zulässiger Fluss als Prozentwert für eine Wandlinie. Die Wand-Überlappungskompensation reduziert den Fluss einer Wand, wenn sie nah an einer vorhandenen Wand liegt. Wände, deren Fluss unter diesem Wert liegt, werden durch eine Fahrbewegung ersetzt. Bei Verwendung dieser Einstellung müssen Sie die Wand-Überlappungskompensation aktivieren und die Außenwand vor den Innenwänden drucken."
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract label"
+msgid "Prefer Retract"
+msgstr "Einziehen bevorzugt"
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract description"
+msgid "If enabled, retraction is used rather than combing for travel moves that replace walls whose flow is below the minimum flow threshold."
+msgstr "Bei Aktivierung wird der Einzug anstelle des Combings für zurückzulegende Wege verwendet, die Wände ersetzen, deren Fluss unter der mindestens erforderlichen Flussschwelle liegt."
+
#: fdmprinter.def.json
msgctxt "fill_perimeter_gaps label"
msgid "Fill Gaps Between Walls"
@@ -1203,7 +1244,7 @@ msgstr "Justierung der Z-Naht"
#: fdmprinter.def.json
msgctxt "z_seam_type description"
msgid "Starting point of each path in a layer. When paths in consecutive layers start at the same point a vertical seam may show on the print. When aligning these near a user specified location, the seam is easiest to remove. When placed randomly the inaccuracies at the paths' start will be less noticeable. When taking the shortest path the print will be quicker."
-msgstr "Der Startdruckpunkt von jedem Teil einer Schicht. Wenn der Druck der Teile in aufeinanderfolgenden Schichten am gleichen Punkt startet, kann eine vertikale Naht sichtbar werden. Wird dieser neben einer benutzerdefinierten Position ausgerichtet, ist die Naht am einfachsten zu entfernen. Wird er zufällig platziert, fallen die Ungenauigkeiten am Startpunkt weniger auf. Wird der kürzeste Weg eingestellt, ist der Druck schneller. "
+msgstr "Der Startdruckpunkt von jedem Teil einer Schicht. Wenn der Druck der Teile in aufeinanderfolgenden Schichten am gleichen Punkt startet, kann eine vertikale Naht sichtbar werden. Wird dieser neben einer benutzerdefinierten Position ausgerichtet, ist die Naht am einfachsten zu entfernen. Wird er zufällig platziert, fallen die Ungenauigkeiten am Startpunkt weniger auf. Wird der kürzeste Weg eingestellt, ist der Druck schneller."
#: fdmprinter.def.json
msgctxt "z_seam_type option back"
@@ -1452,8 +1493,8 @@ msgstr "Füllmuster"
#: fdmprinter.def.json
msgctxt "infill_pattern description"
-msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
-msgstr "Das Muster des Füllmaterials des Drucks. Die Linien- und Zickzackfüllmethode wechseln nach jeder Schicht die Richtung, um Materialkosten zu reduzieren. Die Gitter-, Dreieck- Tri-Hexagon-, Würfel-, Octahedral-, Viertelwürfel-, Quer- und konzentrischen Muster werden in jeder Schicht vollständig gedruckt. Würfel-, Viertelwürfel- und Octahedral-Füllungen wechseln mit jeder Schicht, um eine gleichmäßigere Verteilung der Stärke in allen Richtungen zu erzielen."
+msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Gyroid, cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
+msgstr "Das Muster des Füllmaterials des Drucks. Die Linien- und Zickzackfüllmethode wechseln nach jeder Schicht die Richtung, um Materialkosten zu reduzieren. Die Gitter-, Dreieck- Tri-Hexagon-, Würfel-, Octahedral-, Viertelwürfel-, Quer- und konzentrischen Muster werden in jeder Schicht vollständig gedruckt. Gyroid-, Würfel-, Viertelwürfel- und Octahedral-Füllungen wechseln mit jeder Schicht, um eine gleichmäßigere Verteilung der Stärke in allen Richtungen zu erzielen."
#: fdmprinter.def.json
msgctxt "infill_pattern option grid"
@@ -1500,11 +1541,6 @@ msgctxt "infill_pattern option concentric"
msgid "Concentric"
msgstr "Konzentrisch"
-#: fdmprinter.def.json
-msgctxt "infill_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Konzentrisch 3D"
-
#: fdmprinter.def.json
msgctxt "infill_pattern option zigzag"
msgid "Zig Zag"
@@ -1520,6 +1556,11 @@ msgctxt "infill_pattern option cross_3d"
msgid "Cross 3D"
msgstr "3D-Quer"
+#: fdmprinter.def.json
+msgctxt "infill_pattern option gyroid"
+msgid "Gyroid"
+msgstr "Gyroid"
+
#: fdmprinter.def.json
msgctxt "zig_zaggify_infill label"
msgid "Connect Infill Lines"
@@ -1530,6 +1571,16 @@ msgctxt "zig_zaggify_infill description"
msgid "Connect the ends where the infill pattern meets the inner wall using a line which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduce the effects of infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
msgstr "Verbindet die Enden, an denen das Füllmuster auf die Innenwand trifft, mithilfe einer Linie, die der Form der Innenwand folgt. Durch Aktivierung dieser Einstellung kann die Füllung besser an den Wänden haften; auch die Auswirkungen der Füllung auf die Qualität der vertikalen Flächen werden reduziert. Die Deaktivierung dieser Einstellung reduziert den Materialverbrauch."
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons label"
+msgid "Connect Infill Polygons"
+msgstr "Füllungspolygone verbinden"
+
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons description"
+msgid "Connect infill paths where they run next to each other. For infill patterns which consist of several closed polygons, enabling this setting greatly reduces the travel time."
+msgstr "Verbinden Sie Füllungspfade, wenn sie nebeneinander laufen. Bei Füllungsmustern, die aus mehreren geschlossenen Polygonen bestehen, reduziert die Aktivierung dieser Einstellung die Durchlaufzeit erheblich."
+
#: fdmprinter.def.json
msgctxt "infill_angles label"
msgid "Infill Line Directions"
@@ -1560,6 +1611,28 @@ msgctxt "infill_offset_y description"
msgid "The infill pattern is moved this distance along the Y axis."
msgstr "Das Füllmuster wird um diese Distanz entlang der Y-Achse verschoben."
+#: fdmprinter.def.json
+msgctxt "infill_multiplier label"
+msgid "Infill Line Multiplier"
+msgstr "Fülllinie multiplizieren"
+
+#: fdmprinter.def.json
+msgctxt "infill_multiplier description"
+msgid "Convert each infill line to this many lines. The extra lines do not cross over each other, but avoid each other. This makes the infill stiffer, but increases print time and material usage."
+msgstr "Konvertieren Sie jede Fülllinie in diese mehrfachen Linien. Die zusätzlichen Linien überschneiden sich nicht, sondern vermeiden sich vielmehr. Damit wird die Füllung steifer, allerdings erhöhen sich Druckzeit und Materialverbrauch."
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count label"
+msgid "Extra Infill Wall Count"
+msgstr "Zusätzliche Füllung Wandlinien"
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count description"
+msgid ""
+"Add extra walls around the infill area. Such walls can make top/bottom skin lines sag down less which means you need less top/bottom skin layers for the same quality at the cost of some extra material.\n"
+"This feature can combine with the Connect Infill Polygons to connect all the infill into a single extrusion path without the need for travels or retractions if configured right."
+msgstr "Fügen Sie zusätzliche Wände um den Füllbereich hinzu. Derartige Wände können zu einem verringerten Absacken der oberen/unteren Außenhautlinien beitragen, was bedeutet, dass Sie weniger Außenhautschichten oben/unten bei derselben Qualität von Kosten für zusätzliches Material benötigen.\n Diese Funktion ist verknüpfbar mit „Füllungspolygone verbinden“, um alle Füllungen mit einem einzigen Extrusionspfad zu verbinden, ohne dass hierzu Vorwärtsbewegungen oder Rückzüge erforderlich sind, sofern die richtige Konfiguration gewählt wurde."
+
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
msgid "Cubic Subdivision Shell"
@@ -1668,7 +1741,7 @@ msgstr "Mindestbereich Füllung"
#: fdmprinter.def.json
msgctxt "min_infill_area description"
msgid "Don't generate areas of infill smaller than this (use skin instead)."
-msgstr "Keine Füllungsbereiche generieren, die kleiner als dieser sind (stattdessen Außenhaut verwenden). "
+msgstr "Keine Füllungsbereiche generieren, die kleiner als dieser sind (stattdessen Außenhaut verwenden)."
#: fdmprinter.def.json
msgctxt "infill_support_enabled label"
@@ -1788,7 +1861,7 @@ msgstr "Voreingestellte Drucktemperatur"
#: fdmprinter.def.json
msgctxt "default_material_print_temperature description"
msgid "The default temperature used for printing. This should be the \"base\" temperature of a material. All other print temperatures should use offsets based on this value"
-msgstr "Die für den Druck verwendete Standardtemperatur. Dies sollte die „Basis“-Temperatur eines Materials sein. Alle anderen Drucktemperaturen sollten anhand dieses Wertes einen Versatz verwenden."
+msgstr "Die für den Druck verwendete Standardtemperatur. Dies sollte die „Basis“-Temperatur eines Materials sein. Alle anderen Drucktemperaturen sollten anhand dieses Wertes einen Versatz verwenden"
#: fdmprinter.def.json
msgctxt "material_print_temperature label"
@@ -1848,7 +1921,7 @@ msgstr "Standardtemperatur Druckplatte"
#: fdmprinter.def.json
msgctxt "default_material_bed_temperature description"
msgid "The default temperature used for the heated build plate. This should be the \"base\" temperature of a build plate. All other print temperatures should use offsets based on this value"
-msgstr "Die für die erhitzte Druckplatte verwendete Standardtemperatur. Dies sollte die „Basis“-Temperatur einer Druckplatte sein. Alle anderen Drucktemperaturen sollten anhand dieses Wertes einen Versatz verwenden."
+msgstr "Die für die erhitzte Druckplatte verwendete Standardtemperatur. Dies sollte die „Basis“-Temperatur einer Druckplatte sein. Alle anderen Drucktemperaturen sollten anhand dieses Wertes einen Versatz verwenden"
#: fdmprinter.def.json
msgctxt "material_bed_temperature label"
@@ -1870,16 +1943,6 @@ msgctxt "material_bed_temperature_layer_0 description"
msgid "The temperature used for the heated build plate at the first layer."
msgstr "Die Temperatur, die für die erhitzte Druckplatte an der ersten Schicht verwendet wird."
-#: fdmprinter.def.json
-msgctxt "material_diameter label"
-msgid "Diameter"
-msgstr "Durchmesser"
-
-#: fdmprinter.def.json
-msgctxt "material_diameter description"
-msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
-msgstr "Der Durchmesser des verwendeten Filaments wird angepasst. Stellen Sie hier den Durchmesser des verwendeten Filaments ein."
-
#: fdmprinter.def.json
msgctxt "material_adhesion_tendency label"
msgid "Adhesion Tendency"
@@ -1888,7 +1951,7 @@ msgstr "Haftungstendenz"
#: fdmprinter.def.json
msgctxt "material_adhesion_tendency description"
msgid "Surface adhesion tendency."
-msgstr "Oberflächenhaftungstendenz"
+msgstr "Oberflächenhaftungstendenz."
#: fdmprinter.def.json
msgctxt "material_surface_energy label"
@@ -1948,7 +2011,7 @@ msgstr "Einziehen bei Schichtänderung"
#: fdmprinter.def.json
msgctxt "retract_at_layer_change description"
msgid "Retract the filament when the nozzle is moving to the next layer."
-msgstr "Ziehen Sie das Filament ein, wenn die Düse zur nächsten Schicht fährt. "
+msgstr "Ziehen Sie das Filament ein, wenn die Düse zur nächsten Schicht fährt."
#: fdmprinter.def.json
msgctxt "retraction_amount label"
@@ -2298,7 +2361,7 @@ msgstr "Anzahl der langsamen Schichten"
#: fdmprinter.def.json
msgctxt "speed_slowdown_layers description"
msgid "The first few layers are printed slower than the rest of the model, to get better adhesion to the build plate and improve the overall success rate of prints. The speed is gradually increased over these layers."
-msgstr "Die ersten Schichten werden langsamer als der Rest des Modells gedruckt, damit sie besser am Druckbett haften und um die Wahrscheinlichkeit eines erfolgreichen Drucks zu erhöhen. Die Geschwindigkeit wird während des Druckens dieser Schichten schrittweise erhöht. "
+msgstr "Die ersten Schichten werden langsamer als der Rest des Modells gedruckt, damit sie besser am Druckbett haften und um die Wahrscheinlichkeit eines erfolgreichen Drucks zu erhöhen. Die Geschwindigkeit wird während des Druckens dieser Schichten schrittweise erhöht."
#: fdmprinter.def.json
msgctxt "speed_equalize_flow_enabled label"
@@ -2678,7 +2741,7 @@ msgstr "Ruckfunktion Druck für die erste Schicht"
#: fdmprinter.def.json
msgctxt "jerk_print_layer_0 description"
msgid "The maximum instantaneous velocity change during the printing of the initial layer."
-msgstr "Die maximale unmittelbare Geschwindigkeitsänderung während des Druckens für die erste Schicht"
+msgstr "Die maximale unmittelbare Geschwindigkeitsänderung während des Druckens für die erste Schicht."
#: fdmprinter.def.json
msgctxt "jerk_travel_layer_0 label"
@@ -2717,8 +2780,8 @@ msgstr "Combing-Modus"
#: fdmprinter.def.json
msgctxt "retraction_combing description"
-msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
-msgstr "Durch Combing bleibt die Düse während der Bewegung innerhalb von bereits gedruckten Bereichen. Dies führt zu einer leicht verlängerten Bewegungszeit, reduziert jedoch die Notwendigkeit von Einzügen. Wenn Combing deaktiviert ist, wird das Material eingezogen und die Düse bewegt sich in einer geraden Linie zum nächsten Punkt. Es ist außerdem möglich, das Combing über die oberen/unteren Außenhautbereiche zu vermeiden, indem nur die Füllung berücksichtigt wird."
+msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas and also to only comb within the infill. Note that the 'Within Infill' option behaves exactly like the 'Not in Skin' option in earlier Cura releases."
+msgstr "Durch Combing bleibt die Düse während der Bewegung innerhalb von bereits gedruckten Bereichen. Dies führt zu einer leicht verlängerten Bewegungszeit, reduziert jedoch die Notwendigkeit von Einzügen. Wenn Combing deaktiviert ist, wird das Material eingezogen und die Düse bewegt sich in einer geraden Linie zum nächsten Punkt. Es ist außerdem möglich, das Combing über die oberen/unteren Außenhautbereiche zu vermeiden, indem nur die Füllung berücksichtigt wird. Die Option „Innerhalb der Füllung“ verhält sich genauso wie die Option „Nicht in Außenhaut“ in früheren Cura Versionen."
#: fdmprinter.def.json
msgctxt "retraction_combing option off"
@@ -2735,6 +2798,11 @@ msgctxt "retraction_combing option noskin"
msgid "Not in Skin"
msgstr "Nicht in Außenhaut"
+#: fdmprinter.def.json
+msgctxt "retraction_combing option infill"
+msgid "Within Infill"
+msgstr "Innerhalb der Füllung"
+
#: fdmprinter.def.json
msgctxt "retraction_combing_max_distance label"
msgid "Max Comb Distance With No Retract"
@@ -3115,11 +3183,6 @@ msgctxt "support_pattern option concentric"
msgid "Concentric"
msgstr "Konzentrisch"
-#: fdmprinter.def.json
-msgctxt "support_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Konzentrisch 3D"
-
#: fdmprinter.def.json
msgctxt "support_pattern option zigzag"
msgid "Zig Zag"
@@ -3180,6 +3243,56 @@ msgctxt "support_line_distance description"
msgid "Distance between the printed support structure lines. This setting is calculated by the support density."
msgstr "Der Abstand zwischen den gedruckten Stützstrukturlinien. Diese Einstellung wird anhand der Dichte der Stützstruktur berechnet."
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance label"
+msgid "Initial Layer Support Line Distance"
+msgstr "Linienabstand der ursprünglichen Stützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance description"
+msgid "Distance between the printed initial layer support structure lines. This setting is calculated by the support density."
+msgstr "Der Abstand zwischen der ursprünglichen gedruckten Stützstrukturlinien. Diese Einstellung wird anhand der Dichte der Stützstruktur berechnet."
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle label"
+msgid "Support Infill Line Direction"
+msgstr "Unterstützung Linienrichtung Füllung"
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle description"
+msgid "Orientation of the infill pattern for supports. The support infill pattern is rotated in the horizontal plane."
+msgstr "Ausrichtung des Füllmusters für Unterstützung. Das Füllmuster für Unterstützung wird in der horizontalen Planfläche gedreht."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable label"
+msgid "Enable Support Brim"
+msgstr "Stütz-Brim aktivieren"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable description"
+msgid "Generate a brim within the support infill regions of the first layer. This brim is printed underneath the support, not around it. Enabling this setting increases the adhesion of support to the build plate."
+msgstr "Erstellen Sie ein Brim in den Stützstruktur-Füllungsbereichen der ersten Schicht. Das Brim wird unterhalb der Stützstruktur und nicht drumherum gedruckt. Die Aktivierung dieser Einstellung erhöht die Haftung der Stützstruktur am Druckbett."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width label"
+msgid "Support Brim Width"
+msgstr "Breite der Brim-Stützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width description"
+msgid "The width of the brim to print underneath the support. A larger brim enhances adhesion to the build plate, at the cost of some extra material."
+msgstr "Die Breite des unter der Stützstruktur zu druckenden Brims. Ein größeres Brim erhöht die Haftung am Druckbett, jedoch erhöht sich hierdurch der Materialverbrauch."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count label"
+msgid "Support Brim Line Count"
+msgstr "Anzahl der Brim-Stützstrukturlinien"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count description"
+msgid "The number of lines used for the support brim. More brim lines enhance adhesion to the build plate, at the cost of some extra material."
+msgstr "Die Anzahl der Linien für die Brim-Stützstruktur. Eine größere Anzahl von Brim-Linien verbessert die Haftung am Druckbett, jedoch erhöht sich hierdurch der Materialverbrauch."
+
#: fdmprinter.def.json
msgctxt "support_z_distance label"
msgid "Support Z Distance"
@@ -3470,11 +3583,6 @@ msgctxt "support_interface_pattern option concentric"
msgid "Concentric"
msgstr "Konzentrisch"
-#: fdmprinter.def.json
-msgctxt "support_interface_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Konzentrisch 3D"
-
#: fdmprinter.def.json
msgctxt "support_interface_pattern option zigzag"
msgid "Zig Zag"
@@ -3510,11 +3618,6 @@ msgctxt "support_roof_pattern option concentric"
msgid "Concentric"
msgstr "Konzentrisch"
-#: fdmprinter.def.json
-msgctxt "support_roof_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Konzentrisch 3D"
-
#: fdmprinter.def.json
msgctxt "support_roof_pattern option zigzag"
msgid "Zig Zag"
@@ -3550,16 +3653,31 @@ msgctxt "support_bottom_pattern option concentric"
msgid "Concentric"
msgstr "Konzentrisch"
-#: fdmprinter.def.json
-msgctxt "support_bottom_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Konzentrisch 3D"
-
#: fdmprinter.def.json
msgctxt "support_bottom_pattern option zigzag"
msgid "Zig Zag"
msgstr "Zickzack"
+#: fdmprinter.def.json
+msgctxt "support_fan_enable label"
+msgid "Fan Speed Override"
+msgstr "Lüfterdrehzahl überschreiben"
+
+#: fdmprinter.def.json
+msgctxt "support_fan_enable description"
+msgid "When enabled, the print cooling fan speed is altered for the skin regions immediately above the support."
+msgstr "Bei Aktivierung wird die Lüfterdrehzahl für die Druckkühlung für die Außenhautbereiche direkt über der Stützstruktur geändert."
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed label"
+msgid "Supported Skin Fan Speed"
+msgstr "Unterstützte Lüfterdrehzahl für Außenhaut"
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed description"
+msgid "Percentage fan speed to use when printing the skin regions immediately above the support. Using a high fan speed can make the support easier to remove."
+msgstr "Prozentwert der Lüfterdrehzahl für die Verwendung beim Drucken der Außenhautbereiche direkt oberhalb der Stützstruktur. Die Verwendung einer hohen Lüfterdrehzahl ermöglicht ein leichteres Entfernen der Stützstruktur."
+
#: fdmprinter.def.json
msgctxt "support_use_towers label"
msgid "Use Towers"
@@ -3742,6 +3860,16 @@ msgctxt "brim_line_count description"
msgid "The number of lines used for a brim. More brim lines enhance adhesion to the build plate, but also reduces the effective print area."
msgstr "Die Anzahl der Linien für das Brim-Element. Eine größere Anzahl von Brim-Linien verbessert die Haftung am Druckbett, es wird dadurch aber auch der verwendbare Druckbereich verkleinert."
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support label"
+msgid "Brim Replaces Support"
+msgstr "Brim ersetzt die Stützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support description"
+msgid "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions of the first layer of support by brim regions."
+msgstr "Erzwingen Sie den Druck des Brims um das Modell herum, auch wenn dieser Raum sonst durch die Stützstruktur belegt würde. Dies ersetzt einige der ersten Schichten der Stützstruktur durch Brim-Bereiche."
+
#: fdmprinter.def.json
msgctxt "brim_outside_only label"
msgid "Brim Only on Outside"
@@ -3884,8 +4012,8 @@ msgstr "Die Breite der Linien in der Raft-Basisschicht. Dabei sollte es sich um
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing label"
-msgid "Raft Line Spacing"
-msgstr "Raft-Linienabstand"
+msgid "Raft Base Line Spacing"
+msgstr "Linienabstand der Raft-Basis"
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing description"
@@ -4102,16 +4230,6 @@ msgctxt "prime_tower_min_volume description"
msgid "The minimum volume for each layer of the prime tower in order to purge enough material."
msgstr "Das Mindestvolumen für jede Schicht des Einzugsturms, um ausreichend Material zu spülen."
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness label"
-msgid "Prime Tower Thickness"
-msgstr "Dicke Einzugsturm"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness description"
-msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
-msgstr "Die Dicke des Hohleinzugsturms. Eine Dicke, die mehr als die Hälfte des Mindestvolumens für den Einzugsturm beträgt, führt zu einem dichten Einzugsturm."
-
#: fdmprinter.def.json
msgctxt "prime_tower_position_x label"
msgid "Prime Tower X Position"
@@ -4152,26 +4270,6 @@ msgctxt "prime_tower_wipe_enabled description"
msgid "After printing the prime tower with one nozzle, wipe the oozed material from the other nozzle off on the prime tower."
msgstr "Nach dem Drucken des Einzugsturms mit einer Düse wird das ausgetretene Material von der anderen Düse am Einzugsturm abgewischt."
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe label"
-msgid "Wipe Nozzle After Switch"
-msgstr "Düse nach dem Schalten abwischen"
-
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe description"
-msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
-msgstr "Wischt nach dem Schalten des Extruders ausgetretenes Material am ersten Druckelement an der Düse ab. Hierdurch wird eine sichere, langsame Wischbewegung an einer Position ausgeführt, an der das ausgetretene Material am wenigsten Schaden an der Oberflächenqualität Ihres Drucks verursacht."
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume label"
-msgid "Prime Tower Purge Volume"
-msgstr "Einzugsturm Spülvolumen"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume description"
-msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
-msgstr "Menge des zu spülenden Filaments beim Wischen des Spülturms. Spülen ist hilfreich, um dem Filament-Verlust durch Aussickern während der Inaktivität der Düse zu kompensieren."
-
#: fdmprinter.def.json
msgctxt "ooze_shield_enabled label"
msgid "Enable Ooze Shield"
@@ -4657,6 +4755,16 @@ msgctxt "material_flow_temp_graph description"
msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
msgstr "Der Materialfluss (in mm3 pro Sekunde) in Bezug zur Temperatur (Grad Celsius)."
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference label"
+msgid "Minimum Polygon Circumference"
+msgstr "Mindestumfang Polygon"
+
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference description"
+msgid "Polygons in sliced layers that have a circumference smaller than this amount will be filtered out. Lower values lead to higher resolution mesh at the cost of slicing time. It is meant mostly for high resolution SLA printers and very tiny 3D models with a lot of details."
+msgstr "Polygone in geschnittenen Schichten, die einen Umfang unter diesem Wert haben, werden ausgefiltert. Niedrigere Werte führen zu einem Mesh mit höherer Auflösung zulasten der Slicing-Zeit. Dies gilt in erster Linie für SLA-Drucker mit höherer Auflösung und sehr kleine 3D-Modelle mit zahlreichen Details."
+
#: fdmprinter.def.json
msgctxt "meshfix_maximum_resolution label"
msgid "Maximum Resolution"
@@ -5314,6 +5422,26 @@ msgctxt "adaptive_layer_height_threshold description"
msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
msgstr "Das ist der Schwellenwert, der definiert, ob eine kleinere Schicht verwendet wird oder nicht. Dieser Wert wird mit dem der stärksten Neigung in einer Schicht verglichen."
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle label"
+msgid "Overhanging Wall Angle"
+msgstr "Winkel für überhängende Wände"
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle description"
+msgid "Walls that overhang more than this angle will be printed using overhanging wall settings. When the value is 90, no walls will be treated as overhanging."
+msgstr "Wände, die über diesen Winkel hinaus hängen, werden mithilfe der Einstellungen für Winkel für überhängende Wände gedruckt. Wenn der Wert 90 beträgt, werden keine Wände als überhängend behandelt."
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor label"
+msgid "Overhanging Wall Speed"
+msgstr "Geschwindigkeit für überhängende Wände"
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor description"
+msgid "Overhanging walls will be printed at this percentage of their normal print speed."
+msgstr "Überhängende Wände werden zu diesem Prozentwert ihrer normalen Druckgeschwindigkeit gedruckt."
+
#: fdmprinter.def.json
msgctxt "bridge_settings_enabled label"
msgid "Enable Bridge Settings"
@@ -5344,16 +5472,6 @@ msgctxt "bridge_skin_support_threshold description"
msgid "If a skin region is supported for less than this percentage of its area, print it using the bridge settings. Otherwise it is printed using the normal skin settings."
msgstr "Wenn ein Außenhautbereich für weniger als diesen Prozentwert seines Bereichs unterstützt wird, drucken Sie ihn mit den Brückeneinstellungen. Ansonsten erfolgt der Druck mit den normalen Außenhauteinstellungen."
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang label"
-msgid "Bridge Wall Max Overhang"
-msgstr "Max. Überhang Brückenwand"
-
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang description"
-msgid "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings."
-msgstr "Die maximal zulässige Breite des Luftbereichs unter einer Wandlinie vor dem Druck der Wand mithilfe der Brückeneinstellungen, ausgedrückt als Prozentwert der Wandliniendicke. Wenn der Luftspalt breiter als dieser Wert ist, wird die Wandlinie mithilfe der Brückeneinstellungen gedruckt. Ansonsten wird die Wandlinie mit den normalen Einstellungen gedruckt. Je niedriger der Wert, desto wahrscheinlicher ist es, dass Überhang-Wandlinien mithilfe der Brückeneinstellungen gedruckt werden."
-
#: fdmprinter.def.json
msgctxt "bridge_wall_coast label"
msgid "Bridge Wall Coasting"
@@ -5574,6 +5692,74 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Transformationsmatrix, die beim Laden aus der Datei auf das Modell angewandt wird."
+#~ msgctxt "connect_skin_polygons description"
+#~ msgid "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happend midway over infill this feature can reduce the top surface quality."
+#~ msgstr "Außenhaut-Pfade oben/unten verbinden, wenn sie nebeneinander laufen. Bei konzentrischen Mustern reduziert die Aktivierung dieser Einstellung die Durchlaufzeit erheblich. Da die Verbindungen jedoch auf halbem Weg über der Füllung erfolgen können, kann diese Funktion die Oberflächenqualität reduzieren."
+
+#~ msgctxt "infill_pattern description"
+#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
+#~ msgstr "Das Muster des Füllmaterials des Drucks. Die Linien- und Zickzackfüllmethode wechseln nach jeder Schicht die Richtung, um Materialkosten zu reduzieren. Die Gitter-, Dreieck- Tri-Hexagon-, Würfel-, Octahedral-, Viertelwürfel-, Quer- und konzentrischen Muster werden in jeder Schicht vollständig gedruckt. Würfel-, Viertelwürfel- und Octahedral-Füllungen wechseln mit jeder Schicht, um eine gleichmäßigere Verteilung der Stärke in allen Richtungen zu erzielen."
+
+#~ msgctxt "infill_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Konzentrisch 3D"
+
+#~ msgctxt "retraction_combing description"
+#~ msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
+#~ msgstr "Durch Combing bleibt die Düse während der Bewegung innerhalb von bereits gedruckten Bereichen. Dies führt zu einer leicht verlängerten Bewegungszeit, reduziert jedoch die Notwendigkeit von Einzügen. Wenn Combing deaktiviert ist, wird das Material eingezogen und die Düse bewegt sich in einer geraden Linie zum nächsten Punkt. Es ist außerdem möglich, das Combing über die oberen/unteren Außenhautbereiche zu vermeiden, indem nur die Füllung berücksichtigt wird."
+
+#~ msgctxt "support_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Konzentrisch 3D"
+
+#~ msgctxt "support_interface_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Konzentrisch 3D"
+
+#~ msgctxt "support_roof_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Konzentrisch 3D"
+
+#~ msgctxt "support_bottom_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Konzentrisch 3D"
+
+#~ msgctxt "raft_base_line_spacing label"
+#~ msgid "Raft Line Spacing"
+#~ msgstr "Raft-Linienabstand"
+
+#~ msgctxt "prime_tower_wall_thickness label"
+#~ msgid "Prime Tower Thickness"
+#~ msgstr "Dicke Einzugsturm"
+
+#~ msgctxt "prime_tower_wall_thickness description"
+#~ msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
+#~ msgstr "Die Dicke des Hohleinzugsturms. Eine Dicke, die mehr als die Hälfte des Mindestvolumens für den Einzugsturm beträgt, führt zu einem dichten Einzugsturm."
+
+#~ msgctxt "dual_pre_wipe label"
+#~ msgid "Wipe Nozzle After Switch"
+#~ msgstr "Düse nach dem Schalten abwischen"
+
+#~ msgctxt "dual_pre_wipe description"
+#~ msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
+#~ msgstr "Wischt nach dem Schalten des Extruders ausgetretenes Material am ersten Druckelement an der Düse ab. Hierdurch wird eine sichere, langsame Wischbewegung an einer Position ausgeführt, an der das ausgetretene Material am wenigsten Schaden an der Oberflächenqualität Ihres Drucks verursacht."
+
+#~ msgctxt "prime_tower_purge_volume label"
+#~ msgid "Prime Tower Purge Volume"
+#~ msgstr "Einzugsturm Spülvolumen"
+
+#~ msgctxt "prime_tower_purge_volume description"
+#~ msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
+#~ msgstr "Menge des zu spülenden Filaments beim Wischen des Spülturms. Spülen ist hilfreich, um dem Filament-Verlust durch Aussickern während der Inaktivität der Düse zu kompensieren."
+
+#~ msgctxt "bridge_wall_max_overhang label"
+#~ msgid "Bridge Wall Max Overhang"
+#~ msgstr "Max. Überhang Brückenwand"
+
+#~ msgctxt "bridge_wall_max_overhang description"
+#~ msgid "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings."
+#~ msgstr "Die maximal zulässige Breite des Luftbereichs unter einer Wandlinie vor dem Druck der Wand mithilfe der Brückeneinstellungen, ausgedrückt als Prozentwert der Wandliniendicke. Wenn der Luftspalt breiter als dieser Wert ist, wird die Wandlinie mithilfe der Brückeneinstellungen gedruckt. Ansonsten wird die Wandlinie mit den normalen Einstellungen gedruckt. Je niedriger der Wert, desto wahrscheinlicher ist es, dass Überhang-Wandlinien mithilfe der Brückeneinstellungen gedruckt werden."
+
#~ msgctxt "optimize_wall_printing_order description"
#~ msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization."
#~ msgstr "Optimieren Sie die Reihenfolge, in der die Wände gedruckt werden, um die Anzahl der Einzüge und die zurückgelegten Distanzen zu reduzieren. Dieser Schritt bringt für die meisten Teile Vorteile, allerdings werden einige möglicherweise länger benötigen. Vergleichen Sie deshalb bitte die Schätzung der Druckzeiten mit und ohne Optimierung."
diff --git a/resources/i18n/es_ES/cura.po b/resources/i18n/es_ES/cura.po
index 66add367c5..e6b5867e39 100644
--- a/resources/i18n/es_ES/cura.po
+++ b/resources/i18n/es_ES/cura.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0200\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0100\n"
+"PO-Revision-Date: 2018-09-28 14:55+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -40,6 +40,17 @@ msgctxt "@item:inlistbox"
msgid "G-code File"
msgstr "Archivo GCode"
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:67
+msgctxt "@error:not supported"
+msgid "GCodeWriter does not support non-text mode."
+msgstr "GCodeWriter no es compatible con el modo sin texto."
+
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:73
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:89
+msgctxt "@warning:status"
+msgid "Please prepare G-code before exporting."
+msgstr "Prepare el Gcode antes de la exportación."
+
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
msgctxt "@info:title"
msgid "3D Model Assistant"
@@ -53,107 +64,53 @@ msgid ""
"
{model_names}
\n"
"
Find out how to ensure the best possible print quality and reliability.
"
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
-msgctxt "@action:button"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Imprimir con un enrutador Doodle3D"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:66
-msgctxt "@properties:tooltip"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Imprimir con un enrutador Doodle3D"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:86
-msgctxt "@info:status"
-msgid "Connecting to Doodle3D Connect"
-msgstr "Conectar con Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:258
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:204
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:398
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:88
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
-#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
-#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:275
-msgctxt "@action:button"
-msgid "Cancel"
-msgstr "Cancelar"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:154
-msgctxt "@info:status"
-msgid "Sending data to Doodle3D Connect"
-msgstr "Enviando datos a Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:161
-msgctxt "@info:status"
-msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
-msgstr "No se pueden enviar datos a Doodle3D Connect. ¿Hay otro trabajo que todavía esté activo?"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:175
-msgctxt "@info:status"
-msgid "Storing data on Doodle3D Connect"
-msgstr "Guardando datos en Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:213
-msgctxt "@info:status"
-msgid "File sent to Doodle3D Connect"
-msgstr "Archivo enviado a Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@action:button"
-msgid "Open Connect..."
-msgstr "Abrir Connect..."
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@info:tooltip"
-msgid "Open the Doodle3D Connect web interface"
-msgstr "Abrir la interfaz web de Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:32
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Mostrar registro de cambios"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:20
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py:25
+msgctxt "@action"
+msgid "Update Firmware"
+msgstr "Actualizar firmware"
+
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:23
msgctxt "@item:inmenu"
msgid "Flatten active settings"
msgstr "Aplanar ajustes activos"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:32
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:35
msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr "El perfil se ha aplanado y activado."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:40
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:32
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "Impresión USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:41
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:33
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "Imprimir mediante USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:42
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:34
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "Imprimir mediante USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:83
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:69
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Conectado mediante USB"
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:92
+msgctxt "@label"
+msgid "A USB print is in progress, closing Cura will stop this print. Are you sure?"
+msgstr "Se está realizando una impresión con USB, si cierra Cura detendrá la impresión. ¿Desea continuar?"
+
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/install/X3GWriter/__init__.py:15
#: /home/ruben/Projects/Cura/plugins/X3GWriter/__init__.py:15
msgctxt "X3G Writer File Description"
@@ -176,7 +133,12 @@ msgctxt "@item:inlistbox"
msgid "Compressed G-code File"
msgstr "Archivo GCode comprimido"
-#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/GCodeGzWriter.py:38
+msgctxt "@error:not supported"
+msgid "GCodeGzWriter does not support text mode."
+msgstr "GCodeGzWriter no es compatible con el modo texto."
+
+#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:28
msgctxt "@item:inlistbox"
msgid "Ultimaker Format Package"
msgstr "Paquete de formato Ultimaker"
@@ -198,10 +160,10 @@ msgid "Save to Removable Drive {0}"
msgstr "Guardar en unidad extraíble {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:64
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:133
msgctxt "@info:status"
msgid "There are no file formats available to write with!"
-msgstr "No hay formatos de archivo disponibles con los que escribir."
+msgstr "¡No hay formatos de archivo disponibles con los que escribir!"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:94
#, python-brace-format
@@ -237,7 +199,7 @@ msgstr "No se pudo guardar en unidad extraíble {0}: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:137
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:133
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:140
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1592
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1607
msgctxt "@info:title"
msgid "Error"
msgstr "Error"
@@ -266,8 +228,8 @@ msgstr "Expulsar dispositivo extraíble {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:151
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:163
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1582
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1681
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1597
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1695
msgctxt "@info:title"
msgid "Warning"
msgstr "Advertencia"
@@ -294,259 +256,269 @@ msgctxt "@item:intext"
msgid "Removable Drive"
msgstr "Unidad extraíble"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:70
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:78
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:88
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "Imprimir a través de la red"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:71
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:79
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:74
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:89
msgctxt "@properties:tooltip"
msgid "Print over network"
msgstr "Imprime a través de la red"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:84
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:87
msgctxt "@info:status"
msgid "Connected over the network."
msgstr "Conectado a través de la red."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:87
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:90
msgctxt "@info:status"
msgid "Connected over the network. Please approve the access request on the printer."
msgstr "Conectado a través de la red. Apruebe la solicitud de acceso en la impresora."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:89
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:92
msgctxt "@info:status"
msgid "Connected over the network. No access to control the printer."
msgstr "Conectado a través de la red. No hay acceso para controlar la impresora."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:94
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:97
msgctxt "@info:status"
msgid "Access to the printer requested. Please approve the request on the printer"
-msgstr "Acceso a la impresora solicitado. Apruebe la solicitud en la impresora."
+msgstr "Acceso a la impresora solicitado. Apruebe la solicitud en la impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:97
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:100
msgctxt "@info:title"
msgid "Authentication status"
msgstr "Estado de la autenticación"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:99
-msgctxt "@info:status"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:100
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:106
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:110
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:108
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:112
msgctxt "@info:title"
msgid "Authentication Status"
msgstr "Estado de la autenticación"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:101
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:103
msgctxt "@action:button"
msgid "Retry"
msgstr "Volver a intentar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:104
msgctxt "@info:tooltip"
msgid "Re-send the access request"
-msgstr "Reenvía la solicitud de acceso."
+msgstr "Reenvía la solicitud de acceso"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:105
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:107
msgctxt "@info:status"
msgid "Access to the printer accepted"
msgstr "Acceso a la impresora aceptado"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:109
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:111
msgctxt "@info:status"
msgid "No access to print with this printer. Unable to send print job."
msgstr "No hay acceso para imprimir con esta impresora. No se puede enviar el trabajo de impresión."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:111
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:29
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:33
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:70
msgctxt "@action:button"
msgid "Request Access"
msgstr "Solicitar acceso"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:113
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:28
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:72
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:115
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:34
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:71
msgctxt "@info:tooltip"
msgid "Send access request to the printer"
-msgstr "Envía la solicitud de acceso a la impresora."
+msgstr "Envía la solicitud de acceso a la impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:198
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:200
msgctxt "@label"
msgid "Unable to start a new print job."
msgstr "No se puede iniciar un nuevo trabajo de impresión."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:200
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:202
msgctxt "@label"
msgid "There is an issue with the configuration of your Ultimaker, which makes it impossible to start the print. Please resolve this issues before continuing."
msgstr "Un problema con la configuración de Ultimaker impide iniciar la impresión. Soluciónelo antes de continuar."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:206
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:228
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:208
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:230
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "Configuración desajustada"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:220
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:222
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "¿Seguro que desea imprimir con la configuración seleccionada?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:222
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:224
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "La configuración o calibración de la impresora y de Cura no coinciden. Para obtener el mejor resultado, segmente siempre los PrintCores y los materiales que se insertan en la impresora."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:249
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:166
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:251
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:199
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "Envío de nuevos trabajos (temporalmente) bloqueado; se sigue enviando el trabajo de impresión previo."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:256
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:185
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:202
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:258
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:234
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "Enviando datos a la impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:257
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:186
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:203
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:259
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:219
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:235
msgctxt "@info:title"
msgid "Sending Data"
msgstr "Enviando datos"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:321
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:260
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:236
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:381
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:20
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
+#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:279
+msgctxt "@action:button"
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:323
#, python-brace-format
msgctxt "@info:status"
msgid "No Printcore loaded in slot {slot_number}"
msgstr "No se ha cargado ningún PrintCore en la ranura {slot_number}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:327
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:329
#, python-brace-format
msgctxt "@info:status"
msgid "No material loaded in slot {slot_number}"
msgstr "No se ha cargado ningún material en la ranura {slot_number}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:350
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:352
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {cura_printcore_name}, Printer: {remote_printcore_name}) selected for extruder {extruder_id}"
msgstr "PrintCore distinto (Cura: {cura_printcore_name}, impresora: {remote_printcore_name}) seleccionado para extrusor {extruder_id}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:359
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:361
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "Material distinto (Cura: {0}, impresora: {1}) seleccionado para extrusor {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:545
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:547
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "Sincronizar con la impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:547
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:549
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "¿Desea utilizar la configuración actual de su impresora en Cura?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:549
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:551
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Los PrintCores o los materiales de la impresora difieren de los del proyecto actual. Para obtener el mejor resultado, segmente siempre los PrintCores y materiales que se hayan insertado en la impresora."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:81
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:91
msgctxt "@info:status"
msgid "Connected over the network"
-msgstr "Conectado a través de la red."
+msgstr "Conectado a través de la red"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:303
msgctxt "@info:status"
msgid "Print job was successfully sent to the printer."
msgstr "El trabajo de impresión se ha enviado correctamente a la impresora."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:264
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:305
msgctxt "@info:title"
msgid "Data Sent"
msgstr "Fecha de envío"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:265
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:306
msgctxt "@action:button"
msgid "View in Monitor"
msgstr "Ver en pantalla"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:353
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:422
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "{printer_name} ha terminado de imprimir «{job_name}»."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:355
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:424
#, python-brace-format
msgctxt "@info:status"
msgid "The print job '{job_name}' was finished."
msgstr "El trabajo de impresión '{job_name}' ha terminado."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:356
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:425
msgctxt "@info:status"
msgid "Print finished"
msgstr "Impresión terminada"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:20
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py:26
msgctxt "@action"
msgid "Connect via Network"
msgstr "Conectar a través de la red"
-#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:13
msgctxt "@item:inmenu"
msgid "Monitor"
msgstr "Supervisar"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:69
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:119
+msgctxt "@info"
+msgid "Could not access update information."
+msgstr "No se pudo acceder a la información actualizada."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:17
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr "Hay nuevas funciones disponibles para {machine_name}. Se recomienda actualizar el firmware de la impresora."
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:73
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:21
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "Nuevo firmware de %s disponible"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:76
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:27
msgctxt "@action:button"
msgid "How to update"
msgstr "Cómo actualizar"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:92
-msgctxt "@info"
-msgid "Could not access update information."
-msgstr "No se pudo acceder a la información actualizada."
-
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Layer view"
msgstr "Vista de capas"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:113
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
-msgstr "Cura no muestra correctamente las capas si la impresión de alambre está habilitada."
+msgstr "Cura no muestra correctamente las capas si la impresión de alambre está habilitada"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:114
msgctxt "@info:title"
msgid "Simulation View"
msgstr "Vista de simulación"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:27
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:35
msgid "Modify G-Code"
msgstr "Modificar GCode"
@@ -560,32 +532,32 @@ msgctxt "@info:tooltip"
msgid "Create a volume in which supports are not printed."
msgstr "Cree un volumen que no imprima los soportes."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:44
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
msgctxt "@info"
msgid "Cura collects anonymized usage statistics."
msgstr "Cura recopila estadísticas de uso de forma anónima."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:47
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:55
msgctxt "@info:title"
msgid "Collecting Data"
msgstr "Recopilando datos"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:57
msgctxt "@action:button"
msgid "More info"
msgstr "Más información"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:58
msgctxt "@action:tooltip"
msgid "See more information on what data Cura sends."
msgstr "Obtenga más información sobre qué datos envía Cura."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:60
msgctxt "@action:button"
msgid "Allow"
msgstr "Permitir"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:53
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:61
msgctxt "@action:tooltip"
msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
msgstr "Permitir a Cura enviar estadísticas de uso de forma anónima para ayudar a priorizar mejoras futuras para Cura. Se envían algunas de sus preferencias y ajustes, la versión de Cura y un resumen de los modelos que está fragmentando."
@@ -595,19 +567,6 @@ msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Perfiles de Cura 15.04"
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
-msgctxt "@item:inlistbox"
-msgid "Blender file"
-msgstr "Archivo Blender"
-
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
-msgctxt "@info:status"
-msgid ""
-"Could not export using \"{}\" quality!\n"
-"Felt back to \"{}\"."
-msgstr "No ha podido exportarse con la calidad \"{}\"\n"
-"Retroceder a \"{}\"."
-
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "JPG Image"
@@ -633,49 +592,56 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "Imagen GIF"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "No se puede segmentar con el material actual, ya que es incompatible con el dispositivo o la configuración seleccionados."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:344
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:367
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:376
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:363
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:387
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:396
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:405
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:414
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "No se puede segmentar"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:343
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:362
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "Los ajustes actuales no permiten la segmentación. Los siguientes ajustes contienen errores: {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr "Los ajustes de algunos modelos no permiten la segmentación. Los siguientes ajustes contienen errores en uno o más modelos: {error_labels}."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:375
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:395
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "No se puede segmentar porque la torre auxiliar o la posición o posiciones de preparación no son válidas."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:385
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:404
+#, python-format
+msgctxt "@info:status"
+msgid "Unable to slice because there are objects associated with disabled Extruder %s."
+msgstr "No se puede segmentar porque hay objetos asociados al extrusor %s que está deshabilitado."
+
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:413
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "No hay nada que segmentar porque ninguno de los modelos se adapta al volumen de impresión. Escale o rote los modelos para que se adapten."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "Procesando capas"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:title"
msgid "Information"
msgstr "Información"
@@ -691,29 +657,40 @@ msgid "Configure Per Model Settings"
msgstr "Configurar ajustes por modelo"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:175
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:575
msgctxt "@title:tab"
msgid "Recommended"
msgstr "Recomendado"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:177
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:580
msgctxt "@title:tab"
msgid "Custom"
msgstr "Personalizado"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:32
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:28
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:34
msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "Archivo 3MF"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:199
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:695
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:714
msgctxt "@label"
msgid "Nozzle"
msgstr "Tobera"
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:468
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Project file {0} contains an unknown machine type {1}. Cannot import the machine. Models will be imported instead."
+msgstr "El archivo del proyecto{0} contiene un tipo de máquina desconocida {1}. No se puede importar la máquina, en su lugar, se importarán los modelos."
+
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:471
+msgctxt "@info:title"
+msgid "Open Project File"
+msgstr "Abrir archivo de proyecto"
+
#: /home/ruben/Projects/Cura/plugins/SolidView/__init__.py:12
msgctxt "@item:inmenu"
msgid "Solid view"
@@ -724,18 +701,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "Archivo G"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:322
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "Analizar GCode"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:470
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:326
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:474
msgctxt "@info:title"
msgid "G-code Details"
msgstr "Datos de GCode"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:468
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:472
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "Asegúrese de que el GCode es adecuado para la impresora y para su configuración antes de enviar el archivo a la misma. Es posible que la representación del GCode no sea precisa."
@@ -746,27 +723,27 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Perfil de cura"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:26
msgctxt "@item:inlistbox"
msgid "3MF file"
msgstr "Archivo 3MF"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:34
msgctxt "@item:inlistbox"
msgid "Cura Project 3MF file"
msgstr "Archivo 3MF del proyecto de Cura"
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/ThreeMFWriter.py:179
+msgctxt "@error:zip"
+msgid "Error writing 3mf file."
+msgstr "Error al escribir el archivo 3MF."
+
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelection.py:17
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelection.py:18
msgctxt "@action"
msgid "Select upgrades"
msgstr "Seleccionar actualizaciones"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py:12
-msgctxt "@action"
-msgid "Upgrade Firmware"
-msgstr "Actualizar firmware"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py:14
msgctxt "@action"
msgid "Checkup"
@@ -777,79 +754,79 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "Nivelar placa de impresión"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:82
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "Pared exterior"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:83
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "Paredes interiores"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:84
msgctxt "@tooltip"
msgid "Skin"
msgstr "Forro"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:85
msgctxt "@tooltip"
msgid "Infill"
msgstr "Relleno"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:86
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "Relleno de soporte"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:87
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "Interfaz de soporte"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:88
msgctxt "@tooltip"
msgid "Support"
msgstr "Soporte"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
msgctxt "@tooltip"
msgid "Skirt"
msgstr "Falda"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
msgctxt "@tooltip"
msgid "Travel"
msgstr "Desplazamiento"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
msgctxt "@tooltip"
msgid "Retractions"
msgstr "Retracciones"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
msgctxt "@tooltip"
msgid "Other"
msgstr "Otro"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:229
-msgctxt "@label unknown material"
-msgid "Unknown"
-msgstr "Desconocido"
-
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:313
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:310
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "Archivo {0} presegmentado"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:235
+#: /home/ruben/Projects/Cura/cura/API/Account.py:71
+msgctxt "@info:title"
+msgid "Login failed"
+msgstr "Fallo de inicio de sesión"
+
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:201
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "El archivo ya existe"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:236
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:202
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:122
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
@@ -861,23 +838,23 @@ msgctxt "@menuitem"
msgid "Not overridden"
msgstr "No reemplazado"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:119
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "El material seleccionado no es compatible con la máquina o la configuración seleccionada."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:120
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
msgctxt "@info:title"
msgid "Incompatible Material"
msgstr "Material incompatible"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:842
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:866
#, python-format
msgctxt "@info:generic"
msgid "Settings have been changed to match the current availability of extruders: [%s]"
msgstr "La configuración se ha cambiado para que coincida con los extrusores disponibles en este momento: [%s]."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:844
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:868
msgctxt "@info:title"
msgid "Settings updated"
msgstr "Ajustes actualizados"
@@ -906,8 +883,6 @@ msgid "Export succeeded"
msgstr "Exportación correcta"
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:170
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
@@ -915,58 +890,70 @@ msgstr "Error al importar el perfil de {0}: {1} or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "No custom profile to import in file {0}"
-msgstr "No hay ningún perfil personalizado que importar en el archivo {0}."
+msgstr "No hay ningún perfil personalizado para importar en el archivo {0}."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags !"
+msgid "Failed to import profile from {0}:"
+msgstr "Error al importar el perfil de {0}:"
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:218
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:228
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "This profile {0} contains incorrect data, could not import it."
msgstr "Este perfil {0} contiene datos incorrectos, no se han podido importar."
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:241
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it."
msgstr "El equipo definido en el perfil {0} ({1}) no coincide con el equipo actual ({2}), no se ha podido importar."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:316
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:312
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Failed to import profile from {0}:"
+msgstr "Error al importar el perfil de {0}:"
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:315
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "Perfil {0} importado correctamente"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:319
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:318
#, python-brace-format
msgctxt "@info:status"
msgid "File {0} does not contain any valid profile."
msgstr "El archivo {0} no contiene ningún perfil válido."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:322
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "El perfil {0} tiene un tipo de archivo desconocido o está corrupto."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:340
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:339
msgctxt "@label"
msgid "Custom profile"
msgstr "Perfil personalizado"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:356
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:355
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "Al perfil le falta un tipo de calidad."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:368
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:369
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "No se ha podido encontrar un tipo de calidad {0} para la configuración actual."
-#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:60
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:63
#, python-brace-format
msgctxt "@label"
msgid "Group #{group_nr}"
@@ -993,42 +980,42 @@ msgctxt "@item:inlistbox"
msgid "All Files (*)"
msgstr "Todos los archivos (*)"
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:544
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:636
msgctxt "@label"
msgid "Custom Material"
msgstr "Material personalizado"
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:545
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:637
msgctxt "@label"
msgid "Custom"
msgstr "Personalizado"
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:80
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:81
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
msgstr "La altura del volumen de impresión se ha reducido debido al valor del ajuste «Secuencia de impresión» para evitar que el caballete colisione con los modelos impresos."
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:82
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:83
msgctxt "@info:title"
msgid "Build Volume"
msgstr "Volumen de impresión"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:98
msgctxt "@info:backup_failed"
msgid "Could not create archive from user data directory: {}"
msgstr "No se ha podido crear el archivo desde el directorio de datos de usuario: {}"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:103
msgctxt "@info:title"
msgid "Backup"
msgstr "Copia de seguridad"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:113
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup without having proper data or meta data."
msgstr "Se ha intentado restaurar una copia de seguridad de Cura sin tener los datos o metadatos adecuados."
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:123
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup that does not match your current version."
msgstr "Se ha intentado restaurar una copia de seguridad de Cura que no coincide con la versión actual."
@@ -1039,32 +1026,32 @@ msgid "Multiplying and placing objects"
msgstr "Multiplicar y colocar objetos"
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:28
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
msgctxt "@info:title"
msgid "Placing Object"
msgstr "Colocando objeto"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:96
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:149
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
msgctxt "@info:status"
msgid "Unable to find a location within the build volume for all objects"
msgstr "No se puede encontrar una ubicación dentro del volumen de impresión para todos los objetos"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:30
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:66
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:67
msgctxt "@info:status"
msgid "Finding new location for objects"
msgstr "Buscando nueva ubicación para los objetos"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:34
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:70
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:71
msgctxt "@info:title"
msgid "Finding Location"
msgstr "Buscando ubicación"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:97
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:151
msgctxt "@info:title"
msgid "Can't Find Location"
msgstr "No se puede encontrar la ubicación"
@@ -1072,7 +1059,7 @@ msgstr "No se puede encontrar la ubicación"
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:87
msgctxt "@title:window"
msgid "Cura can't start"
-msgstr "Cura no puede iniciarse."
+msgstr "Cura no puede iniciarse"
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:93
msgctxt "@label crash message"
@@ -1082,12 +1069,7 @@ msgid ""
"
Backups can be found in the configuration folder.
\n"
"
Please send us this Crash Report to fix the problem.
\n"
" "
-msgstr ""
-"
¡Vaya! Ultimaker Cura ha encontrado un error.
\n"
-"
Hemos detectado un error irreversible durante el inicio, posiblemente como consecuencia de varios archivos de configuración erróneos. Le recomendamos que realice una copia de seguridad y que restablezca los ajustes.
\n"
-"
Las copias de seguridad se encuentran en la carpeta de configuración.
\n"
-"
Envíenos el informe de errores para que podamos solucionar el problema.
\n"
-" "
+msgstr "
¡Vaya! Ultimaker Cura ha encontrado un error.
\n
Hemos detectado un error irreversible durante el inicio, posiblemente como consecuencia de varios archivos de configuración erróneos. Le recomendamos que realice una copia de seguridad y que restablezca los ajustes.
\n
Las copias de seguridad se encuentran en la carpeta de configuración.
\n
Envíenos el informe de errores para que podamos solucionar el problema.
A fatal error has occurred in Cura. Please send us this Crash Report to fix the problem
\n"
"
Please use the \"Send report\" button to post a bug report automatically to our servers
\n"
" "
-msgstr ""
-"
Se ha producido un error grave en Cura. Envíenos este informe de errores para que podamos solucionar el problema.
\n"
-"
Utilice el botón \"Enviar informe\" para publicar automáticamente el informe de errores en nuestros servidores.
\n"
-" "
+msgstr "
Se ha producido un error grave en Cura. Envíenos este informe de errores para que podamos solucionar el problema.
\n
Utilice el botón \"Enviar informe\" para publicar automáticamente el informe de errores en nuestros servidores.
\n "
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:177
msgctxt "@title:groupbox"
@@ -1203,223 +1182,233 @@ msgctxt "@action:button"
msgid "Send report"
msgstr "Enviar informe"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:328
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:473
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Cargando máquinas..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:756
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:775
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "Configurando escena..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:789
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:811
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Cargando interfaz..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1023
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1037
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1581
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1596
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "Solo se puede cargar un archivo GCode a la vez. Se omitió la importación de {0}"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1591
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1606
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "No se puede abrir ningún archivo si se está cargando un archivo GCode. Se omitió la importación de {0}"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1680
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1694
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr "No se puede cargar el modelo seleccionado, es demasiado pequeño."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:59
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:61
msgctxt "@title"
msgid "Machine Settings"
msgstr "Ajustes de la máquina"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:78
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:80
msgctxt "@title:tab"
msgid "Printer"
msgstr "Impresora"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:97
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:99
msgctxt "@label"
msgid "Printer Settings"
msgstr "Ajustes de la impresora"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:108
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:110
msgctxt "@label"
msgid "X (Width)"
msgstr "X (anchura)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:109
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:384
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:418
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:430
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:855
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:111
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:121
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:131
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:237
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:386
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:402
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:428
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:440
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:896
msgctxt "@label"
msgid "mm"
msgstr "mm"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:118
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:120
msgctxt "@label"
msgid "Y (Depth)"
msgstr "Y (profundidad)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:128
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:130
msgctxt "@label"
msgid "Z (Height)"
msgstr "Z (altura)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:140
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:142
msgctxt "@label"
msgid "Build plate shape"
msgstr "Forma de la placa de impresión"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:149
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:151
msgctxt "@option:check"
msgid "Origin at center"
msgstr "Origen en el centro"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:157
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:159
msgctxt "@option:check"
msgid "Heated bed"
msgstr "Plataforma caliente"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:168
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:170
msgctxt "@label"
msgid "G-code flavor"
msgstr "Tipo de GCode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:181
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:183
msgctxt "@label"
msgid "Printhead Settings"
msgstr "Ajustes del cabezal de impresión"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:191
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:193
msgctxt "@label"
msgid "X min"
msgstr "X mín."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:192
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:194
msgctxt "@tooltip"
msgid "Distance from the left of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distancia desde la parte izquierda del cabezal de impresión hasta el centro de la tobera. Se usa para evitar que colisionen la impresión anterior con el cabezal de impresión al imprimir «de uno en uno»."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:201
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:203
msgctxt "@label"
msgid "Y min"
msgstr "Y mín."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:202
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:204
msgctxt "@tooltip"
msgid "Distance from the front of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distancia desde la parte frontal del cabezal de impresión hasta el centro de la tobera. Se usa para evitar que colisionen la impresión anterior con el cabezal de impresión al imprimir «de uno en uno»."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:211
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:213
msgctxt "@label"
msgid "X max"
msgstr "X máx."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:212
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:214
msgctxt "@tooltip"
msgid "Distance from the right of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distancia desde la parte derecha del cabezal de impresión hasta el centro de la tobera. Se usa para evitar que colisionen la impresión anterior con el cabezal de impresión al imprimir «de uno en uno»."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:221
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:223
msgctxt "@label"
msgid "Y max"
msgstr "Y máx."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:222
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:224
msgctxt "@tooltip"
msgid "Distance from the rear of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distancia desde la parte trasera del cabezal de impresión hasta el centro de la tobera. Se usa para evitar que colisionen la impresión anterior con el cabezal de impresión al imprimir «de uno en uno»."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:234
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
msgctxt "@label"
msgid "Gantry height"
msgstr "Altura del caballete"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:238
msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "Diferencia de altura entre la punta de la tobera y el sistema del puente (ejes X e Y). Se usa para evitar que colisionen la impresión anterior con el caballete al imprimir «de uno en uno»."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:257
msgctxt "@label"
msgid "Number of Extruders"
msgstr "Número de extrusores"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:311
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:313
msgctxt "@label"
msgid "Start G-code"
msgstr "Iniciar GCode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:321
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:323
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very start."
msgstr "Los comandos de GCode que se ejecutarán justo al inicio."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:330
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:332
msgctxt "@label"
msgid "End G-code"
msgstr "Finalizar GCode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:340
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:342
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very end."
msgstr "Los comandos de GCode que se ejecutarán justo al final."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:371
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:373
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Ajustes de la tobera"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:385
msgctxt "@label"
msgid "Nozzle size"
msgstr "Tamaño de la tobera"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:399
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
msgctxt "@label"
msgid "Compatible material diameter"
msgstr "Diámetro del material compatible"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:403
msgctxt "@tooltip"
msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
msgstr "El diámetro nominal del filamento compatible con la impresora. El diámetro exacto se sobrescribirá según el material o el perfil."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:417
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:427
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "Desplazamiento de la tobera sobre el eje X"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:429
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:439
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Desplazamiento de la tobera sobre el eje Y"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:450
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+msgctxt "@label"
+msgid "Cooling Fan Number"
+msgstr "Número de ventilador de enfriamiento"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:452
+msgctxt "@label"
+msgid ""
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:472
msgctxt "@label"
msgid "Extruder Start G-code"
msgstr "GCode inicial del extrusor"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:468
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:490
msgctxt "@label"
msgid "Extruder End G-code"
msgstr "GCode final del extrusor"
@@ -1439,12 +1428,20 @@ msgctxt "@info"
msgid "Could not connect to the Cura Package database. Please check your connection."
msgstr "No se ha podido conectar con la base de datos del Paquete Cura. Compruebe la conexión."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:38
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:28
msgctxt "@title:tab"
msgid "Plugins"
msgstr "Complementos"
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:75
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:66
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:551
+msgctxt "@title:tab"
+msgid "Materials"
+msgstr "Materiales"
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
msgctxt "@label"
msgid "Version"
@@ -1460,8 +1457,14 @@ msgctxt "@label"
msgid "Author"
msgstr "Autor"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:97
+msgctxt "@label"
+msgid "Downloads"
+msgstr "Descargas"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:116
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:158
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:258
msgctxt "@label"
msgid "Unknown"
msgstr "Desconocido"
@@ -1494,17 +1497,57 @@ msgctxt "@action:button"
msgid "Back"
msgstr "Atrás"
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:20
+msgctxt "@title:window"
+msgid "Confirm uninstall"
+msgstr "Confirmar desinstalación"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:50
+msgctxt "@text:window"
+msgid "You are uninstalling materials and/or profiles that are still in use. Confirming will reset the following materials/profiles to their defaults."
+msgstr "Va a desinstalar materiales o perfiles que todavía están en uso. Si confirma la desinstalación, los siguientes materiales o perfiles volverán a sus valores predeterminados."
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:51
+msgctxt "@text:window"
+msgid "Materials"
+msgstr "Materiales"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:52
+msgctxt "@text:window"
+msgid "Profiles"
+msgstr "Perfiles"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:89
+msgctxt "@action:button"
+msgid "Confirm"
+msgstr "Confirmar"
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
msgctxt "@info"
msgid "You will need to restart Cura before changes in packages have effect."
msgstr "Tendrá que reiniciar Cura para que los cambios de los paquetes surtan efecto."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:34
msgctxt "@info:button"
msgid "Quit Cura"
msgstr "Salir de Cura"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Contributions"
+msgstr "Contribuciones de la comunidad"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Plugins"
+msgstr "Complementos de la comunidad"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:43
+msgctxt "@label"
+msgid "Generic Materials"
+msgstr "Materiales genéricos"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:56
msgctxt "@title:tab"
msgid "Installed"
msgstr "Instalado"
@@ -1547,12 +1590,12 @@ msgctxt "@action:button"
msgid "Decline"
msgstr "Rechazar"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:23
msgctxt "@label"
msgid "Featured"
msgstr "Destacado"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:31
msgctxt "@label"
msgid "Compatibility"
msgstr "Compatibilidad"
@@ -1562,10 +1605,15 @@ msgctxt "@info"
msgid "Fetching packages..."
msgstr "Buscando paquetes..."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:88
msgctxt "@label"
-msgid "Contact"
-msgstr "Contacto"
+msgid "Website"
+msgstr "Sitio web"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:94
+msgctxt "@label"
+msgid "Email"
+msgstr "Correo electrónico"
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
msgctxt "@info:tooltip"
@@ -1578,48 +1626,88 @@ msgid "Changelog"
msgstr "Registro de cambios"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:84
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:56
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:464
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:509
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:185
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:53
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:467
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:514
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:121
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:166
#: /home/ruben/Projects/Cura/resources/qml/EngineLog.qml:38
msgctxt "@action:button"
msgid "Close"
msgstr "Cerrar"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:22
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:31
+msgctxt "@title"
+msgid "Update Firmware"
+msgstr "Actualizar firmware"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:39
+msgctxt "@label"
+msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
+msgstr "El firmware es la parte del software que se ejecuta directamente en la impresora 3D. Este firmware controla los motores de pasos, regula la temperatura y, finalmente, hace que funcione la impresora."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:46
+msgctxt "@label"
+msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
+msgstr "El firmware que se envía con las nuevas impresoras funciona, pero las nuevas versiones suelen tener más funciones y mejoras."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:58
+msgctxt "@action:button"
+msgid "Automatically upgrade Firmware"
+msgstr "Actualización de firmware automática"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:69
+msgctxt "@action:button"
+msgid "Upload custom Firmware"
+msgstr "Cargar firmware personalizado"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:83
+msgctxt "@label"
+msgid "Firmware can not be updated because there is no connection with the printer."
+msgstr "No se puede actualizar el firmware porque no hay conexión con la impresora."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:91
+msgctxt "@label"
+msgid "Firmware can not be updated because the connection with the printer does not support upgrading firmware."
+msgstr "No se puede actualizar el firmware porque la conexión con la impresora no permite actualizaciones de firmware."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:98
+msgctxt "@title:window"
+msgid "Select custom firmware"
+msgstr "Seleccionar firmware personalizado"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:119
msgctxt "@title:window"
msgid "Firmware Update"
msgstr "Actualización del firmware"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:42
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:143
msgctxt "@label"
msgid "Updating firmware."
msgstr "Actualización del firmware."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:44
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:145
msgctxt "@label"
msgid "Firmware update completed."
msgstr "Actualización del firmware completada."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:46
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:147
msgctxt "@label"
msgid "Firmware update failed due to an unknown error."
msgstr "Se ha producido un error al actualizar el firmware debido a un error desconocido."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:48
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:149
msgctxt "@label"
msgid "Firmware update failed due to an communication error."
msgstr "Se ha producido un error al actualizar el firmware debido a un error de comunicación."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:50
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:151
msgctxt "@label"
msgid "Firmware update failed due to an input/output error."
msgstr "Se ha producido un error al actualizar el firmware debido a un error de entrada/salida."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:52
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:153
msgctxt "@label"
msgid "Firmware update failed due to missing firmware."
msgstr "Se ha producido un error al actualizar el firmware porque falta el firmware."
@@ -1629,22 +1717,22 @@ msgctxt "@title:window"
msgid "User Agreement"
msgstr "Acuerdo de usuario"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:57
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:46
msgctxt "@window:title"
msgid "Existing Connection"
msgstr "Conexión existente"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:59
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:48
msgctxt "@message:text"
msgid "This printer/group is already added to Cura. Please select another printer/group."
msgstr "Esta impresora o grupo de impresoras ya se ha añadido a Cura. Seleccione otra impresora o grupo de impresoras."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:76
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:65
msgctxt "@title:window"
msgid "Connect to Networked Printer"
msgstr "Conectar con la impresora en red"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:86
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:75
msgctxt "@label"
msgid ""
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
@@ -1652,333 +1740,395 @@ msgid ""
"Select your printer from the list below:"
msgstr "Para imprimir directamente en la impresora a través de la red, asegúrese de que esta está conectada a la red utilizando un cable de red o conéctela a la red wifi. Si no conecta Cura con la impresora, también puede utilizar una unidad USB para transferir archivos GCode a la impresora.\n\nSeleccione la impresora de la siguiente lista:"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:85
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
msgctxt "@action:button"
msgid "Add"
msgstr "Agregar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:95
msgctxt "@action:button"
msgid "Edit"
msgstr "Editar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:128
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:48
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:117
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:132
msgctxt "@action:button"
msgid "Remove"
msgstr "Eliminar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:125
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:114
msgctxt "@action:button"
msgid "Refresh"
msgstr "Actualizar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:207
msgctxt "@label"
msgid "If your printer is not listed, read the network printing troubleshooting guide"
msgstr "Si la impresora no aparece en la lista, lea la guía de solución de problemas de impresión y red"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:245
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:234
msgctxt "@label"
msgid "Type"
msgstr "Tipo"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:282
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:271
msgctxt "@label"
msgid "Firmware version"
msgstr "Versión de firmware"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:294
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:283
msgctxt "@label"
msgid "Address"
msgstr "Dirección"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:316
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:305
msgctxt "@label"
-msgid "This printer is not set up to host a group of Ultimaker 3 printers."
-msgstr "La impresora no está configurada para alojar un grupo de impresoras Ultimaker 3."
+msgid "This printer is not set up to host a group of printers."
+msgstr "Esta impresora no está configurada para alojar un grupo de impresoras."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:320
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:309
msgctxt "@label"
-msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
-msgstr "La impresora aloja un grupo de %1 impresoras Ultimaker 3."
+msgid "This printer is the host for a group of %1 printers."
+msgstr "Esta impresora aloja un grupo de %1 impresoras."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:330
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:319
msgctxt "@label"
msgid "The printer at this address has not yet responded."
msgstr "La impresora todavía no ha respondido en esta dirección."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:335
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:39
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:324
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:42
msgctxt "@action:button"
msgid "Connect"
msgstr "Conectar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:349
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:338
msgctxt "@title:window"
msgid "Printer Address"
msgstr "Dirección de la impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:377
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:361
msgctxt "@alabel"
msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Introduzca la dirección IP o el nombre de host de la impresora en red."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:407
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:390
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:132
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
msgstr "Aceptar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:30
-msgctxt "@title:window"
-msgid "Print over network"
-msgstr "Imprimir a través de la red"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:61
-msgctxt "@label"
-msgid "Printer selection"
-msgstr "Selección de la impresora"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:100
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:44
msgctxt "@action:button"
msgid "Print"
msgstr "Imprimir"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:36
-msgctxt "@label: arg 1 is group name"
-msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
-msgstr "%1 no está configurada para alojar un grupo de impresoras conectadas de Ultimaker 3"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:47
+msgctxt "@title:window"
+msgid "Print over network"
+msgstr "Imprimir a través de la red"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
-msgctxt "@label link to connect manager"
-msgid "Add/Remove printers"
-msgstr "Agregar/eliminar impresoras"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:79
+msgctxt "@label"
+msgid "Printer selection"
+msgstr "Selección de la impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
-msgctxt "@info:tooltip"
-msgid "Opens the print jobs page with your default web browser."
-msgstr "Abre la página de trabajos de impresión en su navegador web por defecto."
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:173
+msgctxt "@label"
+msgid "Not available"
+msgstr "No disponible"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:15
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:130
-msgctxt "@action:button"
-msgid "View print jobs"
-msgstr "Ver trabajos de impresión"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:175
+msgctxt "@label"
+msgid "Unreachable"
+msgstr "No se puede conectar"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:37
-msgctxt "@label:status"
-msgid "Preparing to print"
-msgstr "Preparando para impresión"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:39
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:263
-msgctxt "@label:status"
-msgid "Printing"
-msgstr "Imprimiendo"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:41
-msgctxt "@label:status"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:180
+msgctxt "@label"
msgid "Available"
msgstr "Disponible"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:37
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:44
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:46
msgctxt "@label:status"
-msgid "Lost connection with the printer"
-msgstr "Se ha perdido la conexión con la impresora."
+msgid "Aborted"
+msgstr "Cancelado"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:45
-msgctxt "@label:status"
-msgid "Unavailable"
-msgstr "No disponible"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label:status"
-msgid "Unknown"
-msgstr "Desconocido"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:249
-msgctxt "@label:status"
-msgid "Disabled"
-msgstr "Deshabilitado"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:265
-msgctxt "@label:status"
-msgid "Reserved"
-msgstr "Reservado"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:268
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:39
msgctxt "@label:status"
msgid "Finished"
msgstr "Terminado"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:271
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:392
-msgctxt "@label"
-msgid "Preparing to print"
-msgstr "Preparando para impresión"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:273
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:42
msgctxt "@label:status"
-msgid "Action required"
-msgstr "Acción requerida"
+msgid "Preparing"
+msgstr "Preparando"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:276
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:48
msgctxt "@label:status"
-msgid "Paused"
-msgstr "En pausa"
+msgid "Pausing"
+msgstr "Pausando"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:52
msgctxt "@label:status"
msgid "Resuming"
msgstr "Reanudando"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:280
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:54
msgctxt "@label:status"
-msgid "Print aborted"
-msgstr "Impresión cancelada"
+msgid "Action required"
+msgstr "Acción requerida"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:373
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:394
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:213
msgctxt "@label"
-msgid "Not accepting print jobs"
-msgstr "No se aceptan trabajos de impresión"
+msgid "Waiting for: Unavailable printer"
+msgstr "Esperando: impresora no disponible"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:387
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:215
msgctxt "@label"
-msgid "Finishes at: "
-msgstr "Termina a las: "
+msgid "Waiting for: First available"
+msgstr "Esperando: primera disponible"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:389
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:217
msgctxt "@label"
-msgid "Clear build plate"
-msgstr "Borrar placa de impresión"
+msgid "Waiting for: "
+msgstr "Esperando: "
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:396
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:299
msgctxt "@label"
-msgid "Waiting for configuration change"
-msgstr "Esperando a que se cambie la configuración"
+msgid "Configuration change"
+msgstr "Cambio de configuración"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:63
-msgctxt "@title"
-msgid "Print jobs"
-msgstr "Trabajos de impresión"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:93
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:365
msgctxt "@label"
-msgid "Printing"
-msgstr "Imprimiendo"
+msgid "The assigned printer, %1, requires the following configuration change(s):"
+msgstr "Es necesario modificar la siguiente configuración de la impresora asignada %1:"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:111
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:367
+msgctxt "@label"
+msgid "The printer %1 is assigned, but the job contains an unknown material configuration."
+msgstr "Se ha asignado la impresora 1%, pero el trabajo tiene una configuración de material desconocido."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:375
+msgctxt "@label"
+msgid "Change material %1 from %2 to %3."
+msgstr "Cambiar material %1, de %2 a %3."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:378
+msgctxt "@label"
+msgid "Load %3 as material %1 (This cannot be overridden)."
+msgstr "Cargar %3 como material %1 (no se puede anular)."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:381
+msgctxt "@label"
+msgid "Change print core %1 from %2 to %3."
+msgstr "Cambiar print core %1, de %2 a %3."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:384
+msgctxt "@label"
+msgid "Change build plate to %1 (This cannot be overridden)."
+msgstr "Cambiar la placa de impresión a %1 (no se puede anular)."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:404
+msgctxt "@label"
+msgid "Override"
+msgstr "Anular"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:432
+msgctxt "@label"
+msgid "Starting a print job with an incompatible configuration could damage your 3D printer. Are you sure you want to override the configuration and print %1?"
+msgstr "Iniciar un trabajo de impresión con una configuración no compatible puede causar daños en su impresora 3D. ¿Seguro de que desea sobrescribir la configuración e imprimir %1?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:435
+msgctxt "@window:title"
+msgid "Override configuration configuration and start print"
+msgstr "Sobrescribir la configuración e iniciar la impresión"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:466
+msgctxt "@label"
+msgid "Glass"
+msgstr "Vidrio"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:469
+msgctxt "@label"
+msgid "Aluminum"
+msgstr "Aluminio"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:39
+msgctxt "@label link to connect manager"
+msgid "Manage queue"
+msgstr "Administrar cola"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:60
msgctxt "@label"
msgid "Queued"
msgstr "En cola"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:170
-msgctxt "@label:title"
-msgid "Printers"
-msgstr "Impresoras"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:36
+msgctxt "@label"
+msgid "Printing"
+msgstr "Imprimiendo"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:224
-msgctxt "@action:button"
-msgid "View printers"
-msgstr "Ver impresoras"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:49
+msgctxt "@label link to connect manager"
+msgid "Manage printers"
+msgstr "Administrar impresoras"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:38
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:115
+msgctxt "@label"
+msgid "Move to top"
+msgstr "Mover al principio"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:124
+msgctxt "@label"
+msgid "Delete"
+msgstr "Borrar"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
+msgctxt "@label"
+msgid "Resume"
+msgstr "Reanudar"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
+msgctxt "@label"
+msgid "Pause"
+msgstr "Pausar"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:146
+msgctxt "@label"
+msgid "Abort"
+msgstr "Cancelar"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:178
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to move %1 to the top of the queue?"
+msgstr "¿Seguro que desea mover %1 al principio de la cola?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:179
+msgctxt "@window:title"
+msgid "Move print job to top"
+msgstr "Mover trabajo de impresión al principio"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:188
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to delete %1?"
+msgstr "¿Seguro que desea borrar %1?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:189
+msgctxt "@window:title"
+msgid "Delete print job"
+msgstr "Borrar trabajo de impresión"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:198
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to abort %1?"
+msgstr "¿Seguro que desea cancelar %1?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
+msgctxt "@window:title"
+msgid "Abort print"
+msgstr "Cancela la impresión"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:43
msgctxt "@info:tooltip"
msgid "Connect to a printer"
-msgstr "Conecta a una impresora."
+msgstr "Conecta a una impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:117
-msgctxt "@info:tooltip"
-msgid "Load the configuration of the printer into Cura"
-msgstr "Carga la configuración de la impresora en Cura."
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:118
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:121
msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Activar configuración"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:122
+msgctxt "@info:tooltip"
+msgid "Load the configuration of the printer into Cura"
+msgstr "Carga la configuración de la impresora en Cura"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:130
msgctxt "@label"
msgid "Color scheme"
msgstr "Combinación de colores"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:145
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Color del material"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:149
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Tipo de línea"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:153
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr "Velocidad"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:157
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr "Grosor de la capa"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:198
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Modo de compatibilidad"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:284
msgctxt "@label"
msgid "Show Travels"
msgstr "Mostrar desplazamientos"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:290
msgctxt "@label"
msgid "Show Helpers"
msgstr "Mostrar asistentes"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:296
msgctxt "@label"
msgid "Show Shell"
msgstr "Mostrar perímetro"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:302
msgctxt "@label"
msgid "Show Infill"
msgstr "Mostrar relleno"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:355
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Mostrar solo capas superiores"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:366
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "Mostrar cinco capas detalladas en la parte superior"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:379
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Superior o inferior"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:383
msgctxt "@label"
msgid "Inner Wall"
msgstr "Pared interior"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:448
msgctxt "@label"
msgid "min"
msgstr "mín."
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:500
msgctxt "@label"
msgid "max"
msgstr "máx."
@@ -1993,20 +2143,20 @@ msgctxt "@label"
msgid "Post Processing Scripts"
msgstr "Secuencias de comandos de posprocesamiento"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:225
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:227
msgctxt "@action"
msgid "Add a script"
msgstr "Añadir secuencia de comando"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:271
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:273
msgctxt "@label"
msgid "Settings"
msgstr "Ajustes"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:474
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:477
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
-msgstr "Cambia las secuencias de comandos de posprocesamiento."
+msgstr "Cambia las secuencias de comandos de posprocesamiento"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:16
msgctxt "@title:window"
@@ -2098,53 +2248,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Suavizado"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:37
msgctxt "@label"
msgid "Mesh Type"
msgstr "Tipo de malla"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:68
msgctxt "@label"
msgid "Normal model"
msgstr "Modelo normal"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:75
msgctxt "@label"
msgid "Print as support"
-msgstr "Imprimir según compatibilidad"
+msgstr "Imprimir como soporte"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:83
msgctxt "@label"
msgid "Don't support overlap with other models"
-msgstr "No es compatible la superposición con otros modelos"
+msgstr "No crear soporte en otros modelos (por superposición)"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:91
msgctxt "@label"
msgid "Modify settings for overlap with other models"
-msgstr "Modificar ajustes para superponer con otros modelos"
+msgstr "Modificar ajustes de otros modelos (por superposición)"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:99
msgctxt "@label"
msgid "Modify settings for infill of other models"
-msgstr "Modificar ajustes para rellenar con otros modelos"
+msgstr "Modificar ajustes del relleno de otros modelos"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:342
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:347
msgctxt "@action:button"
msgid "Select settings"
msgstr "Seleccionar ajustes"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:384
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:389
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Seleccionar ajustes o personalizar este modelo"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:432
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:437
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:98
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Filtrar..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:446
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:451
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Mostrar todo"
@@ -2166,13 +2316,13 @@ msgid "Create new"
msgstr "Crear nuevo"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:70
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:68
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:72
msgctxt "@action:title"
msgid "Summary - Cura Project"
msgstr "Resumen: proyecto de Cura"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:92
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:96
msgctxt "@action:label"
msgid "Printer settings"
msgstr "Ajustes de la impresora"
@@ -2189,18 +2339,19 @@ msgid "Update"
msgstr "Actualizar"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:143
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:105
msgctxt "@action:label"
msgid "Type"
msgstr "Tipo"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:159
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
msgctxt "@action:label"
msgid "Printer Group"
msgstr "Grupo de impresoras"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:180
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:192
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:196
msgctxt "@action:label"
msgid "Profile settings"
msgstr "Ajustes del perfil"
@@ -2212,19 +2363,20 @@ msgstr "¿Cómo debería solucionarse el conflicto en el perfil?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:216
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:308
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:216
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:220
msgctxt "@action:label"
msgid "Name"
msgstr "Nombre"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:231
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:200
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:204
msgctxt "@action:label"
msgid "Not in profile"
msgstr "No está en el perfil"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:236
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:209
msgctxt "@action:label"
msgid "%1 override"
msgid_plural "%1 overrides"
@@ -2254,7 +2406,7 @@ msgid "How should the conflict in the material be resolved?"
msgstr "¿Cómo debería solucionarse el conflicto en el material?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:327
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:233
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:237
msgctxt "@action:label"
msgid "Setting visibility"
msgstr "Visibilidad de los ajustes"
@@ -2265,13 +2417,13 @@ msgid "Mode"
msgstr "Modo"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:352
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:246
msgctxt "@action:label"
msgid "Visible settings:"
msgstr "Ajustes visibles:"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:357
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:247
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:251
msgctxt "@action:label"
msgid "%1 out of %2"
msgstr "%1 de un total de %2"
@@ -2327,40 +2479,10 @@ msgctxt "@action:button"
msgid "Move to Next Position"
msgstr "Mover a la siguiente posición"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:30
-msgctxt "@title"
-msgid "Upgrade Firmware"
-msgstr "Actualización de firmware"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:41
-msgctxt "@label"
-msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
-msgstr "El firmware es la parte del software que se ejecuta directamente en la impresora 3D. Este firmware controla los motores de pasos, regula la temperatura y, finalmente, hace que funcione la impresora."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:51
-msgctxt "@label"
-msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
-msgstr "El firmware que se envía con las nuevas impresoras funciona, pero las nuevas versiones suelen tener más funciones y mejoras."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:65
-msgctxt "@action:button"
-msgid "Automatically upgrade Firmware"
-msgstr "Actualización de firmware automática"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:75
-msgctxt "@action:button"
-msgid "Upload custom Firmware"
-msgstr "Cargar firmware personalizado"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:87
-msgctxt "@title:window"
-msgid "Select custom firmware"
-msgstr "Seleccionar firmware personalizado"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:37
msgctxt "@label"
msgid "Please select any upgrades made to this Ultimaker Original"
-msgstr "Seleccione cualquier actualización de Ultimaker Original."
+msgstr "Seleccione cualquier actualización de Ultimaker Original"
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:45
msgctxt "@label"
@@ -2463,23 +2585,23 @@ msgstr "¡Todo correcto! Ha terminado con la comprobación."
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:119
msgctxt "@label:MonitorStatus"
msgid "Not connected to a printer"
-msgstr "No está conectado a ninguna impresora."
+msgstr "No está conectado a ninguna impresora"
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:123
msgctxt "@label:MonitorStatus"
msgid "Printer does not accept commands"
-msgstr "La impresora no acepta comandos."
+msgstr "La impresora no acepta comandos"
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:133
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:197
msgctxt "@label:MonitorStatus"
msgid "In maintenance. Please check the printer"
-msgstr "En mantenimiento. Compruebe la impresora."
+msgstr "En mantenimiento. Compruebe la impresora"
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:144
msgctxt "@label:MonitorStatus"
msgid "Lost connection with the printer"
-msgstr "Se ha perdido la conexión con la impresora."
+msgstr "Se ha perdido la conexión con la impresora"
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:146
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:187
@@ -2502,29 +2624,13 @@ msgstr "Preparando..."
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:154
msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
-msgstr "Retire la impresión."
-
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
-msgctxt "@label:"
-msgid "Pause"
-msgstr "Pausar"
-
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
-msgctxt "@label:"
-msgid "Resume"
-msgstr "Reanudar"
+msgstr "Retire la impresión"
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:325
-msgctxt "@label:"
+msgctxt "@label"
msgid "Abort Print"
msgstr "Cancelar impresión"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
-msgctxt "@window:title"
-msgid "Abort print"
-msgstr "Cancela la impresión"
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:337
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
@@ -2558,19 +2664,17 @@ msgid "Customized"
msgstr "Valor personalizado"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:634
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:637
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Preguntar siempre"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:635
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Descartar y no volver a preguntar"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Guardar y no volver a preguntar"
@@ -2590,101 +2694,179 @@ msgctxt "@action:button"
msgid "Create New Profile"
msgstr "Crear nuevo perfil"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:65
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:71
msgctxt "@title"
msgid "Information"
msgstr "Información"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:94
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:100
msgctxt "@title:window"
msgid "Confirm Diameter Change"
msgstr "Confirmar cambio de diámetro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:101
msgctxt "@label (%1 is a number)"
msgid "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?"
msgstr "El nuevo diámetro del filamento está ajustado en %1 mm y no es compatible con el extrusor actual. ¿Desea continuar?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:133
msgctxt "@label"
msgid "Display Name"
msgstr "Mostrar nombre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:143
msgctxt "@label"
msgid "Brand"
msgstr "Marca"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:153
msgctxt "@label"
msgid "Material Type"
msgstr "Tipo de material"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:162
msgctxt "@label"
msgid "Color"
msgstr "Color"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:212
msgctxt "@label"
msgid "Properties"
msgstr "Propiedades"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:214
msgctxt "@label"
msgid "Density"
msgstr "Densidad"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:229
msgctxt "@label"
msgid "Diameter"
msgstr "Diámetro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:253
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:263
msgctxt "@label"
msgid "Filament Cost"
msgstr "Coste del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:269
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:280
msgctxt "@label"
msgid "Filament weight"
msgstr "Anchura del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:298
msgctxt "@label"
msgid "Filament length"
msgstr "Longitud del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:295
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:307
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Coste por metro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:309
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:321
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "Este material está vinculado a %1 y comparte alguna de sus propiedades."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:316
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:328
msgctxt "@label"
msgid "Unlink Material"
msgstr "Desvincular material"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:327
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:339
msgctxt "@label"
msgid "Description"
msgstr "Descripción"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:352
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Información sobre adherencia"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:366
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:378
msgctxt "@label"
msgid "Print settings"
msgstr "Ajustes de impresión"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:84
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
+msgctxt "@action:button"
+msgid "Activate"
+msgstr "Activar"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:101
+msgctxt "@action:button"
+msgid "Create"
+msgstr "Crear"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:114
+msgctxt "@action:button"
+msgid "Duplicate"
+msgstr "Duplicado"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
+msgctxt "@action:button"
+msgid "Import"
+msgstr "Importar"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
+msgctxt "@action:button"
+msgid "Export"
+msgstr "Exportar"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:203
+msgctxt "@action:label"
+msgid "Printer"
+msgstr "Impresora"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:262
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
+msgctxt "@title:window"
+msgid "Confirm Remove"
+msgstr "Confirmar eliminación"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:263
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
+msgctxt "@label (%1 is object name)"
+msgid "Are you sure you wish to remove %1? This cannot be undone!"
+msgstr "¿Seguro que desea eliminar %1? ¡Esta acción no se puede deshacer!"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:285
+msgctxt "@title:window"
+msgid "Import Material"
+msgstr "Importar material"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:286
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Could not import material %1: %2"
+msgstr "No se pudo importar el material en %1: %2"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:290
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully imported material %1"
+msgstr "El material se ha importado correctamente en %1"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:308
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:316
+msgctxt "@title:window"
+msgid "Export Material"
+msgstr "Exportar material"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:320
+msgctxt "@info:status Don't translate the XML tags and !"
+msgid "Failed to export material to %1: %2"
+msgstr "Se ha producido un error al exportar el material a %1: %2"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:326
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully exported material to %1"
+msgstr "El material se ha exportado correctamente a %1"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:14
msgctxt "@title:tab"
msgid "Setting Visibility"
@@ -2721,289 +2903,287 @@ msgid "Unit"
msgstr "Unidad"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:544
msgctxt "@title:tab"
msgid "General"
msgstr "General"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:142
msgctxt "@label"
msgid "Interface"
msgstr "Interfaz"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:153
msgctxt "@label"
msgid "Language:"
msgstr "Idioma:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:220
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Currency:"
msgstr "Moneda:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:235
msgctxt "@label"
msgid "Theme:"
msgstr "Tema:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:294
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:292
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Tendrá que reiniciar la aplicación para que estos cambios tengan efecto."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:311
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:309
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Segmentar automáticamente al cambiar los ajustes."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:317
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Segmentar automáticamente"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:333
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:331
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Comportamiento de la ventanilla"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:339
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Resaltar en rojo las áreas del modelo sin soporte. Sin soporte, estas áreas no se imprimirán correctamente."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:348
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Mostrar voladizos"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:357
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:355
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
-msgstr "Mueve la cámara de manera que el modelo se encuentre en el centro de la vista cuando se selecciona un modelo."
+msgstr "Mueve la cámara de manera que el modelo se encuentre en el centro de la vista cuando se selecciona un modelo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:360
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "Centrar cámara cuando se selecciona elemento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:371
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:369
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "¿Se debería invertir el comportamiento predeterminado del zoom de cura?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:376
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:374
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "Invertir la dirección del zoom de la cámara."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "¿Debería moverse el zoom en la dirección del ratón?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "Hacer zoom en la dirección del ratón"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:401
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "¿Deben moverse los modelos en la plataforma de modo que no se crucen?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:406
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Ensure models are kept apart"
-msgstr "Asegúrese de que lo modelos están separados."
+msgstr "Asegúrese de que lo modelos están separados"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:413
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "¿Deben moverse los modelos del área de impresión de modo que no toquen la placa de impresión?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:420
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:418
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Arrastrar modelos a la placa de impresión de forma automática"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
msgctxt "@info:tooltip"
msgid "Show caution message in g-code reader."
msgstr "Se muestra el mensaje de advertencia en el lector de GCode."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:439
msgctxt "@option:check"
msgid "Caution message in g-code reader"
msgstr "Mensaje de advertencia en el lector de GCode"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:449
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:447
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "¿Debe forzarse el modo de compatibilidad de la capa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:452
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "Forzar modo de compatibilidad de la vista de capas (necesario reiniciar)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:470
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:468
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Abrir y guardar archivos"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:477
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:475
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "¿Deben ajustarse los modelos al volumen de impresión si son demasiado grandes?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:482
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:480
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Escalar modelos de gran tamaño"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Un modelo puede mostrarse demasiado pequeño si su unidad son metros en lugar de milímetros, por ejemplo. ¿Deben escalarse estos modelos?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:497
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:495
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Escalar modelos demasiado pequeños"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@info:tooltip"
msgid "Should models be selected after they are loaded?"
msgstr "¿Se deberían seleccionar los modelos después de haberse cargado?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:510
msgctxt "@option:check"
msgid "Select models when loaded"
msgstr "Seleccionar modelos al abrirlos"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "¿Debe añadirse automáticamente un prefijo basado en el nombre de la impresora al nombre del trabajo de impresión?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:527
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:525
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "Agregar prefijo de la máquina al nombre del trabajo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "¿Mostrar un resumen al guardar un archivo de proyecto?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:541
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:539
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Mostrar un cuadro de diálogo de resumen al guardar el proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:551
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:549
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Comportamiento predeterminado al abrir un archivo del proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:559
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:557
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Comportamiento predeterminado al abrir un archivo del proyecto: "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
msgctxt "@option:openProject"
-msgid "Always ask"
+msgid "Always ask me this"
msgstr "Preguntar siempre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:574
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:572
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Abrir siempre como un proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:575
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Importar modelos siempre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:611
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:609
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Si ha realizado cambios en un perfil y, a continuación, ha cambiado a otro, aparecerá un cuadro de diálogo que le preguntará si desea guardar o descartar los cambios. También puede elegir el comportamiento predeterminado, así ese cuadro de diálogo no volverá a aparecer."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:618
msgctxt "@label"
-msgid "Override Profile"
-msgstr "Anular perfil"
+msgid "Profiles"
+msgstr "Perfiles"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:670
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:623
+msgctxt "@window:text"
+msgid "Default behavior for changed setting values when switching to a different profile: "
+msgstr "Comportamiento predeterminado para los valores modificados al cambiar a otro perfil: "
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
+msgctxt "@option:discardOrKeep"
+msgid "Always discard changed settings"
+msgstr "Descartar siempre los ajustes modificados"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:639
+msgctxt "@option:discardOrKeep"
+msgid "Always transfer changed settings to new profile"
+msgstr "Transferir siempre los ajustes modificados al nuevo perfil"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:673
msgctxt "@label"
msgid "Privacy"
msgstr "Privacidad"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:678
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:681
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "¿Debe Cura buscar actualizaciones cuando se abre el programa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:683
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:686
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Buscar actualizaciones al iniciar"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:697
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "¿Deben enviarse datos anónimos sobre la impresión a Ultimaker? Tenga en cuenta que no se envían ni almacenan modelos, direcciones IP ni otra información de identificación personal."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:702
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "Enviar información (anónima) de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:711
msgctxt "@action:button"
msgid "More information"
msgstr "Más información"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:729
msgctxt "@label"
msgid "Experimental"
msgstr "Experimental"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:733
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:736
msgctxt "@info:tooltip"
msgid "Use multi build plate functionality"
msgstr "Utilizar funcionalidad de placa de impresión múltiple"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:738
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:741
msgctxt "@option:check"
msgid "Use multi build plate functionality (restart required)"
msgstr "Utilizar funcionalidad de placa de impresión múltiple (reinicio requerido)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:748
-msgctxt "@info:tooltip"
-msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
-msgstr "¿Los modelos recién cargados se deben organizar en la placa de impresión? Se han usado junto con la placa de impresión múltiple (EXPERIMENTAL)"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:753
-msgctxt "@option:check"
-msgid "Do not arrange objects on load"
-msgstr "No organizar objetos al cargar"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:16
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:536
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:549
msgctxt "@title:tab"
msgid "Printers"
msgstr "Impresoras"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:90
-msgctxt "@action:button"
-msgid "Activate"
-msgstr "Activar"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:55
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:129
msgctxt "@action:button"
@@ -3021,7 +3201,7 @@ msgid "Connection:"
msgstr "Conexión:"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:162
-#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:47
+#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:55
msgctxt "@info:status"
msgid "The printer is not connected."
msgstr "La impresora no está conectada."
@@ -3034,12 +3214,12 @@ msgstr "Estado:"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:181
msgctxt "@label:MonitorStatus"
msgid "Waiting for a printjob"
-msgstr "Esperando un trabajo de impresión..."
+msgstr "Esperando un trabajo de impresión"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:193
msgctxt "@label:MonitorStatus"
msgid "Waiting for someone to clear the build plate"
-msgstr "Esperando a que alguien limpie la placa de impresión..."
+msgstr "Esperando a que alguien limpie la placa de impresión"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:199
msgctxt "@label:MonitorStatus"
@@ -3047,7 +3227,7 @@ msgid "Aborting print..."
msgstr "Cancelando impresión..."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:540
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:553
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Perfiles"
@@ -3062,18 +3242,6 @@ msgctxt "@label"
msgid "Duplicate"
msgstr "Duplicado"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:145
-msgctxt "@action:button"
-msgid "Import"
-msgstr "Importar"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:158
-msgctxt "@action:button"
-msgid "Export"
-msgstr "Exportar"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:174
msgctxt "@title:window"
msgid "Create Profile"
@@ -3084,18 +3252,6 @@ msgctxt "@title:window"
msgid "Duplicate Profile"
msgstr "Duplicar perfil"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:221
-msgctxt "@title:window"
-msgid "Confirm Remove"
-msgstr "Confirmar eliminación"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:222
-msgctxt "@label (%1 is object name)"
-msgid "Are you sure you wish to remove %1? This cannot be undone!"
-msgstr "¿Seguro que desea eliminar %1? ¡Esta acción no se puede deshacer!"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:256
msgctxt "@title:window"
msgid "Rename Profile"
@@ -3116,228 +3272,200 @@ msgctxt "@label %1 is printer name"
msgid "Printer: %1"
msgstr "Impresora: %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Protected profiles"
msgstr "Perfiles protegidos"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Custom profiles"
msgstr "Perfiles personalizados"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:468
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:480
msgctxt "@action:button"
msgid "Update profile with current settings/overrides"
msgstr "Actualizar perfil con ajustes o sobrescrituras actuales"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:475
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:487
msgctxt "@action:button"
msgid "Discard current changes"
msgstr "Descartar cambios actuales"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:504
msgctxt "@action:label"
msgid "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below."
msgstr "Este perfil utiliza los ajustes predeterminados especificados por la impresora, por eso no aparece ningún ajuste o sobrescritura en la lista que se ve a continuación."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:511
msgctxt "@action:label"
msgid "Your current settings match the selected profile."
msgstr "Los ajustes actuales coinciden con el perfil seleccionado."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:518
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:530
msgctxt "@title:tab"
msgid "Global Settings"
msgstr "Ajustes globales"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:40
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:538
-msgctxt "@title:tab"
-msgid "Materials"
-msgstr "Materiales"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:105
-msgctxt "@action:button"
-msgid "Create"
-msgstr "Crear"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:118
-msgctxt "@action:button"
-msgid "Duplicate"
-msgstr "Duplicado"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:235
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:243
-msgctxt "@title:window"
-msgid "Import Material"
-msgstr "Importar material"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:244
-msgctxt "@info:status Don't translate the XML tags or !"
-msgid "Could not import material %1: %2"
-msgstr "No se pudo importar el material en %1: %2."
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:248
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully imported material %1"
-msgstr "El material se ha importado correctamente en %1."
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:266
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:274
-msgctxt "@title:window"
-msgid "Export Material"
-msgstr "Exportar material"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:278
-msgctxt "@info:status Don't translate the XML tags and !"
-msgid "Failed to export material to %1: %2"
-msgstr "Se ha producido un error al exportar el material a %1: %2."
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:284
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully exported material to %1"
-msgstr "El material se ha exportado correctamente a %1."
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:337
-msgctxt "@action:label"
-msgid "Printer"
-msgstr "Impresora"
-
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:896
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:953
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Agregar impresora"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:195
msgctxt "@label"
msgid "Printer Name:"
msgstr "Nombre de la impresora:"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:219
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Agregar impresora"
+#: /home/ruben/Projects/Cura/resources/qml/JobSpecs.qml:84
+msgctxt "@text Print job name"
+msgid "Untitled"
+msgstr "Sin título"
+
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:15
msgctxt "@title:window"
msgid "About Cura"
msgstr "Acerca de Cura"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:55
msgctxt "@label"
msgid "version: %1"
msgstr "versión: %1"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:56
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
msgctxt "@label"
msgid "End-to-end solution for fused filament 3D printing."
msgstr "Solución completa para la impresión 3D de filamento fundido."
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:82
msgctxt "@info:credit"
msgid ""
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
"Cura proudly uses the following open source projects:"
msgstr "Ultimaker B.V. ha desarrollado Cura en cooperación con la comunidad.\nCura se enorgullece de utilizar los siguientes proyectos de código abierto:"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
msgctxt "@label"
msgid "Graphical user interface"
msgstr "Interfaz gráfica de usuario (GUI)"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
msgctxt "@label"
msgid "Application framework"
msgstr "Entorno de la aplicación"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
msgctxt "@label"
msgid "G-code generator"
msgstr "Generador de GCode"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
msgctxt "@label"
msgid "Interprocess communication library"
msgstr "Biblioteca de comunicación entre procesos"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
msgctxt "@label"
msgid "Programming language"
msgstr "Lenguaje de programación"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:124
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
msgctxt "@label"
msgid "GUI framework"
msgstr "Entorno de la GUI"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:125
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
msgctxt "@label"
msgid "GUI framework bindings"
msgstr "Enlaces del entorno de la GUI"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:126
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:140
msgctxt "@label"
msgid "C/C++ Binding library"
msgstr "Biblioteca de enlaces C/C++"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:141
msgctxt "@label"
msgid "Data interchange format"
msgstr "Formato de intercambio de datos"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:142
msgctxt "@label"
msgid "Support library for scientific computing"
msgstr "Biblioteca de apoyo para cálculos científicos"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:129
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:143
msgctxt "@label"
msgid "Support library for faster math"
msgstr "Biblioteca de apoyo para cálculos más rápidos"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:130
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:144
msgctxt "@label"
msgid "Support library for handling STL files"
msgstr "Biblioteca de apoyo para gestionar archivos STL"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:145
+msgctxt "@label"
+msgid "Support library for handling planar objects"
+msgstr "Biblioteca de compatibilidad para trabajar con objetos planos"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:146
+msgctxt "@label"
+msgid "Support library for handling triangular meshes"
+msgstr "Biblioteca de compatibilidad para trabajar con mallas triangulares"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
+msgctxt "@label"
+msgid "Support library for analysis of complex networks"
+msgstr "Biblioteca de compatibilidad para analizar redes complejas"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
msgctxt "@label"
msgid "Support library for handling 3MF files"
msgstr "Biblioteca de compatibilidad para trabajar con archivos 3MF"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:149
+msgctxt "@label"
+msgid "Support library for file metadata and streaming"
+msgstr "Biblioteca de compatibilidad para metadatos y transmisión de archivos"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:150
msgctxt "@label"
msgid "Serial communication library"
msgstr "Biblioteca de comunicación en serie"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:151
msgctxt "@label"
msgid "ZeroConf discovery library"
msgstr "Biblioteca de detección para Zeroconf"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:152
msgctxt "@label"
msgid "Polygon clipping library"
msgstr "Biblioteca de recorte de polígonos"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:153
msgctxt "@Label"
msgid "Python HTTP library"
msgstr "Biblioteca HTTP de Python"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:155
msgctxt "@label"
msgid "Font"
msgstr "Fuente"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:156
msgctxt "@label"
msgid "SVG icons"
msgstr "Iconos SVG"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:157
msgctxt "@label"
msgid "Linux cross-distribution application deployment"
msgstr "Implementación de la aplicación de distribución múltiple de Linux"
@@ -3347,7 +3475,7 @@ msgctxt "@label"
msgid "Profile:"
msgstr "Perfil:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
@@ -3355,53 +3483,53 @@ msgid ""
"Click to open the profile manager."
msgstr "Algunos valores de los ajustes o sobrescrituras son distintos a los valores almacenados en el perfil.\n\nHaga clic para abrir el administrador de perfiles."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:200
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Buscar..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:544
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:545
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Copiar valor en todos los extrusores"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:553
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:554
msgctxt "@action:menu"
msgid "Copy all changed values to all extruders"
msgstr "Copiar todos los valores cambiados en todos los extrusores"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:568
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:591
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Ocultar este ajuste"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:609
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "No mostrar este ajuste"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:590
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:613
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Mostrar este ajuste"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:614
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:426
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:637
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Configurar visibilidad de los ajustes..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:644
msgctxt "@action:inmenu"
msgid "Collapse All"
msgstr "Contraer todo"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:649
msgctxt "@action:inmenu"
msgid "Expand All"
msgstr "Ampliar todo"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:253
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
@@ -3419,17 +3547,17 @@ msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Afectado por"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:155
msgctxt "@label"
msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
msgstr "Este ajuste siempre se comparte entre extrusores. Si lo modifica, modificará el valor de todos los extrusores."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:158
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "El valor se resuelve según los valores de los extrusores. "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:189
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
@@ -3437,7 +3565,7 @@ msgid ""
"Click to restore the value of the profile."
msgstr "Este ajuste tiene un valor distinto del perfil.\n\nHaga clic para restaurar el valor del perfil."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:281
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
@@ -3481,7 +3609,7 @@ msgid "Send a custom G-code command to the connected printer. Press 'enter' to s
msgstr "Envíe un comando de GCode personalizado a la impresora conectada. Pulse «Intro» para enviar el comando."
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ExtruderBox.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:268
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:272
msgctxt "@label"
msgid "Extruder"
msgstr "Extrusor"
@@ -3534,7 +3662,7 @@ msgid "The nozzle inserted in this extruder."
msgstr "Tobera insertada en este extrusor."
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/HeatedBedBox.qml:25
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:489
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:493
msgctxt "@label"
msgid "Build plate"
msgstr "Placa de impresión"
@@ -3559,6 +3687,21 @@ msgctxt "@tooltip of pre-heat"
msgid "Heat the bed in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the bed to heat up when you're ready to print."
msgstr "Caliente la plataforma antes de imprimir. Puede continuar ajustando la impresión durante el calentamiento, así no tendrá que esperar a que la plataforma se caliente para poder imprimir."
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:13
+msgctxt "@label:category menu label"
+msgid "Material"
+msgstr "Material"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:37
+msgctxt "@label:category menu label"
+msgid "Favorites"
+msgstr "Favoritos"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:61
+msgctxt "@label:category menu label"
+msgid "Generic"
+msgstr "Genérico"
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/PrinterMenu.qml:25
msgctxt "@label:category menu label"
msgid "Network enabled printers"
@@ -3574,27 +3717,27 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Ver"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:39
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:42
msgctxt "@action:inmenu menubar:view"
msgid "&Camera position"
msgstr "&Posición de la cámara"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:58
msgctxt "@action:inmenu menubar:view"
msgid "&Build plate"
-msgstr "&Placa de impresión"
+msgstr "P&laca de impresión"
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:13
msgctxt "@action:inmenu"
msgid "Visible Settings"
msgstr "Ajustes visibles"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:42
msgctxt "@action:inmenu"
msgid "Show All Settings"
msgstr "Mostrar todos los ajustes"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:53
msgctxt "@action:inmenu"
msgid "Manage Setting Visibility..."
msgstr "Gestionar visibilidad de los ajustes..."
@@ -3655,347 +3798,346 @@ msgid ""
"G-code files cannot be modified"
msgstr "Ajustes de impresión deshabilitados\nNo se pueden modificar los archivos GCode"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:340
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr "00 h 00 min"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:359
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:358
msgctxt "@tooltip"
msgid "Time specification"
msgstr "Especificación de tiempos"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:440
msgctxt "@label"
msgid "Cost specification"
msgstr "Especificación de costes"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
msgctxt "@label m for meter"
msgid "%1m"
msgstr "%1 m"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:447
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:456
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
msgctxt "@label g for grams"
msgid "%1g"
msgstr "%1 g"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:453
msgctxt "@label"
msgid "Total:"
msgstr "Total:"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:577
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Configuración de impresión recomendada
Imprimir con los ajustes recomendados para la impresora, el material y la calidad seleccionados."
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:582
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "Configuración de impresión personalizada
Imprimir con un control muy detallado del proceso de segmentación."
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:106
msgctxt "@label"
msgid "Active print"
msgstr "Activar impresión"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:115
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:114
msgctxt "@label"
msgid "Job Name"
msgstr "Nombre del trabajo"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:122
msgctxt "@label"
msgid "Printing Time"
msgstr "Tiempo de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:130
msgctxt "@label"
msgid "Estimated time left"
msgstr "Tiempo restante estimado"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:78
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
-msgid "Toggle Fu&ll Screen"
-msgstr "A<ernar pantalla completa"
+msgid "Toggle Full Screen"
+msgstr "Alternar pantalla completa"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:85
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "Des&hacer"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "&Rehacer"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "&Salir"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:113
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
msgctxt "@action:inmenu menubar:view"
-msgid "&3D View"
-msgstr "&Vista en 3D"
+msgid "3D View"
+msgstr "Vista en 3D"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
msgctxt "@action:inmenu menubar:view"
-msgid "&Front View"
-msgstr "&Vista frontal"
+msgid "Front View"
+msgstr "Vista frontal"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
msgctxt "@action:inmenu menubar:view"
-msgid "&Top View"
-msgstr "&Vista superior"
+msgid "Top View"
+msgstr "Vista superior"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
msgctxt "@action:inmenu menubar:view"
-msgid "&Left Side View"
-msgstr "&Vista del lado izquierdo"
+msgid "Left Side View"
+msgstr "Vista del lado izquierdo"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
msgctxt "@action:inmenu menubar:view"
-msgid "&Right Side View"
-msgstr "&Vista del lado derecho"
+msgid "Right Side View"
+msgstr "Vista del lado derecho"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Configurar Cura..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:155
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "&Agregar impresora..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:161
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
msgstr "Adm&inistrar impresoras ..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Administrar materiales..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:176
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Actualizar perfil con ajustes o sobrescrituras actuales"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Descartar cambios actuales"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "&Crear perfil a partir de ajustes o sobrescrituras actuales..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:202
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Administrar perfiles..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:209
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Mostrar &documentación en línea"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "Informar de un &error"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:225
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
-msgid "&About..."
-msgstr "&Acerca de..."
+msgid "About..."
+msgstr "Acerca de..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:232
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
-msgid "Delete &Selected Model"
-msgid_plural "Delete &Selected Models"
-msgstr[0] "Eliminar modelo &seleccionado"
-msgstr[1] "Eliminar modelos &seleccionados"
+msgid "Delete Selected Model"
+msgid_plural "Delete Selected Models"
+msgstr[0] "Eliminar modelo seleccionado"
+msgstr[1] "Eliminar modelos seleccionados"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Centrar modelo seleccionado"
msgstr[1] "Centrar modelos seleccionados"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Multiplicar modelo seleccionado"
msgstr[1] "Multiplicar modelos seleccionados"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Eliminar modelo"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:278
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "Ce&ntrar modelo en plataforma"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "A&grupar modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Desagrupar modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "Co&mbinar modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
msgstr "&Multiplicar modelo..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:331
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
-msgid "&Select All Models"
-msgstr "&Seleccionar todos los modelos"
+msgid "Select All Models"
+msgstr "Seleccionar todos los modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
-msgid "&Clear Build Plate"
-msgstr "&Borrar placa de impresión"
+msgid "Clear Build Plate"
+msgstr "Borrar placa de impresión"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
+msgctxt "@action:inmenu menubar:file"
+msgid "Reload All Models"
+msgstr "Recargar todos los modelos"
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
-msgctxt "@action:inmenu menubar:file"
-msgid "Re&load All Models"
-msgstr "&Recargar todos los modelos"
-
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:360
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models To All Build Plates"
msgstr "Organizar todos los modelos en todas las placas de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:367
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Organizar todos los modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Organizar selección"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:382
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Restablecer las posiciones de todos los modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
-msgid "Reset All Model &Transformations"
-msgstr "Restablecer las &transformaciones de todos los modelos"
+msgid "Reset All Model Transformations"
+msgstr "Restablecer las transformaciones de todos los modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:396
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Abrir archivo(s)..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:404
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Nuevo proyecto..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:411
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
msgstr "&Mostrar registro del motor..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:419
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Mostrar carpeta de configuración"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:433
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse packages..."
msgstr "Examinar paquetes..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:440
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:inmenu menubar:view"
msgid "Expand/Collapse Sidebar"
msgstr "Expandir/contraer barra lateral"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:27
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Cargue un modelo en 3D"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:37
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Preparado para segmentar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:39
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "Segmentando..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:41
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Listo para %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:43
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
-msgstr "No se puede segmentar."
+msgstr "No se puede segmentar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:45
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "No se puede segmentar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Slice current printjob"
msgstr "Fragmentar trabajo de impresión actual"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Cancel slicing process"
msgstr "Cancelar proceso de fragmentación"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Preparar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Cancelar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:320
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "Seleccione el dispositivo de salida activo"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:713
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:767
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Abrir archivo(s)"
@@ -4015,129 +4157,145 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr "Ultimaker Cura"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:103
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "&Archivo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:121
+msgctxt "@title:menu menubar:file"
+msgid "&Save..."
+msgstr "&Guardar..."
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:142
+msgctxt "@title:menu menubar:file"
+msgid "&Export..."
+msgstr "&Exportar..."
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:153
msgctxt "@action:inmenu menubar:file"
-msgid "&Save Selection to File"
-msgstr "Guardar &selección en archivo"
+msgid "Export Selection..."
+msgstr "Exportar selección..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
-msgctxt "@title:menu menubar:file"
-msgid "Save &As..."
-msgstr "Guardar &como..."
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
-msgctxt "@title:menu menubar:file"
-msgid "Save &Project..."
-msgstr "Guardar &proyecto..."
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:174
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Edición"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:191
msgctxt "@title:menu"
msgid "&View"
msgstr "&Ver"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
msgctxt "@title:menu"
msgid "&Settings"
msgstr "A&justes"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
-msgctxt "@title:menu menubar:toplevel"
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+msgctxt "@title:menu menubar:settings"
msgid "&Printer"
msgstr "&Impresora"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:195
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:207
msgctxt "@title:menu"
msgid "&Material"
msgstr "&Material"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:204
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:216
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Definir como extrusor activo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:210
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:222
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:188
msgctxt "@action:inmenu"
msgid "Enable Extruder"
msgstr "Habilitar extrusor"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:217
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:229
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:194
msgctxt "@action:inmenu"
msgid "Disable Extruder"
msgstr "Deshabilitar extrusor"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:230
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:241
msgctxt "@title:menu"
+msgid "&Build plate"
+msgstr "&Placa de impresión"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:242
+msgctxt "@title:settings"
msgid "&Profile"
msgstr "&Perfil"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:252
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "E&xtensiones"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:274
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:286
msgctxt "@title:menu menubar:toplevel"
msgid "&Toolbox"
msgstr "&Cuadro de herramientas"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:281
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:294
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "Pre&ferencias"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:289
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:302
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "A&yuda"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:348
msgctxt "@label"
msgid "This package will be installed after restarting."
msgstr "Este paquete se instalará después de reiniciar."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:364
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:377
msgctxt "@action:button"
msgid "Open File"
msgstr "Abrir archivo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:547
msgctxt "@title:tab"
msgid "Settings"
msgstr "Ajustes"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:579
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:593
msgctxt "@title:window"
msgid "New project"
msgstr "Nuevo proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:594
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "¿Está seguro de que desea iniciar un nuevo proyecto? Esto borrará la placa de impresión y cualquier ajuste no guardado."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:814
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:722
+msgctxt "@title:window"
+msgid "Closing Cura"
+msgstr "Cerrando Cura"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:723
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:735
+msgctxt "@label"
+msgid "Are you sure you want to exit Cura?"
+msgstr "¿Seguro que desea salir de Cura?"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:868
msgctxt "@window:title"
msgid "Install Package"
msgstr "Instalar paquete"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:821
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:875
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Abrir archivo(s)"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:824
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:878
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "Hemos encontrado uno o más archivos de GCode entre los archivos que ha seleccionado. Solo puede abrir los archivos GCode de uno en uno. Si desea abrir un archivo GCode, seleccione solo uno."
@@ -4147,112 +4305,107 @@ msgctxt "@title:window"
msgid "Save Project"
msgstr "Guardar proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:116
-msgctxt "@action:label"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:137
msgctxt "@action:label"
msgid "Build plate"
msgstr "Placa de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:169
msgctxt "@action:label"
msgid "Extruder %1"
msgstr "Extrusor %1"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:179
msgctxt "@action:label"
msgid "%1 & material"
msgstr "%1 y material"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:268
msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "No mostrar resumen de proyecto al guardar de nuevo"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:283
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:287
msgctxt "@action:button"
msgid "Save"
msgstr "Guardar"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:192
msgctxt "@label"
msgid "Layer Height"
msgstr "Altura de capa"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:277
msgctxt "@tooltip"
msgid "This quality profile is not available for you current material and nozzle configuration. Please change these to enable this quality profile"
-msgstr "Este perfil de calidad no está disponible para la configuración de material y de tobera actual. Cámbiela para poder habilitar este perfil de calidad."
+msgstr "Este perfil de calidad no está disponible para la configuración de material y de tobera actual. Cámbiela para poder habilitar este perfil de calidad"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:450
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
-msgstr "Hay un perfil personalizado activado en este momento. Para habilitar el control deslizante de calidad, seleccione un perfil de calidad predeterminado en la pestaña Personalizado."
+msgstr "Hay un perfil personalizado activado en este momento. Para habilitar el control deslizante de calidad, seleccione un perfil de calidad predeterminado en la pestaña Personalizado"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:467
msgctxt "@label"
msgid "Print Speed"
msgstr "Velocidad de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:479
msgctxt "@label"
msgid "Slower"
msgstr "Más lento"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:490
msgctxt "@label"
msgid "Faster"
msgstr "Más rápido"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:518
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr "Ha modificado algunos ajustes del perfil. Si desea cambiarlos, hágalo en el modo personalizado."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:506
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:541
msgctxt "@label"
msgid "Infill"
msgstr "Relleno"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:740
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:777
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Un relleno gradual aumentará gradualmente la cantidad de relleno hacia arriba."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:752
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:791
msgctxt "@label"
msgid "Enable gradual"
msgstr "Habilitar gradual"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:819
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:858
msgctxt "@label"
msgid "Generate Support"
msgstr "Generar soporte"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:853
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:892
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "Generar estructuras para soportar piezas del modelo que tengan voladizos. Sin estas estructuras, estas piezas se romperían durante la impresión."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:925
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:964
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Seleccione qué extrusor se utilizará como soporte. Esta opción formará estructuras de soporte por debajo del modelo para evitar que éste se combe o la impresión se haga en el aire."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:948
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:987
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Adherencia de la placa de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1003
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1042
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
msgstr "Habilita la impresión de un borde o una balsa. Esta opción agregará un área plana alrededor del objeto, que es fácil de cortar después."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1043
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1082
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "¿Necesita ayuda para mejorar sus impresiones? Lea las Guías de solución de problemas de Ultimaker"
@@ -4299,23 +4452,22 @@ msgctxt "@label"
msgid "Printer type"
msgstr "Tipo de impresora"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:376
msgctxt "@label"
msgid "Material"
msgstr "Material"
-# Added after the string freeze.
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:538
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:543
msgctxt "@label"
-msgid "Use adhesion sheet or glue with this material combination"
-msgstr "Use láminas de adherencia o pegamento con esta combinación de materiales"
+msgid "Use glue with this material combination"
+msgstr "Utilizar pegamento con esta combinación de materiales"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:570
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:575
msgctxt "@label"
msgid "Check compatibility"
msgstr "Comprobar compatibilidad"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:588
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:593
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Haga clic para comprobar la compatibilidad de los materiales en Utimaker.com."
@@ -4405,16 +4557,6 @@ msgctxt "name"
msgid "God Mode"
msgstr "God Mode"
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "description"
-msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
-msgstr "Acepta códigos GCode y los envía a un enrutador Doodle3D por medio de wifi."
-
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "name"
-msgid "Doodle3D WiFi-Box"
-msgstr "Enrutador Doodle3D"
-
#: ChangeLogPlugin/plugin.json
msgctxt "description"
msgid "Shows changes since latest checked version."
@@ -4425,6 +4567,16 @@ msgctxt "name"
msgid "Changelog"
msgstr "Registro de cambios"
+#: FirmwareUpdater/plugin.json
+msgctxt "description"
+msgid "Provides a machine actions for updating firmware."
+msgstr "Proporciona opciones a la máquina para actualizar el firmware."
+
+#: FirmwareUpdater/plugin.json
+msgctxt "name"
+msgid "Firmware Updater"
+msgstr "Actualizador de firmware"
+
#: ProfileFlattener/plugin.json
msgctxt "description"
msgid "Create a flattend quality changes profile."
@@ -4448,7 +4600,7 @@ msgstr "Impresión USB"
#: UserAgreement/plugin.json
msgctxt "description"
msgid "Ask the user once if he/she agrees with our license."
-msgstr "Preguntar al usuario una vez si acepta la licencia"
+msgstr "Preguntar al usuario una vez si acepta la licencia."
#: UserAgreement/plugin.json
msgctxt "name"
@@ -4495,16 +4647,6 @@ msgctxt "name"
msgid "Prepare Stage"
msgstr "Fase de preparación"
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "description"
-msgid "Provides an edit window for direct script editing."
-msgstr "Proporciona una ventana de edición para la edición directa de secuencias de comandos."
-
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "name"
-msgid "Live scripting tool"
-msgstr "Herramienta de secuencia de comandos en directo"
-
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4568,7 +4710,7 @@ msgstr "Lector de GCode comprimido"
#: PostProcessingPlugin/plugin.json
msgctxt "description"
msgid "Extension that allows for user created scripts for post processing"
-msgstr "Extensión que permite el posprocesamiento de las secuencias de comandos creadas por los usuarios."
+msgstr "Extensión que permite el posprocesamiento de las secuencias de comandos creadas por los usuarios"
#: PostProcessingPlugin/plugin.json
msgctxt "name"
@@ -4578,7 +4720,7 @@ msgstr "Posprocesamiento"
#: SupportEraser/plugin.json
msgctxt "description"
msgid "Creates an eraser mesh to block the printing of support in certain places"
-msgstr "Crea una malla de borrado que impide la impresión de soportes en determinados lugares."
+msgstr "Crea una malla de borrado que impide la impresión de soportes en determinados lugares"
#: SupportEraser/plugin.json
msgctxt "name"
@@ -4615,16 +4757,6 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Lector de perfiles antiguos de Cura"
-#: CuraBlenderPlugin/plugin.json
-msgctxt "description"
-msgid "Helps to open Blender files directly in Cura."
-msgstr "Ayuda a abrir archivos de Blender directamente en Cura."
-
-#: CuraBlenderPlugin/plugin.json
-msgctxt "name"
-msgid "Blender Integration (experimental)"
-msgstr "Integración de Blender (experimental)"
-
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4675,6 +4807,16 @@ msgctxt "name"
msgid "Version Upgrade 2.7 to 3.0"
msgstr "Actualización de la versión 2.7 a la 3.0"
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "description"
+msgid "Upgrades configurations from Cura 3.4 to Cura 3.5."
+msgstr "Actualiza las configuraciones de Cura 3.4 a Cura 3.5."
+
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "name"
+msgid "Version Upgrade 3.4 to 3.5"
+msgstr "Actualización de la versión 3.4 a la 3.5"
+
#: VersionUpgrade/VersionUpgrade30to31/plugin.json
msgctxt "description"
msgid "Upgrades configurations from Cura 3.0 to Cura 3.1."
@@ -4815,6 +4957,299 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Lector de perfiles de Cura"
+#~ msgctxt "@warning:status"
+#~ msgid "Please generate G-code before saving."
+#~ msgstr "Genere un G-code antes de guardar."
+
+#~ msgctxt "@item:inmenu"
+#~ msgid "Profile Assistant"
+#~ msgstr "Asistente del perfil"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Profile Assistant"
+#~ msgstr "Asistente del perfil"
+
+#~ msgctxt "@action"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Actualizar firmware"
+
+#~ msgctxt "@label unknown material"
+#~ msgid "Unknown"
+#~ msgstr "Desconocido"
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "No custom profile to import in file {0}"
+#~ msgstr "No hay ningún perfil personalizado que importar en el archivo {0}"
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "This profile {0} contains incorrect data, could not import it."
+#~ msgstr "Este perfil {0} contiene datos incorrectos, no se han podido importar."
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it."
+#~ msgstr "El equipo definido en el perfil {0} ({1}) no coincide con el equipo actual ({2}), no se ha podido importar."
+
+#~ msgctxt "@title:window"
+#~ msgid "Confirm uninstall "
+#~ msgstr "Confirmar desinstalación "
+
+#~ msgctxt "@label:status"
+#~ msgid "Paused"
+#~ msgstr "En pausa"
+
+#~ msgctxt "@action:button"
+#~ msgid "Previous"
+#~ msgstr "Anterior"
+
+#~ msgctxt "@action:button"
+#~ msgid "Next"
+#~ msgstr "Siguiente"
+
+#~ msgctxt "@label"
+#~ msgid "Tip"
+#~ msgstr "Consejo"
+
+#~ msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
+#~ msgid "%1m / ~ %2g / ~ %4 %3"
+#~ msgstr "%1 m/~ %2 g/~ %4 %3"
+
+#~ msgctxt "@label Print estimates: m for meters, g for grams"
+#~ msgid "%1m / ~ %2g"
+#~ msgstr "%1 m/~ %2 g"
+
+#~ msgctxt "@label"
+#~ msgid "Print experiment"
+#~ msgstr "Ensayo de impresión"
+
+#~ msgctxt "@label"
+#~ msgid "Checklist"
+#~ msgstr "Lista de verificación"
+
+#~ msgctxt "@title"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Actualización de firmware"
+
+#~ msgctxt "description"
+#~ msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+#~ msgstr "Permite a los fabricantes de material crear nuevos perfiles de material y calidad mediante una IU integrada."
+
+#~ msgctxt "name"
+#~ msgid "Print Profile Assistant"
+#~ msgstr "Imprimir asistente del perfil"
+
+#~ msgctxt "@action:button"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Imprimir con un enrutador Doodle3D"
+
+#~ msgctxt "@properties:tooltip"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Imprimir con un enrutador Doodle3D"
+
+#~ msgctxt "@info:status"
+#~ msgid "Connecting to Doodle3D Connect"
+#~ msgstr "Conectar con Doodle3D Connect"
+
+#~ msgctxt "@info:status"
+#~ msgid "Sending data to Doodle3D Connect"
+#~ msgstr "Enviando datos a Doodle3D Connect"
+
+#~ msgctxt "@info:status"
+#~ msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
+#~ msgstr "No se pueden enviar datos a Doodle3D Connect. ¿Hay otro trabajo que todavía esté activo?"
+
+#~ msgctxt "@info:status"
+#~ msgid "Storing data on Doodle3D Connect"
+#~ msgstr "Guardando datos en Doodle3D Connect"
+
+#~ msgctxt "@info:status"
+#~ msgid "File sent to Doodle3D Connect"
+#~ msgstr "Archivo enviado a Doodle3D Connect"
+
+#~ msgctxt "@action:button"
+#~ msgid "Open Connect..."
+#~ msgstr "Abrir Connect..."
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Open the Doodle3D Connect web interface"
+#~ msgstr "Abrir la interfaz web de Doodle3D Connect"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Blender file"
+#~ msgstr "Archivo Blender"
+
+#~ msgctxt "@info:status"
+#~ msgid ""
+#~ "Could not export using \"{}\" quality!\n"
+#~ "Felt back to \"{}\"."
+#~ msgstr ""
+#~ "No ha podido exportarse con la calidad \"{}\"\n"
+#~ "Retroceder a \"{}\"."
+
+#~ msgctxt "@label"
+#~ msgid "Contact"
+#~ msgstr "Contacto"
+
+#~ msgctxt "@label"
+#~ msgid "This printer is not set up to host a group of Ultimaker 3 printers."
+#~ msgstr "La impresora no está configurada para alojar un grupo de impresoras Ultimaker 3."
+
+#~ msgctxt "@label"
+#~ msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
+#~ msgstr "La impresora aloja un grupo de %1 impresoras Ultimaker 3."
+
+#~ msgctxt "@label: arg 1 is group name"
+#~ msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
+#~ msgstr "%1 no está configurada para alojar un grupo de impresoras conectadas de Ultimaker 3"
+
+#~ msgctxt "@label link to connect manager"
+#~ msgid "Add/Remove printers"
+#~ msgstr "Agregar/eliminar impresoras"
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Opens the print jobs page with your default web browser."
+#~ msgstr "Abre la página de trabajos de impresión en su navegador web por defecto."
+
+#~ msgctxt "@action:button"
+#~ msgid "View print jobs"
+#~ msgstr "Ver trabajos de impresión"
+
+#~ msgctxt "@label:status"
+#~ msgid "Preparing to print"
+#~ msgstr "Preparando para impresión"
+
+#~ msgctxt "@label:status"
+#~ msgid "Printing"
+#~ msgstr "Imprimiendo"
+
+#~ msgctxt "@label:status"
+#~ msgid "Available"
+#~ msgstr "Disponible"
+
+#~ msgctxt "@label:status"
+#~ msgid "Lost connection with the printer"
+#~ msgstr "Se ha perdido la conexión con la impresora."
+
+#~ msgctxt "@label:status"
+#~ msgid "Unavailable"
+#~ msgstr "No disponible"
+
+#~ msgctxt "@label:status"
+#~ msgid "Unknown"
+#~ msgstr "Desconocido"
+
+#~ msgctxt "@label:status"
+#~ msgid "Disabled"
+#~ msgstr "Deshabilitado"
+
+#~ msgctxt "@label:status"
+#~ msgid "Reserved"
+#~ msgstr "Reservado"
+
+#~ msgctxt "@label"
+#~ msgid "Preparing to print"
+#~ msgstr "Preparando para impresión"
+
+#~ msgctxt "@label:status"
+#~ msgid "Print aborted"
+#~ msgstr "Impresión cancelada"
+
+#~ msgctxt "@label"
+#~ msgid "Not accepting print jobs"
+#~ msgstr "No se aceptan trabajos de impresión"
+
+#~ msgctxt "@label"
+#~ msgid "Finishes at: "
+#~ msgstr "Termina a las: "
+
+#~ msgctxt "@label"
+#~ msgid "Clear build plate"
+#~ msgstr "Borrar placa de impresión"
+
+#~ msgctxt "@label"
+#~ msgid "Waiting for configuration change"
+#~ msgstr "Esperando a que se cambie la configuración"
+
+#~ msgctxt "@title"
+#~ msgid "Print jobs"
+#~ msgstr "Trabajos de impresión"
+
+#~ msgctxt "@label:title"
+#~ msgid "Printers"
+#~ msgstr "Impresoras"
+
+#~ msgctxt "@action:button"
+#~ msgid "View printers"
+#~ msgstr "Ver impresoras"
+
+#~ msgctxt "@label:"
+#~ msgid "Pause"
+#~ msgstr "Pausar"
+
+#~ msgctxt "@label:"
+#~ msgid "Resume"
+#~ msgstr "Reanudar"
+
+#~ msgctxt "@label:"
+#~ msgid "Abort Print"
+#~ msgstr "Cancelar impresión"
+
+#~ msgctxt "@option:openProject"
+#~ msgid "Always ask"
+#~ msgstr "Preguntar siempre"
+
+#~ msgctxt "@label"
+#~ msgid "Override Profile"
+#~ msgstr "Anular perfil"
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+#~ msgstr "¿Los modelos recién cargados se deben organizar en la placa de impresión? Se han usado junto con la placa de impresión múltiple (EXPERIMENTAL)"
+
+#~ msgctxt "@option:check"
+#~ msgid "Do not arrange objects on load"
+#~ msgstr "No organizar objetos al cargar"
+
+#~ msgctxt "@action:inmenu menubar:file"
+#~ msgid "&Save Selection to File"
+#~ msgstr "&Guardar selección en archivo"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save &As..."
+#~ msgstr "Guardar &como..."
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save &Project..."
+#~ msgstr "Guardar &proyecto..."
+
+# Added after the string freeze.
+#~ msgctxt "@label"
+#~ msgid "Use adhesion sheet or glue with this material combination"
+#~ msgstr "Use láminas de adherencia o pegamento con esta combinación de materiales"
+
+#~ msgctxt "description"
+#~ msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
+#~ msgstr "Acepta códigos GCode y los envía a un enrutador Doodle3D por medio de wifi."
+
+#~ msgctxt "name"
+#~ msgid "Doodle3D WiFi-Box"
+#~ msgstr "Enrutador Doodle3D"
+
+#~ msgctxt "description"
+#~ msgid "Provides an edit window for direct script editing."
+#~ msgstr "Proporciona una ventana de edición para la edición directa de secuencias de comandos."
+
+#~ msgctxt "name"
+#~ msgid "Live scripting tool"
+#~ msgstr "Herramienta de secuencia de comandos en directo"
+
+#~ msgctxt "description"
+#~ msgid "Helps to open Blender files directly in Cura."
+#~ msgstr "Ayuda a abrir archivos de Blender directamente en Cura."
+
+#~ msgctxt "name"
+#~ msgid "Blender Integration (experimental)"
+#~ msgstr "Integración de Blender (experimental)"
+
#~ msgctxt "@info:title"
#~ msgid "Model Checker Warning"
#~ msgstr "Advertencia del comprobador de modelos"
@@ -5082,10 +5517,6 @@ msgstr "Lector de perfiles de Cura"
#~ msgid "Browse plugins..."
#~ msgstr "Examinar complementos..."
-#~ msgctxt "@title:menu"
-#~ msgid "&Build plate"
-#~ msgstr "&Placa de impresión"
-
#~ msgctxt "@title:menu menubar:toplevel"
#~ msgid "P&lugins"
#~ msgstr "&Complementos"
@@ -5311,14 +5742,6 @@ msgstr "Lector de perfiles de Cura"
#~ "\n"
#~ " Disculpe."
-#~ msgctxt "@item:inmenu"
-#~ msgid "Profile Assistant"
-#~ msgstr "Asistente del perfil"
-
-#~ msgctxt "@item:inlistbox"
-#~ msgid "Profile Assistant"
-#~ msgstr "Asistente del perfil"
-
#~ msgctxt "@item:material"
#~ msgid "No material loaded"
#~ msgstr "No se ha cargado material."
@@ -5449,14 +5872,6 @@ msgstr "Lector de perfiles de Cura"
#~ msgid "Configure setting visiblity..."
#~ msgstr "Configurar la visibilidad de los ajustes..."
-#~ msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
-#~ msgid "%1m / ~ %2g / ~ %4 %3"
-#~ msgstr "%1 m/~ %2 g/~ %4 %3"
-
-#~ msgctxt "@label Print estimates: m for meters, g for grams"
-#~ msgid "%1m / ~ %2g"
-#~ msgstr "%1 m/~ %2 g"
-
#~ msgctxt "@title:menuitem %1 is the automatically selected material"
#~ msgid "Automatic: %1"
#~ msgstr "Automático: %1"
@@ -5493,14 +5908,6 @@ msgstr "Lector de perfiles de Cura"
#~ msgid "GCode Profile Reader"
#~ msgstr "Lector de perfiles Gcode"
-#~ msgctxt "description"
-#~ msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
-#~ msgstr "Permite a los fabricantes de material crear nuevos perfiles de material y calidad mediante una IU integrada."
-
-#~ msgctxt "name"
-#~ msgid "Print Profile Assistant"
-#~ msgstr "Imprimir asistente del perfil"
-
#~ msgctxt "@info:status"
#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
#~ msgstr "Se han producido varios errores al abrir el archivo de SolidWorks. Compruebe que el archivo se puede abrir correctamente en SolidWorks."
@@ -5697,10 +6104,6 @@ msgstr "Lector de perfiles de Cura"
#~ msgid "This printer is the host for a group of %1 connected Ultimaker 3 printers"
#~ msgstr "La impresora aloja un grupo de %1 impresoras conectadas de Ultimaker 3"
-#~ msgctxt "@label:status"
-#~ msgid "Preparing"
-#~ msgstr "Preparando"
-
#~ msgctxt "@label"
#~ msgid "Completed on: "
#~ msgstr "Completado el: "
diff --git a/resources/i18n/es_ES/fdmextruder.def.json.po b/resources/i18n/es_ES/fdmextruder.def.json.po
index e8f44ef4f1..3da8d5251f 100644
--- a/resources/i18n/es_ES/fdmextruder.def.json.po
+++ b/resources/i18n/es_ES/fdmextruder.def.json.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0000\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
+"PO-Revision-Date: 2018-09-28 14:25+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -166,6 +166,16 @@ msgctxt "extruder_prime_pos_z description"
msgid "The Z coordinate of the position where the nozzle primes at the start of printing."
msgstr "Coordenada Z de la posición en la que la tobera queda preparada al inicio de la impresión."
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number label"
+msgid "Extruder Print Cooling Fan"
+msgstr "Ventilador de refrigeración de impresión del extrusor"
+
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number description"
+msgid "The number of the print cooling fan associated with this extruder. Only change this from the default value of 0 when you have a different print cooling fan for each extruder."
+msgstr "Número del ventilador de refrigeración de impresión asociado al extrusor. Modifique el valor predeterminado 0 solo cuando disponga de un ventilador de refrigeración de impresión diferente para cada extrusor."
+
#: fdmextruder.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
diff --git a/resources/i18n/es_ES/fdmprinter.def.json.po b/resources/i18n/es_ES/fdmprinter.def.json.po
index b5cd50638f..3b519e7aec 100644
--- a/resources/i18n/es_ES/fdmprinter.def.json.po
+++ b/resources/i18n/es_ES/fdmprinter.def.json.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-03-29 08:36+0200\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
+"PO-Revision-Date: 2018-09-28 14:56+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -81,6 +81,16 @@ msgctxt "material_guid description"
msgid "GUID of the material. This is set automatically. "
msgstr "GUID del material. Este valor se define de forma automática. "
+#: fdmprinter.def.json
+msgctxt "material_diameter label"
+msgid "Diameter"
+msgstr "Diámetro"
+
+#: fdmprinter.def.json
+msgctxt "material_diameter description"
+msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
+msgstr "Ajusta el diámetro del filamento utilizado. Este valor debe coincidir con el diámetro del filamento utilizado."
+
#: fdmprinter.def.json
msgctxt "material_bed_temp_wait label"
msgid "Wait for Build Plate Heatup"
@@ -229,7 +239,7 @@ msgstr "Número de extrusores habilitados"
#: fdmprinter.def.json
msgctxt "extruders_enabled_count description"
msgid "Number of extruder trains that are enabled; automatically set in software"
-msgstr "Número de trenes extrusores habilitados y configurados en el software de forma automática."
+msgstr "Número de trenes extrusores habilitados y configurados en el software de forma automática"
#: fdmprinter.def.json
msgctxt "machine_nozzle_tip_outer_diameter label"
@@ -534,7 +544,7 @@ msgstr "Aceleración máxima sobre el eje X"
#: fdmprinter.def.json
msgctxt "machine_max_acceleration_x description"
msgid "Maximum acceleration for the motor of the X-direction"
-msgstr "Aceleración máxima del motor de la dirección X."
+msgstr "Aceleración máxima del motor de la dirección X"
#: fdmprinter.def.json
msgctxt "machine_max_acceleration_y label"
@@ -1014,7 +1024,7 @@ msgstr "Patrón superior/inferior"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern description"
msgid "The pattern of the top/bottom layers."
-msgstr "Patrón de las capas superiores/inferiores"
+msgstr "Patrón de las capas superiores/inferiores."
#: fdmprinter.def.json
msgctxt "top_bottom_pattern option lines"
@@ -1056,6 +1066,16 @@ msgctxt "top_bottom_pattern_0 option zigzag"
msgid "Zig Zag"
msgstr "Zigzag"
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons label"
+msgid "Connect Top/Bottom Polygons"
+msgstr "Conectar polígonos superiores/inferiores"
+
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons description"
+msgid "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happen midway over infill this feature can reduce the top surface quality."
+msgstr "Conecta las trayectorias de forro superior/inferior cuando están próximas entre sí. Al habilitar este ajuste, en el patrón concéntrico se reduce considerablemente el tiempo de desplazamiento, pero las conexiones pueden producirse en mitad del relleno, con lo que bajaría la calidad de la superficie superior."
+
#: fdmprinter.def.json
msgctxt "skin_angles label"
msgid "Top/Bottom Line Directions"
@@ -1136,6 +1156,26 @@ msgctxt "travel_compensate_overlapping_walls_x_enabled description"
msgid "Compensate the flow for parts of an inner wall being printed where there is already a wall in place."
msgstr "Compensa el flujo en partes de una pared interior que se están imprimiendo donde ya hay una pared."
+#: fdmprinter.def.json
+msgctxt "wall_min_flow label"
+msgid "Minimum Wall Flow"
+msgstr "Flujo de pared mínimo"
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow description"
+msgid "Minimum allowed percentage flow for a wall line. The wall overlap compensation reduces a wall's flow when it lies close to an existing wall. Walls whose flow is less than this value will be replaced with a travel move. When using this setting, you must enable the wall overlap compensation and print the outer wall before inner walls."
+msgstr "Porcentaje mínimo de flujo permitido en una línea de pared. La compensación de superposición reduce el flujo de pared cuando se coloca cerca de otra pared. Las paredes con flujos inferiores a este valor se sustituirán con un movimiento de desplazamiento. Al utilizar este ajuste debe habilitar la compensación de superposición de pared e imprimir la pared exterior antes que las interiores."
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract label"
+msgid "Prefer Retract"
+msgstr "Preferencia de retracción"
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract description"
+msgid "If enabled, retraction is used rather than combing for travel moves that replace walls whose flow is below the minimum flow threshold."
+msgstr "Si se habilita esta opción, se utilizará retracción en lugar de peinada para los movimientos de desplazamiento que sustituyen las paredes cuyo flujo está por debajo de los límites mínimos de flujo."
+
#: fdmprinter.def.json
msgctxt "fill_perimeter_gaps label"
msgid "Fill Gaps Between Walls"
@@ -1354,7 +1394,7 @@ msgstr "Espaciado de líneas del alisado"
#: fdmprinter.def.json
msgctxt "ironing_line_spacing description"
msgid "The distance between the lines of ironing."
-msgstr "Distancia entre las líneas del alisado"
+msgstr "Distancia entre las líneas del alisado."
#: fdmprinter.def.json
msgctxt "ironing_flow label"
@@ -1453,8 +1493,8 @@ msgstr "Patrón de relleno"
#: fdmprinter.def.json
msgctxt "infill_pattern description"
-msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
-msgstr "Patrón del material de relleno de la impresión. El relleno de línea y zigzag cambia de dirección en capas alternas, con lo que se reduce el coste del material. Los patrones de rejilla, triángulo, trihexagonal, cúbico, de octeto, cúbico bitruncado y transversal y concéntrico se imprimen en todas las capas por completo. El relleno cúbico, cúbico bitruncado y de octeto cambian en cada capa para proporcionar una distribución de fuerza equitativa en cada dirección."
+msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Gyroid, cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
+msgstr "Patrón del material de relleno de la impresión. El relleno de línea y zigzag cambia de dirección en capas alternas, con lo que se reduce el coste de material. Los patrones de rejilla, triángulo, trihexágono, cubo, octeto, cubo bitruncado, transversal y concéntrico se imprimen en todas las capas por completo. El relleno giroide, cúbico, cúbico bitruncado y de octeto cambian en cada capa para proporcionar una distribución de fuerza equitativa en cada dirección."
#: fdmprinter.def.json
msgctxt "infill_pattern option grid"
@@ -1501,11 +1541,6 @@ msgctxt "infill_pattern option concentric"
msgid "Concentric"
msgstr "Concéntrico"
-#: fdmprinter.def.json
-msgctxt "infill_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concéntrico 3D"
-
#: fdmprinter.def.json
msgctxt "infill_pattern option zigzag"
msgid "Zig Zag"
@@ -1521,6 +1556,11 @@ msgctxt "infill_pattern option cross_3d"
msgid "Cross 3D"
msgstr "Cruz 3D"
+#: fdmprinter.def.json
+msgctxt "infill_pattern option gyroid"
+msgid "Gyroid"
+msgstr "Giroide"
+
#: fdmprinter.def.json
msgctxt "zig_zaggify_infill label"
msgid "Connect Infill Lines"
@@ -1531,6 +1571,16 @@ msgctxt "zig_zaggify_infill description"
msgid "Connect the ends where the infill pattern meets the inner wall using a line which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduce the effects of infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
msgstr "Conectar los extremos donde los patrones de relleno se juntan con la pared interior usando una línea que siga la forma de esta. Habilitar este ajuste puede lograr que el relleno se adhiera mejor a las paredes y se reduzca el efecto del relleno sobre la calidad de las superficies verticales. Deshabilitar este ajuste reduce la cantidad de material utilizado."
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons label"
+msgid "Connect Infill Polygons"
+msgstr "Conectar polígonos de relleno"
+
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons description"
+msgid "Connect infill paths where they run next to each other. For infill patterns which consist of several closed polygons, enabling this setting greatly reduces the travel time."
+msgstr "Conectar las trayectorias de polígonos cuando están próximas entre sí. Habilitar esta opción reduce considerablemente el tiempo de desplazamiento en los patrones de relleno que constan de varios polígonos cerrados."
+
#: fdmprinter.def.json
msgctxt "infill_angles label"
msgid "Infill Line Directions"
@@ -1554,13 +1604,35 @@ msgstr "El patrón de relleno se mueve esta distancia a lo largo del eje X."
#: fdmprinter.def.json
msgctxt "infill_offset_y label"
msgid "Infill Y Offset"
-msgstr "Desplazamiento del relleno sobre el eje X"
+msgstr "Desplazamiento del relleno sobre el eje Y"
#: fdmprinter.def.json
msgctxt "infill_offset_y description"
msgid "The infill pattern is moved this distance along the Y axis."
msgstr "El patrón de relleno se mueve esta distancia a lo largo del eje Y."
+#: fdmprinter.def.json
+msgctxt "infill_multiplier label"
+msgid "Infill Line Multiplier"
+msgstr "Multiplicador de línea de relleno"
+
+#: fdmprinter.def.json
+msgctxt "infill_multiplier description"
+msgid "Convert each infill line to this many lines. The extra lines do not cross over each other, but avoid each other. This makes the infill stiffer, but increases print time and material usage."
+msgstr "Multiplicar cada línea de relleno. Las líneas adicionales no se cruzan entre sí, sino que se evitan entre ellas. Esto consigue un relleno más rígido, pero incrementa el tiempo de impresión y el uso de material."
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count label"
+msgid "Extra Infill Wall Count"
+msgstr "Recuento de líneas de pared adicional"
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count description"
+msgid ""
+"Add extra walls around the infill area. Such walls can make top/bottom skin lines sag down less which means you need less top/bottom skin layers for the same quality at the cost of some extra material.\n"
+"This feature can combine with the Connect Infill Polygons to connect all the infill into a single extrusion path without the need for travels or retractions if configured right."
+msgstr "Agregar paredes adicionales alrededor del área de relleno. Estas paredes pueden hacer que las líneas del forro superior/inferior se aflojen menos, lo que significa que necesitaría menos capas de forro superior/inferior para obtener la misma calidad utilizando algo más de material.\nPuede utilizar esta función junto a la de Conectar polígonos de relleno para conectar todo el relleno en una única trayectoria de extrusión sin necesidad de desplazamientos ni retracciones si se configura correctamente."
+
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
msgid "Cubic Subdivision Shell"
@@ -1789,7 +1861,7 @@ msgstr "Temperatura de impresión predeterminada"
#: fdmprinter.def.json
msgctxt "default_material_print_temperature description"
msgid "The default temperature used for printing. This should be the \"base\" temperature of a material. All other print temperatures should use offsets based on this value"
-msgstr "La temperatura predeterminada que se utiliza para imprimir. Debería ser la temperatura básica del material. Las demás temperaturas de impresión deberían calcularse a partir de este valor."
+msgstr "La temperatura predeterminada que se utiliza para imprimir. Debería ser la temperatura básica del material. Las demás temperaturas de impresión deberían calcularse a partir de este valor"
#: fdmprinter.def.json
msgctxt "material_print_temperature label"
@@ -1849,7 +1921,7 @@ msgstr "Temperatura predeterminada de la placa de impresión"
#: fdmprinter.def.json
msgctxt "default_material_bed_temperature description"
msgid "The default temperature used for the heated build plate. This should be the \"base\" temperature of a build plate. All other print temperatures should use offsets based on this value"
-msgstr "La temperatura predeterminada que se utiliza en placa de impresión caliente. Debería ser la temperatura básica de una placa de impresión. Las demás temperaturas de impresión deberían calcularse a partir de este valor."
+msgstr "La temperatura predeterminada que se utiliza en placa de impresión caliente. Debería ser la temperatura básica de una placa de impresión. Las demás temperaturas de impresión deberían calcularse a partir de este valor"
#: fdmprinter.def.json
msgctxt "material_bed_temperature label"
@@ -1871,16 +1943,6 @@ msgctxt "material_bed_temperature_layer_0 description"
msgid "The temperature used for the heated build plate at the first layer."
msgstr "Temperatura de la placa de impresión una vez caliente en la primera capa."
-#: fdmprinter.def.json
-msgctxt "material_diameter label"
-msgid "Diameter"
-msgstr "Diámetro"
-
-#: fdmprinter.def.json
-msgctxt "material_diameter description"
-msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
-msgstr "Ajusta el diámetro del filamento utilizado. Este valor debe coincidir con el diámetro del filamento utilizado."
-
#: fdmprinter.def.json
msgctxt "material_adhesion_tendency label"
msgid "Adhesion Tendency"
@@ -2718,8 +2780,8 @@ msgstr "Modo Peinada"
#: fdmprinter.def.json
msgctxt "retraction_combing description"
-msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
-msgstr "La opción de peinada mantiene la tobera dentro de las áreas ya impresas al desplazarse. Esto ocasiona movimientos de desplazamiento ligeramente más largos, pero reduce la necesidad de realizar retracciones. Si se desactiva la opción de peinada, el material se retraerá y la tobera se moverá en línea recta hasta el siguiente punto. Otra posibilidad es evitar la peinada en áreas de forro superiores/inferiores peinando solo dentro del relleno."
+msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas and also to only comb within the infill. Note that the 'Within Infill' option behaves exactly like the 'Not in Skin' option in earlier Cura releases."
+msgstr "La opción de peinada mantiene la tobera dentro de las áreas ya impresas al desplazarse. Esto ocasiona movimientos de desplazamiento ligeramente más largos, pero reduce la necesidad de realizar retracciones. Si se desactiva la opción de peinada, el material se retraerá y la tobera se moverá en línea recta hasta el siguiente punto. Otra posibilidad es evitar la peinada en áreas de forro superiores/inferiores y además peinar solo en el relleno. La opción de «Sobre el relleno» actúa exactamente igual que la «No está en el forro» de las versiones de Cura anteriores."
#: fdmprinter.def.json
msgctxt "retraction_combing option off"
@@ -2736,6 +2798,11 @@ msgctxt "retraction_combing option noskin"
msgid "Not in Skin"
msgstr "No en el forro"
+#: fdmprinter.def.json
+msgctxt "retraction_combing option infill"
+msgid "Within Infill"
+msgstr "Sobre el relleno"
+
#: fdmprinter.def.json
msgctxt "retraction_combing_max_distance label"
msgid "Max Comb Distance With No Retract"
@@ -3116,11 +3183,6 @@ msgctxt "support_pattern option concentric"
msgid "Concentric"
msgstr "Concéntrico"
-#: fdmprinter.def.json
-msgctxt "support_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concéntrico 3D"
-
#: fdmprinter.def.json
msgctxt "support_pattern option zigzag"
msgid "Zig Zag"
@@ -3181,6 +3243,56 @@ msgctxt "support_line_distance description"
msgid "Distance between the printed support structure lines. This setting is calculated by the support density."
msgstr "Distancia entre las líneas de estructuras del soporte impresas. Este ajuste se calcula por la densidad del soporte."
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance label"
+msgid "Initial Layer Support Line Distance"
+msgstr "Distancia de línea del soporte de la capa inicial"
+
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance description"
+msgid "Distance between the printed initial layer support structure lines. This setting is calculated by the support density."
+msgstr "Distancia entre las líneas de estructuras del soporte de la capa inicial impresas. Este ajuste se calcula por la densidad del soporte."
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle label"
+msgid "Support Infill Line Direction"
+msgstr "Dirección de línea de relleno de soporte"
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle description"
+msgid "Orientation of the infill pattern for supports. The support infill pattern is rotated in the horizontal plane."
+msgstr "Orientación del patrón de relleno para soportes. El patrón de relleno de soporte se gira en el plano horizontal."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable label"
+msgid "Enable Support Brim"
+msgstr "Habilitar borde de soporte"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable description"
+msgid "Generate a brim within the support infill regions of the first layer. This brim is printed underneath the support, not around it. Enabling this setting increases the adhesion of support to the build plate."
+msgstr "Genera un borde dentro de las zonas de relleno del soporte de la primera capa. Este borde se imprime por debajo del soporte y no a su alrededor. Si habilita esta configuración aumentará la adhesión del soporte a la placa de impresión."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width label"
+msgid "Support Brim Width"
+msgstr "Ancho del borde de soporte"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width description"
+msgid "The width of the brim to print underneath the support. A larger brim enhances adhesion to the build plate, at the cost of some extra material."
+msgstr "Anchura del borde de impresión que se imprime por debajo del soporte. Una anchura de soporte amplia mejora la adhesión a la placa de impresión, pero requieren material adicional."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count label"
+msgid "Support Brim Line Count"
+msgstr "Recuento de líneas del borde de soporte"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count description"
+msgid "The number of lines used for the support brim. More brim lines enhance adhesion to the build plate, at the cost of some extra material."
+msgstr "Número de líneas utilizadas para el borde de soporte. Más líneas de borde mejoran la adhesión a la placa de impresión, pero requieren material adicional."
+
#: fdmprinter.def.json
msgctxt "support_z_distance label"
msgid "Support Z Distance"
@@ -3471,11 +3583,6 @@ msgctxt "support_interface_pattern option concentric"
msgid "Concentric"
msgstr "Concéntrico"
-#: fdmprinter.def.json
-msgctxt "support_interface_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concéntrico 3D"
-
#: fdmprinter.def.json
msgctxt "support_interface_pattern option zigzag"
msgid "Zig Zag"
@@ -3511,11 +3618,6 @@ msgctxt "support_roof_pattern option concentric"
msgid "Concentric"
msgstr "Concéntrico"
-#: fdmprinter.def.json
-msgctxt "support_roof_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concéntrico 3D"
-
#: fdmprinter.def.json
msgctxt "support_roof_pattern option zigzag"
msgid "Zig Zag"
@@ -3551,16 +3653,31 @@ msgctxt "support_bottom_pattern option concentric"
msgid "Concentric"
msgstr "Concéntrico"
-#: fdmprinter.def.json
-msgctxt "support_bottom_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concéntrico 3D"
-
#: fdmprinter.def.json
msgctxt "support_bottom_pattern option zigzag"
msgid "Zig Zag"
msgstr "Zigzag"
+#: fdmprinter.def.json
+msgctxt "support_fan_enable label"
+msgid "Fan Speed Override"
+msgstr "Alteración de velocidad del ventilador"
+
+#: fdmprinter.def.json
+msgctxt "support_fan_enable description"
+msgid "When enabled, the print cooling fan speed is altered for the skin regions immediately above the support."
+msgstr "Al habilitar esta opción, la velocidad del ventilador de enfriamiento de impresión cambia para las áreas de forro que se encuentran inmediatamente encima del soporte."
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed label"
+msgid "Supported Skin Fan Speed"
+msgstr "Velocidad del ventilador para forro con soporte"
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed description"
+msgid "Percentage fan speed to use when printing the skin regions immediately above the support. Using a high fan speed can make the support easier to remove."
+msgstr "Porcentaje para la velocidad de ventilador que se utiliza al imprimir las áreas del forro que se encuentran inmediatamente encima del soporte. Si utiliza una velocidad alta para el ventilador, será más fácil retirar el soporte."
+
#: fdmprinter.def.json
msgctxt "support_use_towers label"
msgid "Use Towers"
@@ -3743,6 +3860,16 @@ msgctxt "brim_line_count description"
msgid "The number of lines used for a brim. More brim lines enhance adhesion to the build plate, but also reduces the effective print area."
msgstr "Número de líneas utilizadas para un borde. Más líneas de borde mejoran la adhesión a la plataforma de impresión, pero también reducen el área de impresión efectiva."
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support label"
+msgid "Brim Replaces Support"
+msgstr "Sustituir soporte por borde"
+
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support description"
+msgid "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions of the first layer of support by brim regions."
+msgstr "Aplica la impresión de un borde alrededor del modelo, aunque en esa posición debiera estar el soporte. Sustituye algunas áreas de la primera capa de soporte por áreas de borde."
+
#: fdmprinter.def.json
msgctxt "brim_outside_only label"
msgid "Brim Only on Outside"
@@ -3885,8 +4012,8 @@ msgstr "Ancho de las líneas de la capa base de la balsa. Estas deben ser línea
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing label"
-msgid "Raft Line Spacing"
-msgstr "Espaciado de líneas de la balsa"
+msgid "Raft Base Line Spacing"
+msgstr "Espacio de la línea base de la balsa"
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing description"
@@ -4091,7 +4218,7 @@ msgstr "Tamaño de la torre auxiliar"
#: fdmprinter.def.json
msgctxt "prime_tower_size description"
msgid "The width of the prime tower."
-msgstr "Anchura de la torre auxiliar"
+msgstr "Anchura de la torre auxiliar."
#: fdmprinter.def.json
msgctxt "prime_tower_min_volume label"
@@ -4103,16 +4230,6 @@ msgctxt "prime_tower_min_volume description"
msgid "The minimum volume for each layer of the prime tower in order to purge enough material."
msgstr "El volumen mínimo de cada capa de la torre auxiliar que permite purgar suficiente material."
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness label"
-msgid "Prime Tower Thickness"
-msgstr "Grosor de la torre auxiliar"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness description"
-msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
-msgstr "El grosor de la torre auxiliar hueca. Un grosor mayor de la mitad del volumen mínimo de la torre auxiliar dará lugar a una torre auxiliar densa."
-
#: fdmprinter.def.json
msgctxt "prime_tower_position_x label"
msgid "Prime Tower X Position"
@@ -4153,26 +4270,6 @@ msgctxt "prime_tower_wipe_enabled description"
msgid "After printing the prime tower with one nozzle, wipe the oozed material from the other nozzle off on the prime tower."
msgstr "Tras imprimir la torre auxiliar con una tobera, limpie el material rezumado de la otra tobera de la torre auxiliar."
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe label"
-msgid "Wipe Nozzle After Switch"
-msgstr "Limpiar tobera después de cambiar"
-
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe description"
-msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
-msgstr "Tras cambiar de extrusor, limpie el material que rezuma de la tobera en el primer objeto que imprima. Esto lleva a cabo un movimiento de limpieza lento y suave en un lugar en el que el material que rezuma produzca el menor daño posible a la calidad superficial de la impresión."
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume label"
-msgid "Prime Tower Purge Volume"
-msgstr "Volumen de purga de la torre auxiliar"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume description"
-msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
-msgstr "Cantidad de filamentos que purgar al limpiar la torre auxiliar. La purga sirve para compensar la pérdida de filamentos que se produce durante el rezumado cuando la tobera está inactiva."
-
#: fdmprinter.def.json
msgctxt "ooze_shield_enabled label"
msgid "Enable Ooze Shield"
@@ -4476,7 +4573,7 @@ msgstr "Experimental"
#: fdmprinter.def.json
msgctxt "experimental description"
msgid "experimental!"
-msgstr "Experimental"
+msgstr "¡Experimental!"
#: fdmprinter.def.json
msgctxt "support_tree_enable label"
@@ -4658,6 +4755,16 @@ msgctxt "material_flow_temp_graph description"
msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
msgstr "Datos que vinculan el flujo de materiales (en 3 mm por segundo) a la temperatura (grados centígrados)."
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference label"
+msgid "Minimum Polygon Circumference"
+msgstr "Circunferencia mínima de polígono"
+
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference description"
+msgid "Polygons in sliced layers that have a circumference smaller than this amount will be filtered out. Lower values lead to higher resolution mesh at the cost of slicing time. It is meant mostly for high resolution SLA printers and very tiny 3D models with a lot of details."
+msgstr "Se filtran los polígonos en capas segmentadas que tienen una circunferencia más pequeña que esta. Los valores más pequeños suponen una resolución de malla mayor a costa de un tiempo de segmentación. Está indicado, sobre todo, para impresoras SLA y modelos 3D muy pequeños con muchos detalles."
+
#: fdmprinter.def.json
msgctxt "meshfix_maximum_resolution label"
msgid "Maximum Resolution"
@@ -5315,6 +5422,26 @@ msgctxt "adaptive_layer_height_threshold description"
msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
msgstr "Umbral para usar o no una capa más pequeña. Este número se compara con el curtido de la pendiente más empinada de una capa."
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle label"
+msgid "Overhanging Wall Angle"
+msgstr "Ángulo de voladizo de pared"
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle description"
+msgid "Walls that overhang more than this angle will be printed using overhanging wall settings. When the value is 90, no walls will be treated as overhanging."
+msgstr "Las paredes con un ángulo de voladizo mayor que este se imprimirán con los ajustes de voladizo de pared. Cuando el valor sea 90, no se aplicará la condición de voladizo a la pared."
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor label"
+msgid "Overhanging Wall Speed"
+msgstr "Velocidad de voladizo de pared"
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor description"
+msgid "Overhanging walls will be printed at this percentage of their normal print speed."
+msgstr "Los voladizos de pared se imprimirán a este porcentaje de su velocidad de impresión normal."
+
#: fdmprinter.def.json
msgctxt "bridge_settings_enabled label"
msgid "Enable Bridge Settings"
@@ -5345,16 +5472,6 @@ msgctxt "bridge_skin_support_threshold description"
msgid "If a skin region is supported for less than this percentage of its area, print it using the bridge settings. Otherwise it is printed using the normal skin settings."
msgstr "Si un área de forro es compatible con un porcentaje inferior de su área, se imprime utilizando los ajustes de puente. De lo contrario, se imprimirá utilizando los ajustes de forro habituales."
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang label"
-msgid "Bridge Wall Max Overhang"
-msgstr "Voladizo máximo de pared del puente"
-
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang description"
-msgid "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings."
-msgstr "Ancho máximo permitido de la cámara de aire por debajo de una línea de pared antes imprimir la pared utilizando los ajustes de puente. Se expresa como porcentaje del ancho de la línea de la pared. Si la cámara de aire es mayor, la línea de la pared de imprime utilizando los ajustes de puente. De lo contrario, se imprimirá utilizando los ajustes habituales. Cuando menor sea el valor, más probable es que las líneas de pared del voladizo se impriman utilizando ajustes de puente."
-
#: fdmprinter.def.json
msgctxt "bridge_wall_coast label"
msgid "Bridge Wall Coasting"
@@ -5575,6 +5692,74 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Matriz de transformación que se aplicará al modelo cuando se cargue desde el archivo."
+#~ msgctxt "connect_skin_polygons description"
+#~ msgid "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happend midway over infill this feature can reduce the top surface quality."
+#~ msgstr "Conectar las trayectorias de forro superior/inferior cuando están próximas entre sí. Al habilitar este ajuste, en el patrón concéntrico se reduce considerablemente el tiempo de desplazamiento, pero las conexiones pueden producirse en mitad del relleno, con lo que la bajaría la calidad de la superficie superior."
+
+#~ msgctxt "infill_pattern description"
+#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
+#~ msgstr "Patrón del material de relleno de la impresión. El relleno de línea y zigzag cambia de dirección en capas alternas, con lo que se reduce el coste del material. Los patrones de rejilla, triángulo, trihexagonal, cúbico, de octeto, cúbico bitruncado y transversal y concéntrico se imprimen en todas las capas por completo. El relleno cúbico, cúbico bitruncado y de octeto cambian en cada capa para proporcionar una distribución de fuerza equitativa en cada dirección."
+
+#~ msgctxt "infill_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concéntrico 3D"
+
+#~ msgctxt "retraction_combing description"
+#~ msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
+#~ msgstr "La opción de peinada mantiene la tobera dentro de las áreas ya impresas al desplazarse. Esto ocasiona movimientos de desplazamiento ligeramente más largos, pero reduce la necesidad de realizar retracciones. Si se desactiva la opción de peinada, el material se retraerá y la tobera se moverá en línea recta hasta el siguiente punto. Otra posibilidad es evitar la peinada en áreas de forro superiores/inferiores peinando solo dentro del relleno."
+
+#~ msgctxt "support_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concéntrico 3D"
+
+#~ msgctxt "support_interface_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concéntrico 3D"
+
+#~ msgctxt "support_roof_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concéntrico 3D"
+
+#~ msgctxt "support_bottom_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concéntrico 3D"
+
+#~ msgctxt "raft_base_line_spacing label"
+#~ msgid "Raft Line Spacing"
+#~ msgstr "Espaciado de líneas de la balsa"
+
+#~ msgctxt "prime_tower_wall_thickness label"
+#~ msgid "Prime Tower Thickness"
+#~ msgstr "Grosor de la torre auxiliar"
+
+#~ msgctxt "prime_tower_wall_thickness description"
+#~ msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
+#~ msgstr "El grosor de la torre auxiliar hueca. Un grosor mayor de la mitad del volumen mínimo de la torre auxiliar dará lugar a una torre auxiliar densa."
+
+#~ msgctxt "dual_pre_wipe label"
+#~ msgid "Wipe Nozzle After Switch"
+#~ msgstr "Limpiar tobera después de cambiar"
+
+#~ msgctxt "dual_pre_wipe description"
+#~ msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
+#~ msgstr "Tras cambiar de extrusor, limpie el material que rezuma de la tobera en el primer objeto que imprima. Esto lleva a cabo un movimiento de limpieza lento y suave en un lugar en el que el material que rezuma produzca el menor daño posible a la calidad superficial de la impresión."
+
+#~ msgctxt "prime_tower_purge_volume label"
+#~ msgid "Prime Tower Purge Volume"
+#~ msgstr "Volumen de purga de la torre auxiliar"
+
+#~ msgctxt "prime_tower_purge_volume description"
+#~ msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
+#~ msgstr "Cantidad de filamentos que purgar al limpiar la torre auxiliar. La purga sirve para compensar la pérdida de filamentos que se produce durante el rezumado cuando la tobera está inactiva."
+
+#~ msgctxt "bridge_wall_max_overhang label"
+#~ msgid "Bridge Wall Max Overhang"
+#~ msgstr "Voladizo máximo de pared del puente"
+
+#~ msgctxt "bridge_wall_max_overhang description"
+#~ msgid "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings."
+#~ msgstr "Ancho máximo permitido de la cámara de aire por debajo de una línea de pared antes imprimir la pared utilizando los ajustes de puente. Se expresa como porcentaje del ancho de la línea de la pared. Si la cámara de aire es mayor, la línea de la pared de imprime utilizando los ajustes de puente. De lo contrario, se imprimirá utilizando los ajustes habituales. Cuando menor sea el valor, más probable es que las líneas de pared del voladizo se impriman utilizando ajustes de puente."
+
#~ msgctxt "optimize_wall_printing_order description"
#~ msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization."
#~ msgstr "Optimizar el orden en el que se imprimen las paredes a fin de reducir el número de retracciones y la distancia recorrida. La mayoría de los componentes se beneficiarán si este ajuste está habilitado pero, en algunos casos, se puede tardar más, por lo que deben compararse las previsiones de tiempo de impresión con y sin optimización."
diff --git a/resources/i18n/fdmextruder.def.json.pot b/resources/i18n/fdmextruder.def.json.pot
index db93c13b3c..fbb003f3c6 100644
--- a/resources/i18n/fdmextruder.def.json.pot
+++ b/resources/i18n/fdmextruder.def.json.pot
@@ -1,17 +1,12 @@
-# Cura JSON setting files
-# Copyright (C) 2018 Ultimaker
-# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2018.
-#
+#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0000\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
-"Language-Team: TEAM\n"
-"Language: xx_XX\n"
+"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -174,6 +169,19 @@ msgid ""
"printing."
msgstr ""
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number label"
+msgid "Extruder Print Cooling Fan"
+msgstr ""
+
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number description"
+msgid ""
+"The number of the print cooling fan associated with this extruder. Only "
+"change this from the default value of 0 when you have a different print "
+"cooling fan for each extruder."
+msgstr ""
+
#: fdmextruder.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
diff --git a/resources/i18n/fdmprinter.def.json.pot b/resources/i18n/fdmprinter.def.json.pot
index c505755444..96071d82b8 100644
--- a/resources/i18n/fdmprinter.def.json.pot
+++ b/resources/i18n/fdmprinter.def.json.pot
@@ -1,17 +1,12 @@
-# Cura JSON setting files
-# Copyright (C) 2018 Ultimaker
-# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2018.
-#
+#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-03-29 08:36+0200\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
-"Language-Team: TEAM\n"
-"Language: xx_XX\n"
+"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -82,6 +77,18 @@ msgctxt "material_guid description"
msgid "GUID of the material. This is set automatically. "
msgstr ""
+#: fdmprinter.def.json
+msgctxt "material_diameter label"
+msgid "Diameter"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "material_diameter description"
+msgid ""
+"Adjusts the diameter of the filament used. Match this value with the "
+"diameter of the used filament."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "material_bed_temp_wait label"
msgid "Wait for Build Plate Heatup"
@@ -1154,6 +1161,20 @@ msgctxt "top_bottom_pattern_0 option zigzag"
msgid "Zig Zag"
msgstr ""
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons label"
+msgid "Connect Top/Bottom Polygons"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons description"
+msgid ""
+"Connect top/bottom skin paths where they run next to each other. For the "
+"concentric pattern enabling this setting greatly reduces the travel time, "
+"but because the connections can happen midway over infill this feature can "
+"reduce the top surface quality."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "skin_angles label"
msgid "Top/Bottom Line Directions"
@@ -1261,6 +1282,33 @@ msgid ""
"already a wall in place."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "wall_min_flow label"
+msgid "Minimum Wall Flow"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow description"
+msgid ""
+"Minimum allowed percentage flow for a wall line. The wall overlap "
+"compensation reduces a wall's flow when it lies close to an existing wall. "
+"Walls whose flow is less than this value will be replaced with a travel "
+"move. When using this setting, you must enable the wall overlap compensation "
+"and print the outer wall before inner walls."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract label"
+msgid "Prefer Retract"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract description"
+msgid ""
+"If enabled, retraction is used rather than combing for travel moves that "
+"replace walls whose flow is below the minimum flow threshold."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "fill_perimeter_gaps label"
msgid "Fill Gaps Between Walls"
@@ -1627,8 +1675,8 @@ msgid ""
"The pattern of the infill material of the print. The line and zig zag infill "
"swap direction on alternate layers, reducing material cost. The grid, "
"triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric "
-"patterns are fully printed every layer. Cubic, quarter cubic and octet "
-"infill change with every layer to provide a more equal distribution of "
+"patterns are fully printed every layer. Gyroid, cubic, quarter cubic and "
+"octet infill change with every layer to provide a more equal distribution of "
"strength over each direction."
msgstr ""
@@ -1677,11 +1725,6 @@ msgctxt "infill_pattern option concentric"
msgid "Concentric"
msgstr ""
-#: fdmprinter.def.json
-msgctxt "infill_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "infill_pattern option zigzag"
msgid "Zig Zag"
@@ -1697,6 +1740,11 @@ msgctxt "infill_pattern option cross_3d"
msgid "Cross 3D"
msgstr ""
+#: fdmprinter.def.json
+msgctxt "infill_pattern option gyroid"
+msgid "Gyroid"
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "zig_zaggify_infill label"
msgid "Connect Infill Lines"
@@ -1712,6 +1760,19 @@ msgid ""
"of material used."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons label"
+msgid "Connect Infill Polygons"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons description"
+msgid ""
+"Connect infill paths where they run next to each other. For infill patterns "
+"which consist of several closed polygons, enabling this setting greatly "
+"reduces the travel time."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "infill_angles label"
msgid "Infill Line Directions"
@@ -1748,6 +1809,35 @@ msgctxt "infill_offset_y description"
msgid "The infill pattern is moved this distance along the Y axis."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "infill_multiplier label"
+msgid "Infill Line Multiplier"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "infill_multiplier description"
+msgid ""
+"Convert each infill line to this many lines. The extra lines do not cross "
+"over each other, but avoid each other. This makes the infill stiffer, but "
+"increases print time and material usage."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count label"
+msgid "Extra Infill Wall Count"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count description"
+msgid ""
+"Add extra walls around the infill area. Such walls can make top/bottom skin "
+"lines sag down less which means you need less top/bottom skin layers for the "
+"same quality at the cost of some extra material.\n"
+"This feature can combine with the Connect Infill Polygons to connect all the "
+"infill into a single extrusion path without the need for travels or "
+"retractions if configured right."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
msgid "Cubic Subdivision Shell"
@@ -2139,18 +2229,6 @@ msgctxt "material_bed_temperature_layer_0 description"
msgid "The temperature used for the heated build plate at the first layer."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "material_diameter label"
-msgid "Diameter"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "material_diameter description"
-msgid ""
-"Adjusts the diameter of the filament used. Match this value with the "
-"diameter of the used filament."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "material_adhesion_tendency label"
msgid "Adhesion Tendency"
@@ -3106,7 +3184,9 @@ msgid ""
"results in slightly longer travel moves but reduces the need for "
"retractions. If combing is off, the material will retract and the nozzle "
"moves in a straight line to the next point. It is also possible to avoid "
-"combing over top/bottom skin areas by combing within the infill only."
+"combing over top/bottom skin areas and also to only comb within the infill. "
+"Note that the 'Within Infill' option behaves exactly like the 'Not in Skin' "
+"option in earlier Cura releases."
msgstr ""
#: fdmprinter.def.json
@@ -3124,6 +3204,11 @@ msgctxt "retraction_combing option noskin"
msgid "Not in Skin"
msgstr ""
+#: fdmprinter.def.json
+msgctxt "retraction_combing option infill"
+msgid "Within Infill"
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "retraction_combing_max_distance label"
msgid "Max Comb Distance With No Retract"
@@ -3581,11 +3666,6 @@ msgctxt "support_pattern option concentric"
msgid "Concentric"
msgstr ""
-#: fdmprinter.def.json
-msgctxt "support_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "support_pattern option zigzag"
msgid "Zig Zag"
@@ -3658,6 +3738,67 @@ msgid ""
"calculated by the support density."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance label"
+msgid "Initial Layer Support Line Distance"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance description"
+msgid ""
+"Distance between the printed initial layer support structure lines. This "
+"setting is calculated by the support density."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle label"
+msgid "Support Infill Line Direction"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle description"
+msgid ""
+"Orientation of the infill pattern for supports. The support infill pattern "
+"is rotated in the horizontal plane."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable label"
+msgid "Enable Support Brim"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable description"
+msgid ""
+"Generate a brim within the support infill regions of the first layer. This "
+"brim is printed underneath the support, not around it. Enabling this setting "
+"increases the adhesion of support to the build plate."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width label"
+msgid "Support Brim Width"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width description"
+msgid ""
+"The width of the brim to print underneath the support. A larger brim "
+"enhances adhesion to the build plate, at the cost of some extra material."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count label"
+msgid "Support Brim Line Count"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count description"
+msgid ""
+"The number of lines used for the support brim. More brim lines enhance "
+"adhesion to the build plate, at the cost of some extra material."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "support_z_distance label"
msgid "Support Z Distance"
@@ -4005,11 +4146,6 @@ msgctxt "support_interface_pattern option concentric"
msgid "Concentric"
msgstr ""
-#: fdmprinter.def.json
-msgctxt "support_interface_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "support_interface_pattern option zigzag"
msgid "Zig Zag"
@@ -4045,11 +4181,6 @@ msgctxt "support_roof_pattern option concentric"
msgid "Concentric"
msgstr ""
-#: fdmprinter.def.json
-msgctxt "support_roof_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "support_roof_pattern option zigzag"
msgid "Zig Zag"
@@ -4086,13 +4217,32 @@ msgid "Concentric"
msgstr ""
#: fdmprinter.def.json
-msgctxt "support_bottom_pattern option concentric_3d"
-msgid "Concentric 3D"
+msgctxt "support_bottom_pattern option zigzag"
+msgid "Zig Zag"
msgstr ""
#: fdmprinter.def.json
-msgctxt "support_bottom_pattern option zigzag"
-msgid "Zig Zag"
+msgctxt "support_fan_enable label"
+msgid "Fan Speed Override"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_fan_enable description"
+msgid ""
+"When enabled, the print cooling fan speed is altered for the skin regions "
+"immediately above the support."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed label"
+msgid "Supported Skin Fan Speed"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed description"
+msgid ""
+"Percentage fan speed to use when printing the skin regions immediately above "
+"the support. Using a high fan speed can make the support easier to remove."
msgstr ""
#: fdmprinter.def.json
@@ -4313,6 +4463,19 @@ msgid ""
"build plate, but also reduces the effective print area."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support label"
+msgid "Brim Replaces Support"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support description"
+msgid ""
+"Enforce brim to be printed around the model even if that space would "
+"otherwise be occupied by support. This replaces some regions of the first "
+"layer of support by brim regions."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "brim_outside_only label"
msgid "Brim Only on Outside"
@@ -4487,7 +4650,7 @@ msgstr ""
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing label"
-msgid "Raft Line Spacing"
+msgid "Raft Base Line Spacing"
msgstr ""
#: fdmprinter.def.json
@@ -4720,18 +4883,6 @@ msgid ""
"enough material."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness label"
-msgid "Prime Tower Thickness"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness description"
-msgid ""
-"The thickness of the hollow prime tower. A thickness larger than half the "
-"Prime Tower Minimum Volume will result in a dense prime tower."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "prime_tower_position_x label"
msgid "Prime Tower X Position"
@@ -4776,32 +4927,6 @@ msgid ""
"the other nozzle off on the prime tower."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe label"
-msgid "Wipe Nozzle After Switch"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe description"
-msgid ""
-"After switching extruder, wipe the oozed material off of the nozzle on the "
-"first thing printed. This performs a safe slow wipe move at a place where "
-"the oozed material causes least harm to the surface quality of your print."
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume label"
-msgid "Prime Tower Purge Volume"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume description"
-msgid ""
-"Amount of filament to be purged when wiping on the prime tower. Purging is "
-"useful for compensating the filament lost by oozing during inactivity of the "
-"nozzle."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "ooze_shield_enabled label"
msgid "Enable Ooze Shield"
@@ -5402,6 +5527,20 @@ msgid ""
"Celsius)."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference label"
+msgid "Minimum Polygon Circumference"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference description"
+msgid ""
+"Polygons in sliced layers that have a circumference smaller than this amount "
+"will be filtered out. Lower values lead to higher resolution mesh at the "
+"cost of slicing time. It is meant mostly for high resolution SLA printers "
+"and very tiny 3D models with a lot of details."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "meshfix_maximum_resolution label"
msgid "Maximum Resolution"
@@ -6203,6 +6342,30 @@ msgid ""
"the tan of the steepest slope in a layer."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle label"
+msgid "Overhanging Wall Angle"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle description"
+msgid ""
+"Walls that overhang more than this angle will be printed using overhanging "
+"wall settings. When the value is 90, no walls will be treated as overhanging."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor label"
+msgid "Overhanging Wall Speed"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor description"
+msgid ""
+"Overhanging walls will be printed at this percentage of their normal print "
+"speed."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "bridge_settings_enabled label"
msgid "Enable Bridge Settings"
@@ -6241,22 +6404,6 @@ msgid ""
"skin settings."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang label"
-msgid "Bridge Wall Max Overhang"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang description"
-msgid ""
-"The maximum allowed width of the region of air below a wall line before the "
-"wall is printed using bridge settings. Expressed as a percentage of the wall "
-"line width. When the air gap is wider than this, the wall line is printed "
-"using the bridge settings. Otherwise, the wall line is printed using the "
-"normal settings. The lower the value, the more likely it is that overhung "
-"wall lines will be printed using bridge settings."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "bridge_wall_coast label"
msgid "Bridge Wall Coasting"
diff --git a/resources/i18n/fi_FI/cura.po b/resources/i18n/fi_FI/cura.po
index a4b5719da7..442500f21b 100644
--- a/resources/i18n/fi_FI/cura.po
+++ b/resources/i18n/fi_FI/cura.po
@@ -5,9 +5,9 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0200\n"
+"POT-Creation-Date: 2018-10-29 15:01+0100\n"
"PO-Revision-Date: 2017-09-27 12:27+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Finnish\n"
@@ -38,6 +38,17 @@ msgctxt "@item:inlistbox"
msgid "G-code File"
msgstr "GCode-tiedosto"
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:67
+msgctxt "@error:not supported"
+msgid "GCodeWriter does not support non-text mode."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:73
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:89
+msgctxt "@warning:status"
+msgid "Please prepare G-code before exporting."
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
msgctxt "@info:title"
msgid "3D Model Assistant"
@@ -53,102 +64,51 @@ msgid ""
"
"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
-msgctxt "@action:button"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Tulostus Doodle3D WiFi-Boxin avulla"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:66
-msgctxt "@properties:tooltip"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Tulostus Doodle3D WiFi-Boxin avulla"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:86
-msgctxt "@info:status"
-msgid "Connecting to Doodle3D Connect"
-msgstr "Yhteyden muodostaminen Doodle3D Connectiin"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:258
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:204
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:398
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:88
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
-#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
-#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:275
-msgctxt "@action:button"
-msgid "Cancel"
-msgstr "Peruuta"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:154
-msgctxt "@info:status"
-msgid "Sending data to Doodle3D Connect"
-msgstr "Lähetetään tietoja Doodle3D Connectiin"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:161
-msgctxt "@info:status"
-msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
-msgstr "Tietojen lähetys Doodle3D Connectiin ei onnistu. Onko toinen työ yhä aktiivinen?"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:175
-msgctxt "@info:status"
-msgid "Storing data on Doodle3D Connect"
-msgstr "Tietoja tallennetaan Doodle3D Connectiin"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:213
-msgctxt "@info:status"
-msgid "File sent to Doodle3D Connect"
-msgstr "Tiedosto lähetetty Doodle3D Connectiin"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@action:button"
-msgid "Open Connect..."
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@info:tooltip"
-msgid "Open the Doodle3D Connect web interface"
-msgstr "Avaa Doodle3D Connect -verkkoliittymä"
-
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:32
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Näytä muutosloki"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:20
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py:25
+msgctxt "@action"
+msgid "Update Firmware"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:23
msgctxt "@item:inmenu"
msgid "Flatten active settings"
msgstr "Aktivoitujen asetusten tasoitus"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:32
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:35
msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr "Profiili on tasoitettu ja aktivoitu."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:40
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:32
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "USB-tulostus"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:41
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:33
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "Tulosta USB:n kautta"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:42
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:34
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "Tulosta USB:n kautta"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:83
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:69
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Yhdistetty USB:n kautta"
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:92
+msgctxt "@label"
+msgid "A USB print is in progress, closing Cura will stop this print. Are you sure?"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/install/X3GWriter/__init__.py:15
#: /home/ruben/Projects/Cura/plugins/X3GWriter/__init__.py:15
msgctxt "X3G Writer File Description"
@@ -171,7 +131,12 @@ msgctxt "@item:inlistbox"
msgid "Compressed G-code File"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/GCodeGzWriter.py:38
+msgctxt "@error:not supported"
+msgid "GCodeGzWriter does not support text mode."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:28
msgctxt "@item:inlistbox"
msgid "Ultimaker Format Package"
msgstr ""
@@ -193,7 +158,7 @@ msgid "Save to Removable Drive {0}"
msgstr "Tallenna siirrettävälle asemalle {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:64
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:133
msgctxt "@info:status"
msgid "There are no file formats available to write with!"
msgstr ""
@@ -232,7 +197,7 @@ msgstr "Ei voitu tallentaa siirrettävälle asemalle {0}: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:137
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:133
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:140
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1592
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1607
msgctxt "@info:title"
msgid "Error"
msgstr "Virhe"
@@ -261,8 +226,8 @@ msgstr "Poista siirrettävä asema {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:151
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:163
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1582
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1681
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1597
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1695
msgctxt "@info:title"
msgid "Warning"
msgstr "Varoitus"
@@ -289,259 +254,269 @@ msgctxt "@item:intext"
msgid "Removable Drive"
msgstr "Siirrettävä asema"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:70
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:78
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:88
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "Tulosta verkon kautta"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:71
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:79
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:74
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:89
msgctxt "@properties:tooltip"
msgid "Print over network"
msgstr "Tulosta verkon kautta"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:84
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:87
msgctxt "@info:status"
msgid "Connected over the network."
msgstr "Yhdistetty verkon kautta tulostimeen."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:87
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:90
msgctxt "@info:status"
msgid "Connected over the network. Please approve the access request on the printer."
msgstr "Yhdistetty verkon kautta. Hyväksy tulostimen käyttöoikeuspyyntö."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:89
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:92
msgctxt "@info:status"
msgid "Connected over the network. No access to control the printer."
msgstr "Yhdistetty verkon kautta tulostimeen. Ei käyttöoikeutta tulostimen hallintaan."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:94
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:97
msgctxt "@info:status"
msgid "Access to the printer requested. Please approve the request on the printer"
msgstr "Tulostimen käyttöoikeutta pyydetty. Hyväksy tulostimen pyyntö"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:97
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:100
msgctxt "@info:title"
msgid "Authentication status"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:99
-msgctxt "@info:status"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:100
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:106
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:110
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:108
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:112
msgctxt "@info:title"
msgid "Authentication Status"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:101
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:103
msgctxt "@action:button"
msgid "Retry"
msgstr "Yritä uudelleen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:104
msgctxt "@info:tooltip"
msgid "Re-send the access request"
msgstr "Lähetä käyttöoikeuspyyntö uudelleen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:105
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:107
msgctxt "@info:status"
msgid "Access to the printer accepted"
msgstr "Tulostimen käyttöoikeus hyväksytty"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:109
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:111
msgctxt "@info:status"
msgid "No access to print with this printer. Unable to send print job."
msgstr "Tällä tulostimella tulostukseen ei ole käyttöoikeutta. Tulostustyön lähetys ei onnistu."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:111
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:29
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:33
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:70
msgctxt "@action:button"
msgid "Request Access"
msgstr "Pyydä käyttöoikeutta"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:113
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:28
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:72
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:115
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:34
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:71
msgctxt "@info:tooltip"
msgid "Send access request to the printer"
msgstr "Lähetä tulostimen käyttöoikeuspyyntö"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:198
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:200
msgctxt "@label"
msgid "Unable to start a new print job."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:200
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:202
msgctxt "@label"
msgid "There is an issue with the configuration of your Ultimaker, which makes it impossible to start the print. Please resolve this issues before continuing."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:206
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:228
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:208
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:230
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "Ristiriitainen määritys"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:220
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:222
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "Haluatko varmasti tulostaa valitulla määrityksellä?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:222
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:224
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Tulostimen ja Curan määrityksen tai kalibroinnin välillä on ristiriita. Parhaat tulokset saavutetaan viipaloimalla aina tulostimeen asetetuille PrintCoreille ja materiaaleille."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:249
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:166
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:251
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:199
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "Uusien töiden lähettäminen (tilapäisesti) estetty, edellistä tulostustyötä lähetetään vielä."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:256
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:185
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:202
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:258
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:234
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "Lähetetään tietoja tulostimeen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:257
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:186
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:203
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:259
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:219
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:235
msgctxt "@info:title"
msgid "Sending Data"
msgstr "Lähetetään tietoja"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:321
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:260
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:236
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:381
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:20
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
+#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:279
+msgctxt "@action:button"
+msgid "Cancel"
+msgstr "Peruuta"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:323
#, python-brace-format
msgctxt "@info:status"
msgid "No Printcore loaded in slot {slot_number}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:327
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:329
#, python-brace-format
msgctxt "@info:status"
msgid "No material loaded in slot {slot_number}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:350
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:352
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {cura_printcore_name}, Printer: {remote_printcore_name}) selected for extruder {extruder_id}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:359
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:361
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "Eri materiaali (Cura: {0}, tulostin: {1}) valittu suulakkeelle {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:545
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:547
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "Synkronoi tulostimen kanssa"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:547
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:549
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "Haluatko käyttää nykyistä tulostimen määritystä Curassa?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:549
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:551
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Tulostimen PrintCoret tai materiaalit eivät vastaa tulostettavan projektin asetuksia. Parhaat tulokset saavutetaan viipaloimalla aina tulostimeen asetetuille PrintCoreille ja materiaaleille."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:81
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:91
msgctxt "@info:status"
msgid "Connected over the network"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:303
msgctxt "@info:status"
msgid "Print job was successfully sent to the printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:264
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:305
msgctxt "@info:title"
msgid "Data Sent"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:265
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:306
msgctxt "@action:button"
msgid "View in Monitor"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:353
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:422
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "{printer_name} on tulostanut työn '{job_name}'."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:355
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:424
#, python-brace-format
msgctxt "@info:status"
msgid "The print job '{job_name}' was finished."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:356
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:425
msgctxt "@info:status"
msgid "Print finished"
msgstr "Tulosta valmis"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:20
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py:26
msgctxt "@action"
msgid "Connect via Network"
msgstr "Yhdistä verkon kautta"
-#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:13
msgctxt "@item:inmenu"
msgid "Monitor"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:69
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:119
+msgctxt "@info"
+msgid "Could not access update information."
+msgstr "Päivitystietoja ei löytynyt."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:17
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:73
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:21
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "Uusi tulostimen %s laiteohjelmisto saatavilla"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:76
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:27
msgctxt "@action:button"
msgid "How to update"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:92
-msgctxt "@info"
-msgid "Could not access update information."
-msgstr "Päivitystietoja ei löytynyt."
-
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Layer view"
msgstr "Kerrosnäkymä"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:113
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "Cura ei näytä kerroksia täsmällisesti, kun rautalankatulostus on käytössä"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:114
msgctxt "@info:title"
msgid "Simulation View"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:27
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:35
msgid "Modify G-Code"
msgstr "Muokkaa GCode-arvoa"
@@ -555,32 +530,32 @@ msgctxt "@info:tooltip"
msgid "Create a volume in which supports are not printed."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:44
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
msgctxt "@info"
msgid "Cura collects anonymized usage statistics."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:47
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:55
msgctxt "@info:title"
msgid "Collecting Data"
msgstr "Kerätään tietoja"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:57
msgctxt "@action:button"
msgid "More info"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:58
msgctxt "@action:tooltip"
msgid "See more information on what data Cura sends."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:60
msgctxt "@action:button"
msgid "Allow"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:53
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:61
msgctxt "@action:tooltip"
msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
msgstr ""
@@ -590,18 +565,6 @@ msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Cura 15.04 -profiilit"
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
-msgctxt "@item:inlistbox"
-msgid "Blender file"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
-msgctxt "@info:status"
-msgid ""
-"Could not export using \"{}\" quality!\n"
-"Felt back to \"{}\"."
-msgstr ""
-
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "JPG Image"
@@ -627,49 +590,56 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "GIF-kuva"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "Viipalointi ei onnistu nykyisellä materiaalilla, sillä se ei sovellu käytettäväksi valitun laitteen tai kokoonpanon kanssa."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:344
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:367
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:376
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:363
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:387
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:396
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:405
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:414
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "Viipalointi ei onnistu"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:343
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:362
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "Viipalointi ei onnistu nykyisten asetuksien ollessa voimassa. Seuraavissa asetuksissa on virheitä: {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:375
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:395
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "Viipalointi ei onnistu, koska esitäyttötorni tai esitäytön sijainti tai sijainnit eivät kelpaa."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:385
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:404
+#, python-format
+msgctxt "@info:status"
+msgid "Unable to slice because there are objects associated with disabled Extruder %s."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:413
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "Ei viipaloitavaa, koska mikään malleista ei sovellu tulostustilavuuteen. Skaalaa tai pyöritä mallia, kunnes se on sopiva."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "Käsitellään kerroksia"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:title"
msgid "Information"
msgstr "Tiedot"
@@ -685,29 +655,40 @@ msgid "Configure Per Model Settings"
msgstr "Määritä mallikohtaiset asetukset"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:175
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:575
msgctxt "@title:tab"
msgid "Recommended"
msgstr "Suositeltu"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:177
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:580
msgctxt "@title:tab"
msgid "Custom"
msgstr "Mukautettu"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:32
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:28
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:34
msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "3MF-tiedosto"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:199
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:695
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:714
msgctxt "@label"
msgid "Nozzle"
msgstr "Suutin"
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:468
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Project file {0} contains an unknown machine type {1}. Cannot import the machine. Models will be imported instead."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:471
+msgctxt "@info:title"
+msgid "Open Project File"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/SolidView/__init__.py:12
msgctxt "@item:inmenu"
msgid "Solid view"
@@ -718,18 +699,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "G File -tiedosto"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:322
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "G-coden jäsennys"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:470
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:326
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:474
msgctxt "@info:title"
msgid "G-code Details"
msgstr "G-coden tiedot"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:468
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:472
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "Varmista, että G-code on tulostimelle ja sen tulostusasetuksille soveltuva, ennen kuin lähetät tiedoston siihen. G-coden esitys ei välttämättä ole tarkka."
@@ -740,27 +721,27 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Cura-profiili"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:26
msgctxt "@item:inlistbox"
msgid "3MF file"
msgstr "3MF-tiedosto"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:34
msgctxt "@item:inlistbox"
msgid "Cura Project 3MF file"
msgstr "Cura-projektin 3MF-tiedosto"
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/ThreeMFWriter.py:179
+msgctxt "@error:zip"
+msgid "Error writing 3mf file."
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelection.py:17
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelection.py:18
msgctxt "@action"
msgid "Select upgrades"
msgstr "Valitse päivitykset"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py:12
-msgctxt "@action"
-msgid "Upgrade Firmware"
-msgstr "Päivitä laiteohjelmisto"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py:14
msgctxt "@action"
msgid "Checkup"
@@ -771,79 +752,79 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "Tasaa alusta"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:82
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "Ulkoseinämä"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:83
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "Sisäseinämät"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:84
msgctxt "@tooltip"
msgid "Skin"
msgstr "Pintakalvo"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:85
msgctxt "@tooltip"
msgid "Infill"
msgstr "Täyttö"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:86
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "Tuen täyttö"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:87
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "Tukiliittymä"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:88
msgctxt "@tooltip"
msgid "Support"
msgstr "Tuki"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
msgctxt "@tooltip"
msgid "Skirt"
msgstr "Helma"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
msgctxt "@tooltip"
msgid "Travel"
msgstr "Siirtoliike"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
msgctxt "@tooltip"
msgid "Retractions"
msgstr "Takaisinvedot"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
msgctxt "@tooltip"
msgid "Other"
msgstr "Muu"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:229
-msgctxt "@label unknown material"
-msgid "Unknown"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:313
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:310
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "Esiviipaloitu tiedosto {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:235
+#: /home/ruben/Projects/Cura/cura/API/Account.py:71
+msgctxt "@info:title"
+msgid "Login failed"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:201
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "Tiedosto on jo olemassa"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:236
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:202
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:122
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
@@ -855,23 +836,23 @@ msgctxt "@menuitem"
msgid "Not overridden"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:119
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "Valittu materiaali ei sovellu käytettäväksi valitun laitteen tai kokoonpanon kanssa."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:120
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
msgctxt "@info:title"
msgid "Incompatible Material"
msgstr "Yhteensopimaton materiaali"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:842
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:866
#, python-format
msgctxt "@info:generic"
msgid "Settings have been changed to match the current availability of extruders: [%s]"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:844
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:868
msgctxt "@info:title"
msgid "Settings updated"
msgstr ""
@@ -900,8 +881,6 @@ msgid "Export succeeded"
msgstr ""
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:170
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
@@ -909,58 +888,70 @@ msgstr "Profiilin tuonti epäonnistui tiedostosta {0}: or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "No custom profile to import in file {0}"
msgstr ""
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags !"
+msgid "Failed to import profile from {0}:"
+msgstr ""
+
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:218
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:228
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "This profile {0} contains incorrect data, could not import it."
msgstr ""
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:241
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:316
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:312
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Failed to import profile from {0}:"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:315
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "Onnistuneesti tuotu profiili {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:319
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:318
#, python-brace-format
msgctxt "@info:status"
msgid "File {0} does not contain any valid profile."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:322
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "Profiililla {0} on tuntematon tiedostotyyppi tai se on vioittunut."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:340
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:339
msgctxt "@label"
msgid "Custom profile"
msgstr "Mukautettu profiili"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:356
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:355
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "Profiilista puuttuu laatutyyppi."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:368
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:369
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "Laatutyyppiä {0} ei löydy nykyiselle kokoonpanolle."
-#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:60
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:63
#, python-brace-format
msgctxt "@label"
msgid "Group #{group_nr}"
@@ -987,42 +978,42 @@ msgctxt "@item:inlistbox"
msgid "All Files (*)"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:544
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:636
msgctxt "@label"
msgid "Custom Material"
msgstr "Mukautettu materiaali"
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:545
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:637
msgctxt "@label"
msgid "Custom"
msgstr "Mukautettu"
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:80
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:81
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
msgstr "Tulostustilavuuden korkeutta on vähennetty tulostusjärjestysasetuksen vuoksi, jotta koroke ei osuisi tulostettuihin malleihin."
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:82
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:83
msgctxt "@info:title"
msgid "Build Volume"
msgstr "Tulostustilavuus"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:98
msgctxt "@info:backup_failed"
msgid "Could not create archive from user data directory: {}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:103
msgctxt "@info:title"
msgid "Backup"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:113
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup without having proper data or meta data."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:123
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup that does not match your current version."
msgstr ""
@@ -1033,32 +1024,32 @@ msgid "Multiplying and placing objects"
msgstr "Kappaleiden kertominen ja sijoittelu"
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:28
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
msgctxt "@info:title"
msgid "Placing Object"
msgstr "Sijoitetaan kappaletta"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:96
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:149
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
msgctxt "@info:status"
msgid "Unable to find a location within the build volume for all objects"
msgstr "Kaikille kappaleille ei löydy paikkaa tulostustilavuudessa."
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:30
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:66
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:67
msgctxt "@info:status"
msgid "Finding new location for objects"
msgstr "Uusien paikkojen etsiminen kappaleille"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:34
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:70
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:71
msgctxt "@info:title"
msgid "Finding Location"
msgstr "Etsitään paikkaa"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:97
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:151
msgctxt "@info:title"
msgid "Can't Find Location"
msgstr "Paikkaa ei löydy"
@@ -1189,223 +1180,233 @@ msgctxt "@action:button"
msgid "Send report"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:328
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:473
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Ladataan laitteita..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:756
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:775
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "Asetetaan näkymää..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:789
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:811
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Ladataan käyttöliittymää..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1023
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1037
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1581
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1596
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "Vain yksi G-code-tiedosto voidaan ladata kerralla. Tiedoston {0} tuonti ohitettiin."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1591
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1606
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "Muita tiedostoja ei voida ladata, kun G-code latautuu. Tiedoston {0} tuonti ohitettiin."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1680
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1694
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:59
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:61
msgctxt "@title"
msgid "Machine Settings"
msgstr "Laitteen asetukset"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:78
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:80
msgctxt "@title:tab"
msgid "Printer"
msgstr "Tulostin"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:97
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:99
msgctxt "@label"
msgid "Printer Settings"
msgstr "Tulostimen asetukset"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:108
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:110
msgctxt "@label"
msgid "X (Width)"
msgstr "X (leveys)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:109
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:384
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:418
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:430
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:855
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:111
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:121
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:131
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:237
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:386
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:402
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:428
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:440
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:896
msgctxt "@label"
msgid "mm"
msgstr "mm"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:118
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:120
msgctxt "@label"
msgid "Y (Depth)"
msgstr "Y (syvyys)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:128
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:130
msgctxt "@label"
msgid "Z (Height)"
msgstr "Z (korkeus)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:140
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:142
msgctxt "@label"
msgid "Build plate shape"
msgstr "Alustan muoto"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:149
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:151
msgctxt "@option:check"
msgid "Origin at center"
msgstr "Alkukohta keskellä"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:157
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:159
msgctxt "@option:check"
msgid "Heated bed"
msgstr "Lämmitettävä pöytä"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:168
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:170
msgctxt "@label"
msgid "G-code flavor"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:181
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:183
msgctxt "@label"
msgid "Printhead Settings"
msgstr "Tulostuspään asetukset"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:191
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:193
msgctxt "@label"
msgid "X min"
msgstr "X väh."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:192
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:194
msgctxt "@tooltip"
msgid "Distance from the left of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Etäisyys tulostuspään vasemmalta puolelta suuttimen keskikohtaan. Käytetään estämään aiempien tulosteiden ja tulostuspään yhteentörmäyksiä, kun tulostetaan yksi kerrallaan."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:201
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:203
msgctxt "@label"
msgid "Y min"
msgstr "Y väh."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:202
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:204
msgctxt "@tooltip"
msgid "Distance from the front of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Etäisyys tulostuspään etupuolelta suuttimen keskikohtaan. Käytetään estämään aiempien tulosteiden ja tulostuspään yhteentörmäyksiä, kun tulostetaan yksi kerrallaan."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:211
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:213
msgctxt "@label"
msgid "X max"
msgstr "X enint."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:212
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:214
msgctxt "@tooltip"
msgid "Distance from the right of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Etäisyys tulostuspään oikealta puolelta suuttimen keskikohtaan. Käytetään estämään aiempien tulosteiden ja tulostuspään yhteentörmäyksiä, kun tulostetaan yksi kerrallaan."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:221
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:223
msgctxt "@label"
msgid "Y max"
msgstr "Y enint."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:222
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:224
msgctxt "@tooltip"
msgid "Distance from the rear of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Etäisyys tulostuspään takapuolelta suuttimen keskikohtaan. Käytetään estämään aiempien tulosteiden ja tulostuspään yhteentörmäyksiä, kun tulostetaan yksi kerrallaan."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:234
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
msgctxt "@label"
msgid "Gantry height"
msgstr "Korokkeen korkeus"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:238
msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "Suuttimen kärjen ja korokejärjestelmän (X- ja Y-akselit) välinen korkeusero. Käytetään estämään aiempien tulosteiden ja korokkeen yhteentörmäyksiä, kun tulostetaan yksi kerrallaan."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:257
msgctxt "@label"
msgid "Number of Extruders"
msgstr "Suulakkeiden määrä"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:311
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:313
msgctxt "@label"
msgid "Start G-code"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:321
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:323
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very start."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:330
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:332
msgctxt "@label"
msgid "End G-code"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:340
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:342
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very end."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:371
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:373
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Suutinasetukset"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:385
msgctxt "@label"
msgid "Nozzle size"
msgstr "Suuttimen koko"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:399
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
msgctxt "@label"
msgid "Compatible material diameter"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:403
msgctxt "@tooltip"
msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
msgstr "Tulostimen tukema tulostuslangan nimellinen halkaisija. Materiaali ja/tai profiili korvaa tarkan halkaisijan."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:417
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:427
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "Suuttimen X-siirtymä"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:429
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:439
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Suuttimen Y-siirtymä"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:450
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+msgctxt "@label"
+msgid "Cooling Fan Number"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:452
+msgctxt "@label"
+msgid ""
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:472
msgctxt "@label"
msgid "Extruder Start G-code"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:468
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:490
msgctxt "@label"
msgid "Extruder End G-code"
msgstr ""
@@ -1425,12 +1426,20 @@ msgctxt "@info"
msgid "Could not connect to the Cura Package database. Please check your connection."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:38
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:28
msgctxt "@title:tab"
msgid "Plugins"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:75
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:66
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:551
+msgctxt "@title:tab"
+msgid "Materials"
+msgstr "Materiaalit"
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
msgctxt "@label"
msgid "Version"
@@ -1446,8 +1455,14 @@ msgctxt "@label"
msgid "Author"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:97
+msgctxt "@label"
+msgid "Downloads"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:116
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:158
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:258
msgctxt "@label"
msgid "Unknown"
msgstr "Tuntematon"
@@ -1480,17 +1495,57 @@ msgctxt "@action:button"
msgid "Back"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:20
+msgctxt "@title:window"
+msgid "Confirm uninstall"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:50
+msgctxt "@text:window"
+msgid "You are uninstalling materials and/or profiles that are still in use. Confirming will reset the following materials/profiles to their defaults."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:51
+msgctxt "@text:window"
+msgid "Materials"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:52
+msgctxt "@text:window"
+msgid "Profiles"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:89
+msgctxt "@action:button"
+msgid "Confirm"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
msgctxt "@info"
msgid "You will need to restart Cura before changes in packages have effect."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:34
msgctxt "@info:button"
msgid "Quit Cura"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Contributions"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Plugins"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:43
+msgctxt "@label"
+msgid "Generic Materials"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:56
msgctxt "@title:tab"
msgid "Installed"
msgstr ""
@@ -1533,12 +1588,12 @@ msgctxt "@action:button"
msgid "Decline"
msgstr "Hylkää"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:23
msgctxt "@label"
msgid "Featured"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:31
msgctxt "@label"
msgid "Compatibility"
msgstr ""
@@ -1548,9 +1603,14 @@ msgctxt "@info"
msgid "Fetching packages..."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:88
msgctxt "@label"
-msgid "Contact"
+msgid "Website"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:94
+msgctxt "@label"
+msgid "Email"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
@@ -1564,48 +1624,88 @@ msgid "Changelog"
msgstr "Muutosloki"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:84
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:56
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:464
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:509
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:185
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:53
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:467
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:514
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:121
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:166
#: /home/ruben/Projects/Cura/resources/qml/EngineLog.qml:38
msgctxt "@action:button"
msgid "Close"
msgstr "Sulje"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:22
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:31
+msgctxt "@title"
+msgid "Update Firmware"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:39
+msgctxt "@label"
+msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
+msgstr "Laiteohjelmisto on suoraan 3D-tulostimessa toimiva ohjelma. Laiteohjelmisto ohjaa askelmoottoreita, säätää lämpötilaa ja saa tulostimen toimimaan."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:46
+msgctxt "@label"
+msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
+msgstr "Uusien tulostimien mukana toimitettava laiteohjelmisto toimii, mutta uusissa versioissa on yleensä enemmän toimintoja ja parannuksia."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:58
+msgctxt "@action:button"
+msgid "Automatically upgrade Firmware"
+msgstr "Päivitä laiteohjelmisto automaattisesti"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:69
+msgctxt "@action:button"
+msgid "Upload custom Firmware"
+msgstr "Lataa mukautettu laiteohjelmisto"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:83
+msgctxt "@label"
+msgid "Firmware can not be updated because there is no connection with the printer."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:91
+msgctxt "@label"
+msgid "Firmware can not be updated because the connection with the printer does not support upgrading firmware."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:98
+msgctxt "@title:window"
+msgid "Select custom firmware"
+msgstr "Valitse mukautettu laiteohjelmisto"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:119
msgctxt "@title:window"
msgid "Firmware Update"
msgstr "Laiteohjelmiston päivitys"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:42
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:143
msgctxt "@label"
msgid "Updating firmware."
msgstr "Päivitetään laiteohjelmistoa."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:44
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:145
msgctxt "@label"
msgid "Firmware update completed."
msgstr "Laiteohjelmiston päivitys suoritettu."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:46
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:147
msgctxt "@label"
msgid "Firmware update failed due to an unknown error."
msgstr "Laiteohjelmiston päivitys epäonnistui tuntemattoman virheen takia."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:48
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:149
msgctxt "@label"
msgid "Firmware update failed due to an communication error."
msgstr "Laiteohjelmiston päivitys epäonnistui tietoliikennevirheen takia."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:50
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:151
msgctxt "@label"
msgid "Firmware update failed due to an input/output error."
msgstr "Laiteohjelmiston päivitys epäonnistui tiedoston lukemiseen tai kirjoittamiseen liittyvän virheen takia."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:52
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:153
msgctxt "@label"
msgid "Firmware update failed due to missing firmware."
msgstr "Laiteohjelmiston päivitys epäonnistui puuttuvan laiteohjelmiston takia."
@@ -1615,22 +1715,22 @@ msgctxt "@title:window"
msgid "User Agreement"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:57
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:46
msgctxt "@window:title"
msgid "Existing Connection"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:59
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:48
msgctxt "@message:text"
msgid "This printer/group is already added to Cura. Please select another printer/group."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:76
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:65
msgctxt "@title:window"
msgid "Connect to Networked Printer"
msgstr "Yhdistä verkkotulostimeen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:86
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:75
msgctxt "@label"
msgid ""
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
@@ -1641,333 +1741,395 @@ msgstr ""
"\n"
"Valitse tulostin alla olevasta luettelosta:"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:85
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
msgctxt "@action:button"
msgid "Add"
msgstr "Lisää"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:95
msgctxt "@action:button"
msgid "Edit"
msgstr "Muokkaa"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:128
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:48
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:117
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:132
msgctxt "@action:button"
msgid "Remove"
msgstr "Poista"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:125
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:114
msgctxt "@action:button"
msgid "Refresh"
msgstr "Päivitä"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:207
msgctxt "@label"
msgid "If your printer is not listed, read the network printing troubleshooting guide"
msgstr "Jos tulostinta ei ole luettelossa, lue verkkotulostuksen vianetsintäopas"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:245
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:234
msgctxt "@label"
msgid "Type"
msgstr "Tyyppi"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:282
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:271
msgctxt "@label"
msgid "Firmware version"
msgstr "Laiteohjelmistoversio"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:294
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:283
msgctxt "@label"
msgid "Address"
msgstr "Osoite"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:316
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:305
msgctxt "@label"
-msgid "This printer is not set up to host a group of Ultimaker 3 printers."
-msgstr "Tätä tulostinta ei ole määritetty Ultimaker 3 -tulostinryhmän isännäksi."
+msgid "This printer is not set up to host a group of printers."
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:320
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:309
msgctxt "@label"
-msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
-msgstr "Tämä tulostin on {count} tulostimen Ultimaker 3 -ryhmän isäntä."
+msgid "This printer is the host for a group of %1 printers."
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:330
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:319
msgctxt "@label"
msgid "The printer at this address has not yet responded."
msgstr "Tämän osoitteen tulostin ei ole vielä vastannut."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:335
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:39
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:324
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:42
msgctxt "@action:button"
msgid "Connect"
msgstr "Yhdistä"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:349
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:338
msgctxt "@title:window"
msgid "Printer Address"
msgstr "Tulostimen osoite"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:377
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:361
msgctxt "@alabel"
msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Anna verkon tulostimen IP-osoite tai isäntänimi."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:407
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:390
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:132
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
msgstr "OK"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:30
-msgctxt "@title:window"
-msgid "Print over network"
-msgstr "Tulosta verkon kautta"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:61
-msgctxt "@label"
-msgid "Printer selection"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:100
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:44
msgctxt "@action:button"
msgid "Print"
msgstr "Tulosta"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:36
-msgctxt "@label: arg 1 is group name"
-msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
-msgstr "%1 ei ole määritetty yhdistetyn Ultimaker 3 -tulostinryhmän isännäksi"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:47
+msgctxt "@title:window"
+msgid "Print over network"
+msgstr "Tulosta verkon kautta"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
-msgctxt "@label link to connect manager"
-msgid "Add/Remove printers"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:79
+msgctxt "@label"
+msgid "Printer selection"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
-msgctxt "@info:tooltip"
-msgid "Opens the print jobs page with your default web browser."
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:173
+msgctxt "@label"
+msgid "Not available"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:15
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:130
-msgctxt "@action:button"
-msgid "View print jobs"
-msgstr "Näytä tulostustyöt"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:37
-msgctxt "@label:status"
-msgid "Preparing to print"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:175
+msgctxt "@label"
+msgid "Unreachable"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:39
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:263
-msgctxt "@label:status"
-msgid "Printing"
-msgstr "Tulostetaan"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:41
-msgctxt "@label:status"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:180
+msgctxt "@label"
msgid "Available"
-msgstr "Saatavilla"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
-msgctxt "@label:status"
-msgid "Lost connection with the printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:45
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:37
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:44
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:46
msgctxt "@label:status"
-msgid "Unavailable"
+msgid "Aborted"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label:status"
-msgid "Unknown"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:249
-msgctxt "@label:status"
-msgid "Disabled"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:265
-msgctxt "@label:status"
-msgid "Reserved"
-msgstr "Varattu"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:268
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:39
msgctxt "@label:status"
msgid "Finished"
msgstr "Valmis"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:271
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:392
-msgctxt "@label"
-msgid "Preparing to print"
-msgstr "Valmistellaan tulostusta"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:273
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:42
msgctxt "@label:status"
-msgid "Action required"
-msgstr "Vaatii toimenpiteitä"
+msgid "Preparing"
+msgstr "Valmistellaan"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:276
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:48
msgctxt "@label:status"
-msgid "Paused"
+msgid "Pausing"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:52
msgctxt "@label:status"
msgid "Resuming"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:280
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:54
msgctxt "@label:status"
-msgid "Print aborted"
-msgstr "Tulostus keskeytetty"
+msgid "Action required"
+msgstr "Vaatii toimenpiteitä"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:373
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:394
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:213
msgctxt "@label"
-msgid "Not accepting print jobs"
-msgstr "Ei hyväksy tulostustöitä"
+msgid "Waiting for: Unavailable printer"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:387
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:215
msgctxt "@label"
-msgid "Finishes at: "
-msgstr "Päättyy: "
+msgid "Waiting for: First available"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:389
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:217
msgctxt "@label"
-msgid "Clear build plate"
-msgstr "Tyhjennä alusta"
+msgid "Waiting for: "
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:396
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:299
msgctxt "@label"
-msgid "Waiting for configuration change"
-msgstr "Odotetaan määrityksen muutosta"
+msgid "Configuration change"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:63
-msgctxt "@title"
-msgid "Print jobs"
-msgstr "Tulostustyöt"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:93
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:365
msgctxt "@label"
-msgid "Printing"
-msgstr "Tulostetaan"
+msgid "The assigned printer, %1, requires the following configuration change(s):"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:111
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:367
+msgctxt "@label"
+msgid "The printer %1 is assigned, but the job contains an unknown material configuration."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:375
+msgctxt "@label"
+msgid "Change material %1 from %2 to %3."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:378
+msgctxt "@label"
+msgid "Load %3 as material %1 (This cannot be overridden)."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:381
+msgctxt "@label"
+msgid "Change print core %1 from %2 to %3."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:384
+msgctxt "@label"
+msgid "Change build plate to %1 (This cannot be overridden)."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:404
+msgctxt "@label"
+msgid "Override"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:432
+msgctxt "@label"
+msgid "Starting a print job with an incompatible configuration could damage your 3D printer. Are you sure you want to override the configuration and print %1?"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:435
+msgctxt "@window:title"
+msgid "Override configuration configuration and start print"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:466
+msgctxt "@label"
+msgid "Glass"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:469
+msgctxt "@label"
+msgid "Aluminum"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:39
+msgctxt "@label link to connect manager"
+msgid "Manage queue"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:60
msgctxt "@label"
msgid "Queued"
msgstr "Jonossa"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:170
-msgctxt "@label:title"
-msgid "Printers"
-msgstr "Tulostimet"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:36
+msgctxt "@label"
+msgid "Printing"
+msgstr "Tulostetaan"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:224
-msgctxt "@action:button"
-msgid "View printers"
-msgstr "Näytä tulostimet"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:49
+msgctxt "@label link to connect manager"
+msgid "Manage printers"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:38
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:115
+msgctxt "@label"
+msgid "Move to top"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:124
+msgctxt "@label"
+msgid "Delete"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
+msgctxt "@label"
+msgid "Resume"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
+msgctxt "@label"
+msgid "Pause"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:146
+msgctxt "@label"
+msgid "Abort"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:178
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to move %1 to the top of the queue?"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:179
+msgctxt "@window:title"
+msgid "Move print job to top"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:188
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to delete %1?"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:189
+msgctxt "@window:title"
+msgid "Delete print job"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:198
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to abort %1?"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
+msgctxt "@window:title"
+msgid "Abort print"
+msgstr "Keskeytä tulostus"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:43
msgctxt "@info:tooltip"
msgid "Connect to a printer"
msgstr "Yhdistä tulostimeen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:117
-msgctxt "@info:tooltip"
-msgid "Load the configuration of the printer into Cura"
-msgstr "Lataa tulostimen määritys Curaan"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:118
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:121
msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Aktivoi määritys"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:122
+msgctxt "@info:tooltip"
+msgid "Load the configuration of the printer into Cura"
+msgstr "Lataa tulostimen määritys Curaan"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:130
msgctxt "@label"
msgid "Color scheme"
msgstr "Värimalli"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:145
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Materiaalin väri"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:149
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Linjojen tyyppi"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:153
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:157
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:198
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Yhteensopivuustila"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:284
msgctxt "@label"
msgid "Show Travels"
msgstr "Näytä siirtoliikkeet"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:290
msgctxt "@label"
msgid "Show Helpers"
msgstr "Näytä avustimet"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:296
msgctxt "@label"
msgid "Show Shell"
msgstr "Näytä kuori"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:302
msgctxt "@label"
msgid "Show Infill"
msgstr "Näytä täyttö"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:355
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Näytä vain yläkerrokset"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:366
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "Näytä 5 yksityiskohtaista kerrosta ylhäällä"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:379
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Yläosa/alaosa"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:383
msgctxt "@label"
msgid "Inner Wall"
msgstr "Sisäseinämä"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:448
msgctxt "@label"
msgid "min"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:500
msgctxt "@label"
msgid "max"
msgstr ""
@@ -1982,17 +2144,17 @@ msgctxt "@label"
msgid "Post Processing Scripts"
msgstr "Jälkikäsittelykomentosarjat"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:225
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:227
msgctxt "@action"
msgid "Add a script"
msgstr "Lisää komentosarja"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:271
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:273
msgctxt "@label"
msgid "Settings"
msgstr "Asetukset"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:474
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:477
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "Muuta aktiivisia jälkikäsittelykomentosarjoja"
@@ -2087,53 +2249,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Tasoitus"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:37
msgctxt "@label"
msgid "Mesh Type"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:68
msgctxt "@label"
msgid "Normal model"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:75
msgctxt "@label"
msgid "Print as support"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:83
msgctxt "@label"
msgid "Don't support overlap with other models"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:91
msgctxt "@label"
msgid "Modify settings for overlap with other models"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:99
msgctxt "@label"
msgid "Modify settings for infill of other models"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:342
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:347
msgctxt "@action:button"
msgid "Select settings"
msgstr "Valitse asetukset"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:384
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:389
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Valitse tätä mallia varten mukautettavat asetukset"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:432
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:437
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:98
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Suodatin..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:446
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:451
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Näytä kaikki"
@@ -2155,13 +2317,13 @@ msgid "Create new"
msgstr "Luo uusi"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:70
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:68
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:72
msgctxt "@action:title"
msgid "Summary - Cura Project"
msgstr "Yhteenveto – Cura-projekti"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:92
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:96
msgctxt "@action:label"
msgid "Printer settings"
msgstr "Tulostimen asetukset"
@@ -2178,18 +2340,19 @@ msgid "Update"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:143
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:105
msgctxt "@action:label"
msgid "Type"
msgstr "Tyyppi"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:159
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
msgctxt "@action:label"
msgid "Printer Group"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:180
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:192
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:196
msgctxt "@action:label"
msgid "Profile settings"
msgstr "Profiilin asetukset"
@@ -2201,19 +2364,20 @@ msgstr "Miten profiilin ristiriita pitäisi ratkaista?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:216
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:308
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:216
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:220
msgctxt "@action:label"
msgid "Name"
msgstr "Nimi"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:231
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:200
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:204
msgctxt "@action:label"
msgid "Not in profile"
msgstr "Ei profiilissa"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:236
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:209
msgctxt "@action:label"
msgid "%1 override"
msgid_plural "%1 overrides"
@@ -2243,7 +2407,7 @@ msgid "How should the conflict in the material be resolved?"
msgstr "Miten materiaalin ristiriita pitäisi ratkaista?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:327
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:233
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:237
msgctxt "@action:label"
msgid "Setting visibility"
msgstr "Asetusten näkyvyys"
@@ -2254,13 +2418,13 @@ msgid "Mode"
msgstr "Tila"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:352
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:246
msgctxt "@action:label"
msgid "Visible settings:"
msgstr "Näkyvät asetukset:"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:357
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:247
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:251
msgctxt "@action:label"
msgid "%1 out of %2"
msgstr "%1/%2"
@@ -2316,36 +2480,6 @@ msgctxt "@action:button"
msgid "Move to Next Position"
msgstr "Siirry seuraavaan positioon"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:30
-msgctxt "@title"
-msgid "Upgrade Firmware"
-msgstr "Laiteohjelmiston päivitys"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:41
-msgctxt "@label"
-msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
-msgstr "Laiteohjelmisto on suoraan 3D-tulostimessa toimiva ohjelma. Laiteohjelmisto ohjaa askelmoottoreita, säätää lämpötilaa ja saa tulostimen toimimaan."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:51
-msgctxt "@label"
-msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
-msgstr "Uusien tulostimien mukana toimitettava laiteohjelmisto toimii, mutta uusissa versioissa on yleensä enemmän toimintoja ja parannuksia."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:65
-msgctxt "@action:button"
-msgid "Automatically upgrade Firmware"
-msgstr "Päivitä laiteohjelmisto automaattisesti"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:75
-msgctxt "@action:button"
-msgid "Upload custom Firmware"
-msgstr "Lataa mukautettu laiteohjelmisto"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:87
-msgctxt "@title:window"
-msgid "Select custom firmware"
-msgstr "Valitse mukautettu laiteohjelmisto"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:37
msgctxt "@label"
msgid "Please select any upgrades made to this Ultimaker Original"
@@ -2493,26 +2627,10 @@ msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr "Poista tuloste"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
-msgctxt "@label:"
-msgid "Pause"
-msgstr "Keskeytä"
-
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
-msgctxt "@label:"
-msgid "Resume"
-msgstr "Jatka"
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:325
-msgctxt "@label:"
+msgctxt "@label"
msgid "Abort Print"
-msgstr "Keskeytä tulostus"
-
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
-msgctxt "@window:title"
-msgid "Abort print"
-msgstr "Keskeytä tulostus"
+msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:337
msgctxt "@label"
@@ -2549,19 +2667,17 @@ msgid "Customized"
msgstr "Mukautettu"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:634
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:637
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Kysy aina"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:635
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Hylkää äläkä kysy uudelleen"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Säilytä äläkä kysy uudelleen"
@@ -2581,101 +2697,179 @@ msgctxt "@action:button"
msgid "Create New Profile"
msgstr "Luo uusi profiili"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:65
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:71
msgctxt "@title"
msgid "Information"
msgstr "Tiedot"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:94
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:100
msgctxt "@title:window"
msgid "Confirm Diameter Change"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:101
msgctxt "@label (%1 is a number)"
msgid "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:133
msgctxt "@label"
msgid "Display Name"
msgstr "Näytä nimi"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:143
msgctxt "@label"
msgid "Brand"
msgstr "Merkki"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:153
msgctxt "@label"
msgid "Material Type"
msgstr "Materiaalin tyyppi"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:162
msgctxt "@label"
msgid "Color"
msgstr "Väri"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:212
msgctxt "@label"
msgid "Properties"
msgstr "Ominaisuudet"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:214
msgctxt "@label"
msgid "Density"
msgstr "Tiheys"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:229
msgctxt "@label"
msgid "Diameter"
msgstr "Läpimitta"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:253
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:263
msgctxt "@label"
msgid "Filament Cost"
msgstr "Tulostuslangan hinta"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:269
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:280
msgctxt "@label"
msgid "Filament weight"
msgstr "Tulostuslangan paino"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:298
msgctxt "@label"
msgid "Filament length"
msgstr "Tulostuslangan pituus"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:295
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:307
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Hinta metriä kohden"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:309
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:321
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "Materiaali on linkitetty kohteeseen %1 ja niillä on joitain samoja ominaisuuksia."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:316
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:328
msgctxt "@label"
msgid "Unlink Material"
msgstr "Poista materiaalin linkitys"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:327
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:339
msgctxt "@label"
msgid "Description"
msgstr "Kuvaus"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:352
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Tarttuvuustiedot"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:366
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:378
msgctxt "@label"
msgid "Print settings"
msgstr "Tulostusasetukset"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:84
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
+msgctxt "@action:button"
+msgid "Activate"
+msgstr "Aktivoi"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:101
+msgctxt "@action:button"
+msgid "Create"
+msgstr "Luo"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:114
+msgctxt "@action:button"
+msgid "Duplicate"
+msgstr "Jäljennös"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
+msgctxt "@action:button"
+msgid "Import"
+msgstr "Tuo"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
+msgctxt "@action:button"
+msgid "Export"
+msgstr "Vie"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:203
+msgctxt "@action:label"
+msgid "Printer"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:262
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
+msgctxt "@title:window"
+msgid "Confirm Remove"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:263
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
+msgctxt "@label (%1 is object name)"
+msgid "Are you sure you wish to remove %1? This cannot be undone!"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:285
+msgctxt "@title:window"
+msgid "Import Material"
+msgstr "Tuo materiaali"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:286
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Could not import material %1: %2"
+msgstr "Materiaalin tuominen epäonnistui: %1: %2"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:290
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully imported material %1"
+msgstr "Materiaalin tuominen onnistui: %1"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:308
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:316
+msgctxt "@title:window"
+msgid "Export Material"
+msgstr "Vie materiaali"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:320
+msgctxt "@info:status Don't translate the XML tags and !"
+msgid "Failed to export material to %1: %2"
+msgstr "Materiaalin vieminen epäonnistui kohteeseen %1: %2"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:326
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully exported material to %1"
+msgstr "Materiaalin vieminen onnistui kohteeseen %1"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:14
msgctxt "@title:tab"
msgid "Setting Visibility"
@@ -2712,289 +2906,287 @@ msgid "Unit"
msgstr "Yksikkö"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:544
msgctxt "@title:tab"
msgid "General"
msgstr "Yleiset"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:142
msgctxt "@label"
msgid "Interface"
msgstr "Käyttöliittymä"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:153
msgctxt "@label"
msgid "Language:"
msgstr "Kieli:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:220
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Currency:"
msgstr "Valuutta:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:235
msgctxt "@label"
msgid "Theme:"
msgstr "Teema:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:294
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:292
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Sovellus on käynnistettävä uudelleen, jotta nämä muutokset tulevat voimaan."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:311
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:309
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Viipaloi automaattisesti, kun asetuksia muutetaan."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:317
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Viipaloi automaattisesti"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:333
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:331
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Näyttöikkunan käyttäytyminen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:339
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Korosta mallin vailla tukea olevat alueet punaisella. Ilman tukea nämä alueet eivät tulostu kunnolla."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:348
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Näytä uloke"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:357
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:355
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
msgstr "Siirtää kameraa siten, että valittuna oleva malli on näkymän keskellä."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:360
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "Keskitä kamera kun kohde on valittu"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:371
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:369
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "Pitääkö Curan oletusarvoinen zoom-toimintatapa muuttaa päinvastaiseksi?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:376
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:374
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "Käännä kameran zoomin suunta päinvastaiseksi."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "Tuleeko zoomauksen siirtyä hiiren suuntaan?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "Zoomaa hiiren suuntaan"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:401
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "Pitäisikö alustalla olevia malleja siirtää niin, etteivät ne enää leikkaa toisiaan?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:406
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "Varmista, että mallit ovat erillään"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:413
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "Pitäisikö tulostusalueella olevia malleja siirtää alas niin, että ne koskettavat tulostusalustaa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:420
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:418
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Pudota mallit automaattisesti alustalle"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
msgctxt "@info:tooltip"
msgid "Show caution message in g-code reader."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:439
msgctxt "@option:check"
msgid "Caution message in g-code reader"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:449
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:447
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "Pakotetaanko kerros yhteensopivuustilaan?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:452
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "Pakota kerrosnäkymän yhteensopivuustila (vaatii uudelleenkäynnistyksen)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:470
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:468
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Tiedostojen avaaminen ja tallentaminen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:477
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:475
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "Pitäisikö mallit skaalata tulostustilavuuteen, jos ne ovat liian isoja?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:482
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:480
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Skaalaa suuret mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Malli voi vaikuttaa erittäin pieneltä, jos sen koko on ilmoitettu esimerkiksi metreissä eikä millimetreissä. Pitäisikö nämä mallit suurentaa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:497
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:495
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Skaalaa erittäin pienet mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@info:tooltip"
msgid "Should models be selected after they are loaded?"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:510
msgctxt "@option:check"
msgid "Select models when loaded"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "Pitäisikö tulostustyön nimeen lisätä automaattisesti tulostimen nimeen perustuva etuliite?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:527
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:525
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "Lisää laitteen etuliite työn nimeen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "Näytetäänkö yhteenveto, kun projektitiedosto tallennetaan?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:541
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:539
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Näytä yhteenvetoikkuna, kun projekti tallennetaan"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:551
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:549
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Projektitiedoston avaamisen oletustoimintatapa"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:559
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:557
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Projektitiedoston avaamisen oletustoimintatapa: "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
msgctxt "@option:openProject"
-msgid "Always ask"
-msgstr "Kysy aina"
+msgid "Always ask me this"
+msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:574
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:572
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Avaa aina projektina"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:575
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Tuo mallit aina"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:611
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:609
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Kun olet tehnyt muutokset profiiliin ja vaihtanut toiseen, näytetään valintaikkuna, jossa kysytään, haluatko säilyttää vai hylätä muutokset. Tässä voit myös valita oletuskäytöksen, jolloin valintaikkunaa ei näytetä uudelleen."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:618
msgctxt "@label"
-msgid "Override Profile"
-msgstr "Kumoa profiili"
+msgid "Profiles"
+msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:670
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:623
+msgctxt "@window:text"
+msgid "Default behavior for changed setting values when switching to a different profile: "
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
+msgctxt "@option:discardOrKeep"
+msgid "Always discard changed settings"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:639
+msgctxt "@option:discardOrKeep"
+msgid "Always transfer changed settings to new profile"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:673
msgctxt "@label"
msgid "Privacy"
msgstr "Tietosuoja"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:678
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:681
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "Pitäisikö Curan tarkistaa saatavilla olevat päivitykset, kun ohjelma käynnistetään?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:683
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:686
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Tarkista päivitykset käynnistettäessä"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:697
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "Pitäisikö anonyymejä tietoja tulosteesta lähettää Ultimakerille? Huomaa, että malleja, IP-osoitteita tai muita henkilökohtaisia tietoja ei lähetetä eikä tallenneta."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:702
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "Lähetä (anonyymit) tulostustiedot"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:711
msgctxt "@action:button"
msgid "More information"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:729
msgctxt "@label"
msgid "Experimental"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:733
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:736
msgctxt "@info:tooltip"
msgid "Use multi build plate functionality"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:738
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:741
msgctxt "@option:check"
msgid "Use multi build plate functionality (restart required)"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:748
-msgctxt "@info:tooltip"
-msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:753
-msgctxt "@option:check"
-msgid "Do not arrange objects on load"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:16
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:536
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:549
msgctxt "@title:tab"
msgid "Printers"
msgstr "Tulostimet"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:90
-msgctxt "@action:button"
-msgid "Activate"
-msgstr "Aktivoi"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:55
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:129
msgctxt "@action:button"
@@ -3012,7 +3204,7 @@ msgid "Connection:"
msgstr "Yhteys:"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:162
-#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:47
+#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:55
msgctxt "@info:status"
msgid "The printer is not connected."
msgstr "Tulostinta ei ole yhdistetty."
@@ -3038,7 +3230,7 @@ msgid "Aborting print..."
msgstr "Keskeytetään tulostus..."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:540
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:553
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Profiilit"
@@ -3053,18 +3245,6 @@ msgctxt "@label"
msgid "Duplicate"
msgstr "Jäljennös"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:145
-msgctxt "@action:button"
-msgid "Import"
-msgstr "Tuo"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:158
-msgctxt "@action:button"
-msgid "Export"
-msgstr "Vie"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:174
msgctxt "@title:window"
msgid "Create Profile"
@@ -3075,18 +3255,6 @@ msgctxt "@title:window"
msgid "Duplicate Profile"
msgstr "Monista profiili"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:221
-msgctxt "@title:window"
-msgid "Confirm Remove"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:222
-msgctxt "@label (%1 is object name)"
-msgid "Are you sure you wish to remove %1? This cannot be undone!"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:256
msgctxt "@title:window"
msgid "Rename Profile"
@@ -3107,126 +3275,78 @@ msgctxt "@label %1 is printer name"
msgid "Printer: %1"
msgstr "Tulostin: %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Protected profiles"
msgstr "Suojatut profiilit"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Custom profiles"
msgstr "Mukautetut profiilit"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:468
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:480
msgctxt "@action:button"
msgid "Update profile with current settings/overrides"
msgstr "Päivitä nykyiset asetukset tai ohitukset profiiliin"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:475
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:487
msgctxt "@action:button"
msgid "Discard current changes"
msgstr "Hylkää tehdyt muutokset"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:504
msgctxt "@action:label"
msgid "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below."
msgstr "Tässä profiilissa käytetään tulostimen oletusarvoja, joten siinä ei ole alla olevan listan asetuksia tai ohituksia."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:511
msgctxt "@action:label"
msgid "Your current settings match the selected profile."
msgstr "Nykyiset asetukset vastaavat valittua profiilia."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:518
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:530
msgctxt "@title:tab"
msgid "Global Settings"
msgstr "Yleiset asetukset"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:40
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:538
-msgctxt "@title:tab"
-msgid "Materials"
-msgstr "Materiaalit"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:105
-msgctxt "@action:button"
-msgid "Create"
-msgstr "Luo"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:118
-msgctxt "@action:button"
-msgid "Duplicate"
-msgstr "Jäljennös"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:235
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:243
-msgctxt "@title:window"
-msgid "Import Material"
-msgstr "Tuo materiaali"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:244
-msgctxt "@info:status Don't translate the XML tags or !"
-msgid "Could not import material %1: %2"
-msgstr "Materiaalin tuominen epäonnistui: %1: %2"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:248
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully imported material %1"
-msgstr "Materiaalin tuominen onnistui: %1"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:266
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:274
-msgctxt "@title:window"
-msgid "Export Material"
-msgstr "Vie materiaali"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:278
-msgctxt "@info:status Don't translate the XML tags and !"
-msgid "Failed to export material to %1: %2"
-msgstr "Materiaalin vieminen epäonnistui kohteeseen %1: %2"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:284
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully exported material to %1"
-msgstr "Materiaalin vieminen onnistui kohteeseen %1"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:337
-msgctxt "@action:label"
-msgid "Printer"
-msgstr ""
-
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:896
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:953
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Lisää tulostin"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:195
msgctxt "@label"
msgid "Printer Name:"
msgstr "Tulostimen nimi:"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:219
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Lisää tulostin"
+#: /home/ruben/Projects/Cura/resources/qml/JobSpecs.qml:84
+msgctxt "@text Print job name"
+msgid "Untitled"
+msgstr ""
+
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:15
msgctxt "@title:window"
msgid "About Cura"
msgstr "Tietoja Curasta"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:55
msgctxt "@label"
msgid "version: %1"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:56
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
msgctxt "@label"
msgid "End-to-end solution for fused filament 3D printing."
msgstr "Kokonaisvaltainen sulatettavan tulostuslangan 3D-tulostusratkaisu."
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:82
msgctxt "@info:credit"
msgid ""
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
@@ -3235,102 +3355,122 @@ msgstr ""
"Cura-ohjelman on kehittänyt Ultimaker B.V. yhteistyössä käyttäjäyhteisön kanssa.\n"
"Cura hyödyntää seuraavia avoimeen lähdekoodiin perustuvia projekteja:"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
msgctxt "@label"
msgid "Graphical user interface"
msgstr "Graafinen käyttöliittymä"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
msgctxt "@label"
msgid "Application framework"
msgstr "Sovelluskehys"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
msgctxt "@label"
msgid "G-code generator"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
msgctxt "@label"
msgid "Interprocess communication library"
msgstr "Prosessien välinen tietoliikennekirjasto"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
msgctxt "@label"
msgid "Programming language"
msgstr "Ohjelmointikieli"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:124
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
msgctxt "@label"
msgid "GUI framework"
msgstr "GUI-kehys"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:125
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
msgctxt "@label"
msgid "GUI framework bindings"
msgstr "GUI-kehyksen sidonnat"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:126
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:140
msgctxt "@label"
msgid "C/C++ Binding library"
msgstr "C/C++ -sidontakirjasto"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:141
msgctxt "@label"
msgid "Data interchange format"
msgstr "Data Interchange Format"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:142
msgctxt "@label"
msgid "Support library for scientific computing"
msgstr "Tieteellisen laskennan tukikirjasto"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:129
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:143
msgctxt "@label"
msgid "Support library for faster math"
msgstr "Nopeamman laskennan tukikirjasto"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:130
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:144
msgctxt "@label"
msgid "Support library for handling STL files"
msgstr "STL-tiedostojen käsittelyn tukikirjasto"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:145
+msgctxt "@label"
+msgid "Support library for handling planar objects"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:146
+msgctxt "@label"
+msgid "Support library for handling triangular meshes"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
+msgctxt "@label"
+msgid "Support library for analysis of complex networks"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
msgctxt "@label"
msgid "Support library for handling 3MF files"
msgstr "Tukikirjasto 3MF-tiedostojen käsittelyyn"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:149
+msgctxt "@label"
+msgid "Support library for file metadata and streaming"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:150
msgctxt "@label"
msgid "Serial communication library"
msgstr "Sarjatietoliikennekirjasto"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:151
msgctxt "@label"
msgid "ZeroConf discovery library"
msgstr "ZeroConf-etsintäkirjasto"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:152
msgctxt "@label"
msgid "Polygon clipping library"
msgstr "Monikulmion leikkauskirjasto"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:153
msgctxt "@Label"
msgid "Python HTTP library"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:155
msgctxt "@label"
msgid "Font"
msgstr "Fontti"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:156
msgctxt "@label"
msgid "SVG icons"
msgstr "SVG-kuvakkeet"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:157
msgctxt "@label"
msgid "Linux cross-distribution application deployment"
msgstr ""
@@ -3340,7 +3480,7 @@ msgctxt "@label"
msgid "Profile:"
msgstr "Profiili:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
@@ -3351,53 +3491,53 @@ msgstr ""
"\n"
"Avaa profiilin hallinta napsauttamalla."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:200
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Haku…"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:544
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:545
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Kopioi arvo kaikkiin suulakepuristimiin"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:553
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:554
msgctxt "@action:menu"
msgid "Copy all changed values to all extruders"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:568
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:591
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Piilota tämä asetus"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:609
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "Älä näytä tätä asetusta"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:590
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:613
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Pidä tämä asetus näkyvissä"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:614
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:426
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:637
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Määritä asetusten näkyvyys..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:644
msgctxt "@action:inmenu"
msgid "Collapse All"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:649
msgctxt "@action:inmenu"
msgid "Expand All"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:253
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
@@ -3418,17 +3558,17 @@ msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Riippuu seuraavista:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:155
msgctxt "@label"
msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:158
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "Arvo perustuu suulakepuristimien arvoihin "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:189
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
@@ -3439,7 +3579,7 @@ msgstr ""
"\n"
"Palauta profiilin arvo napsauttamalla."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:281
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
@@ -3486,7 +3626,7 @@ msgid "Send a custom G-code command to the connected printer. Press 'enter' to s
msgstr ""
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ExtruderBox.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:268
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:272
msgctxt "@label"
msgid "Extruder"
msgstr "Suulake"
@@ -3539,7 +3679,7 @@ msgid "The nozzle inserted in this extruder."
msgstr "Tähän suulakkeeseen liitetty suutin."
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/HeatedBedBox.qml:25
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:489
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:493
msgctxt "@label"
msgid "Build plate"
msgstr "Alusta"
@@ -3564,6 +3704,21 @@ msgctxt "@tooltip of pre-heat"
msgid "Heat the bed in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the bed to heat up when you're ready to print."
msgstr "Lämmitä pöytä ennen tulostusta. Voit edelleen säätää tulostinta sen lämmitessä, eikä sinun tarvitse odottaa pöydän lämpiämistä, kun olet valmis tulostamaan."
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:13
+msgctxt "@label:category menu label"
+msgid "Material"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:37
+msgctxt "@label:category menu label"
+msgid "Favorites"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:61
+msgctxt "@label:category menu label"
+msgid "Generic"
+msgstr ""
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/PrinterMenu.qml:25
msgctxt "@label:category menu label"
msgid "Network enabled printers"
@@ -3579,12 +3734,12 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Näytä"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:39
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:42
msgctxt "@action:inmenu menubar:view"
msgid "&Camera position"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:58
msgctxt "@action:inmenu menubar:view"
msgid "&Build plate"
msgstr ""
@@ -3594,12 +3749,12 @@ msgctxt "@action:inmenu"
msgid "Visible Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:42
msgctxt "@action:inmenu"
msgid "Show All Settings"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:53
msgctxt "@action:inmenu"
msgid "Manage Setting Visibility..."
msgstr ""
@@ -3662,347 +3817,346 @@ msgstr ""
"Tulostuksen asennus ei käytössä\n"
"G-code-tiedostoja ei voida muokata"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:340
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:359
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:358
msgctxt "@tooltip"
msgid "Time specification"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:440
msgctxt "@label"
msgid "Cost specification"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
msgctxt "@label m for meter"
msgid "%1m"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:447
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:456
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
msgctxt "@label g for grams"
msgid "%1g"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:453
msgctxt "@label"
msgid "Total:"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:577
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Suositeltu tulostuksen asennus
Tulosta valitun tulostimen, materiaalin ja laadun suositelluilla asetuksilla."
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:582
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "Mukautettu tulostuksen asennus
Tulosta hallitsemalla täysin kaikkia viipalointiprosessin vaiheita."
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:106
msgctxt "@label"
msgid "Active print"
msgstr "Aktiivinen tulostustyö"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:115
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:114
msgctxt "@label"
msgid "Job Name"
msgstr "Työn nimi"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:122
msgctxt "@label"
msgid "Printing Time"
msgstr "Tulostusaika"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:130
msgctxt "@label"
msgid "Estimated time left"
msgstr "Aikaa jäljellä arviolta"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:78
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
-msgid "Toggle Fu&ll Screen"
-msgstr "Vaihda &koko näyttöön"
+msgid "Toggle Full Screen"
+msgstr "Vaihda koko näyttöön"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:85
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "&Kumoa"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "Tee &uudelleen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "&Lopeta"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:113
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
msgctxt "@action:inmenu menubar:view"
-msgid "&3D View"
+msgid "3D View"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
msgctxt "@action:inmenu menubar:view"
-msgid "&Front View"
+msgid "Front View"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
msgctxt "@action:inmenu menubar:view"
-msgid "&Top View"
+msgid "Top View"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
msgctxt "@action:inmenu menubar:view"
-msgid "&Left Side View"
+msgid "Left Side View"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
msgctxt "@action:inmenu menubar:view"
-msgid "&Right Side View"
+msgid "Right Side View"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Määritä Curan asetukset..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:155
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "L&isää tulostin..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:161
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
msgstr "Tulostinten &hallinta..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Hallitse materiaaleja..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:176
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Päivitä nykyiset asetukset tai ohitukset profiiliin"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Hylkää tehdyt muutokset"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "&Luo profiili nykyisten asetusten tai ohitusten perusteella..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:202
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Profiilien hallinta..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:209
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Näytä sähköinen &dokumentaatio"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "Ilmoita &virheestä"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:225
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
-msgid "&About..."
-msgstr "Ti&etoja..."
+msgid "About..."
+msgstr "Tietoja..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:232
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
-msgid "Delete &Selected Model"
-msgid_plural "Delete &Selected Models"
-msgstr[0] "Poista &valittu malli"
-msgstr[1] "Poista &valitut mallit"
+msgid "Delete Selected Model"
+msgid_plural "Delete Selected Models"
+msgstr[0] "Poista valittu malli"
+msgstr[1] "Poista valitut mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Keskitä valittu malli"
msgstr[1] "Keskitä valitut mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Kerro valittu malli"
msgstr[1] "Kerro valitut mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Poista malli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:278
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "Ke&skitä malli alustalle"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "&Ryhmittele mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Poista mallien ryhmitys"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "&Yhdistä mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
msgstr "&Kerro malli..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:331
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
-msgid "&Select All Models"
-msgstr "&Valitse kaikki mallit"
+msgid "Select All Models"
+msgstr "Valitse kaikki mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
-msgid "&Clear Build Plate"
-msgstr "&Tyhjennä tulostusalusta"
+msgid "Clear Build Plate"
+msgstr "Tyhjennä tulostusalusta"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
+msgctxt "@action:inmenu menubar:file"
+msgid "Reload All Models"
+msgstr "Lataa kaikki mallit uudelleen"
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
-msgctxt "@action:inmenu menubar:file"
-msgid "Re&load All Models"
-msgstr "&Lataa kaikki mallit uudelleen"
-
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:360
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models To All Build Plates"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:367
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Järjestä kaikki mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Järjestä valinta"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:382
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Määritä kaikkien mallien positiot uudelleen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
-msgid "Reset All Model &Transformations"
-msgstr "Määritä kaikkien mallien &muutokset uudelleen"
+msgid "Reset All Model Transformations"
+msgstr "Määritä kaikkien mallien muutokset uudelleen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:396
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Avaa tiedosto(t)..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:404
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Uusi projekti..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:411
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
msgstr "Näytä moottorin l&oki"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:419
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Näytä määrityskansio"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:433
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse packages..."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:440
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:inmenu menubar:view"
msgid "Expand/Collapse Sidebar"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:27
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Lataa 3D-malli"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:37
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Valmiina viipaloimaan"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:39
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "Viipaloidaan..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:41
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Valmis: %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:43
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
msgstr "Viipalointi ei onnistu"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:45
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "Viipalointi ei käytettävissä"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Slice current printjob"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Cancel slicing process"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Valmistele"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Peruuta"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:320
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "Valitse aktiivinen tulostusväline"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:713
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:767
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Avaa tiedosto(t)"
@@ -4022,129 +4176,145 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:103
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
-msgstr "&Tiedosto"
+msgstr "Tie&dosto"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
-msgctxt "@action:inmenu menubar:file"
-msgid "&Save Selection to File"
-msgstr "&Tallenna valinta tiedostoon"
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:121
msgctxt "@title:menu menubar:file"
-msgid "Save &As..."
-msgstr "Tallenna &nimellä…"
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
-msgctxt "@title:menu menubar:file"
-msgid "Save &Project..."
+msgid "&Save..."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:142
+msgctxt "@title:menu menubar:file"
+msgid "&Export..."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:153
+msgctxt "@action:inmenu menubar:file"
+msgid "Export Selection..."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:174
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Muokkaa"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:191
msgctxt "@title:menu"
msgid "&View"
msgstr "&Näytä"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
msgctxt "@title:menu"
msgid "&Settings"
msgstr "&Asetukset"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
-msgctxt "@title:menu menubar:toplevel"
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+msgctxt "@title:menu menubar:settings"
msgid "&Printer"
msgstr "&Tulostin"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:195
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:207
msgctxt "@title:menu"
msgid "&Material"
msgstr "&Materiaali"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:204
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:216
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Aseta aktiiviseksi suulakepuristimeksi"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:210
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:222
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:188
msgctxt "@action:inmenu"
msgid "Enable Extruder"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:217
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:229
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:194
msgctxt "@action:inmenu"
msgid "Disable Extruder"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:230
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:241
msgctxt "@title:menu"
+msgid "&Build plate"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:242
+msgctxt "@title:settings"
msgid "&Profile"
msgstr "&Profiili"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:252
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "Laa&jennukset"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:274
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:286
msgctxt "@title:menu menubar:toplevel"
msgid "&Toolbox"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:281
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:294
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "L&isäasetukset"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:289
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:302
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "&Ohje"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:348
msgctxt "@label"
msgid "This package will be installed after restarting."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:364
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:377
msgctxt "@action:button"
msgid "Open File"
msgstr "Avaa tiedosto"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:547
msgctxt "@title:tab"
msgid "Settings"
msgstr "Asetukset"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:579
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:593
msgctxt "@title:window"
msgid "New project"
msgstr "Uusi projekti"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:594
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "Haluatko varmasti aloittaa uuden projektin? Se tyhjentää alustan ja kaikki tallentamattomat asetukset."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:814
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:722
+msgctxt "@title:window"
+msgid "Closing Cura"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:723
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:735
+msgctxt "@label"
+msgid "Are you sure you want to exit Cura?"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:868
msgctxt "@window:title"
msgid "Install Package"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:821
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:875
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Avaa tiedosto(t)"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:824
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:878
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "Löysimme vähintään yhden Gcode-tiedoston valitsemiesi tiedostojen joukosta. Voit avata vain yhden Gcode-tiedoston kerrallaan. Jos haluat avata Gcode-tiedoston, valitse vain yksi."
@@ -4154,112 +4324,107 @@ msgctxt "@title:window"
msgid "Save Project"
msgstr "Tallenna projekti"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:116
-msgctxt "@action:label"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:137
msgctxt "@action:label"
msgid "Build plate"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:169
msgctxt "@action:label"
msgid "Extruder %1"
msgstr "Suulake %1"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:179
msgctxt "@action:label"
msgid "%1 & material"
msgstr "%1 & materiaali"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:268
msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "Älä näytä projektin yhteenvetoa tallennettaessa"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:283
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:287
msgctxt "@action:button"
msgid "Save"
msgstr "Tallenna"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:192
msgctxt "@label"
msgid "Layer Height"
msgstr "Kerroksen korkeus"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:277
msgctxt "@tooltip"
msgid "This quality profile is not available for you current material and nozzle configuration. Please change these to enable this quality profile"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:450
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:467
msgctxt "@label"
msgid "Print Speed"
msgstr "Tulostusnopeus"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:479
msgctxt "@label"
msgid "Slower"
msgstr "Hitaammin"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:490
msgctxt "@label"
msgid "Faster"
msgstr "Nopeammin"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:518
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:506
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:541
msgctxt "@label"
msgid "Infill"
msgstr "Täyttö"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:740
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:777
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Asteittainen täyttö lisää täytön tiheyttä vähitellen yläosaa kohti."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:752
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:791
msgctxt "@label"
msgid "Enable gradual"
msgstr "Ota asteittainen käyttöön"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:819
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:858
msgctxt "@label"
msgid "Generate Support"
msgstr "Muodosta tuki"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:853
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:892
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "Muodosta rakenteita, jotka tukevat mallin ulokkeita sisältäviä osia. Ilman tukirakenteita kyseiset osat luhistuvat tulostuksen aikana."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:925
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:964
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Valitse tukena käytettävä suulakepuristin. Näin mallin alle rakennetaan tukirakenteita estämään mallin painuminen tai tulostuminen ilmaan."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:948
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:987
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Alustan tarttuvuus"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1003
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1042
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
msgstr "Ota reunuksen tai pohjaristikon tulostus käyttöön. Tämä lisää kappaleen ympärille tai alle tasaisen alueen, joka on helppo leikata pois myöhemmin."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1043
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1082
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "Tarvitsetko apua tulosteiden parantamiseen? Lue Ultimakerin vianmääritysoppaat"
@@ -4306,22 +4471,22 @@ msgctxt "@label"
msgid "Printer type"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:376
msgctxt "@label"
msgid "Material"
msgstr "Materiaali"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:538
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:543
msgctxt "@label"
-msgid "Use adhesion sheet or glue with this material combination"
+msgid "Use glue with this material combination"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:570
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:575
msgctxt "@label"
msgid "Check compatibility"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:588
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:593
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Napsauta ja tarkista materiaalin yhteensopivuus sivustolla Ultimaker.com."
@@ -4411,16 +4576,6 @@ msgctxt "name"
msgid "God Mode"
msgstr "Jumala-tila"
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "description"
-msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
-msgstr "Hyväksyy GCode-määrittelyt ja lähettää ne Wi-Fi-yhteyden kautta Doodle3D WiFi-Boxiin."
-
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "name"
-msgid "Doodle3D WiFi-Box"
-msgstr "Doodle3D WiFi-Box"
-
#: ChangeLogPlugin/plugin.json
msgctxt "description"
msgid "Shows changes since latest checked version."
@@ -4431,6 +4586,16 @@ msgctxt "name"
msgid "Changelog"
msgstr "Muutosloki"
+#: FirmwareUpdater/plugin.json
+msgctxt "description"
+msgid "Provides a machine actions for updating firmware."
+msgstr ""
+
+#: FirmwareUpdater/plugin.json
+msgctxt "name"
+msgid "Firmware Updater"
+msgstr ""
+
#: ProfileFlattener/plugin.json
msgctxt "description"
msgid "Create a flattend quality changes profile."
@@ -4501,16 +4666,6 @@ msgctxt "name"
msgid "Prepare Stage"
msgstr ""
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "description"
-msgid "Provides an edit window for direct script editing."
-msgstr ""
-
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "name"
-msgid "Live scripting tool"
-msgstr ""
-
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4621,16 +4776,6 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Aikaisempien Cura-profiilien lukija"
-#: CuraBlenderPlugin/plugin.json
-msgctxt "description"
-msgid "Helps to open Blender files directly in Cura."
-msgstr ""
-
-#: CuraBlenderPlugin/plugin.json
-msgctxt "name"
-msgid "Blender Integration (experimental)"
-msgstr ""
-
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4681,6 +4826,16 @@ msgctxt "name"
msgid "Version Upgrade 2.7 to 3.0"
msgstr "Päivitys versiosta 2.7 versioon 3.0"
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "description"
+msgid "Upgrades configurations from Cura 3.4 to Cura 3.5."
+msgstr ""
+
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "name"
+msgid "Version Upgrade 3.4 to 3.5"
+msgstr ""
+
#: VersionUpgrade/VersionUpgrade30to31/plugin.json
msgctxt "description"
msgid "Upgrades configurations from Cura 3.0 to Cura 3.1."
@@ -4821,6 +4976,146 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Cura-profiilin lukija"
+#~ msgctxt "@action"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Päivitä laiteohjelmisto"
+
+#~ msgctxt "@title"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Laiteohjelmiston päivitys"
+
+#~ msgctxt "@action:button"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Tulostus Doodle3D WiFi-Boxin avulla"
+
+#~ msgctxt "@properties:tooltip"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Tulostus Doodle3D WiFi-Boxin avulla"
+
+#~ msgctxt "@info:status"
+#~ msgid "Connecting to Doodle3D Connect"
+#~ msgstr "Yhteyden muodostaminen Doodle3D Connectiin"
+
+#~ msgctxt "@info:status"
+#~ msgid "Sending data to Doodle3D Connect"
+#~ msgstr "Lähetetään tietoja Doodle3D Connectiin"
+
+#~ msgctxt "@info:status"
+#~ msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
+#~ msgstr "Tietojen lähetys Doodle3D Connectiin ei onnistu. Onko toinen työ yhä aktiivinen?"
+
+#~ msgctxt "@info:status"
+#~ msgid "Storing data on Doodle3D Connect"
+#~ msgstr "Tietoja tallennetaan Doodle3D Connectiin"
+
+#~ msgctxt "@info:status"
+#~ msgid "File sent to Doodle3D Connect"
+#~ msgstr "Tiedosto lähetetty Doodle3D Connectiin"
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Open the Doodle3D Connect web interface"
+#~ msgstr "Avaa Doodle3D Connect -verkkoliittymä"
+
+#~ msgctxt "@label"
+#~ msgid "This printer is not set up to host a group of Ultimaker 3 printers."
+#~ msgstr "Tätä tulostinta ei ole määritetty Ultimaker 3 -tulostinryhmän isännäksi."
+
+#~ msgctxt "@label"
+#~ msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
+#~ msgstr "Tämä tulostin on {count} tulostimen Ultimaker 3 -ryhmän isäntä."
+
+#~ msgctxt "@label: arg 1 is group name"
+#~ msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
+#~ msgstr "%1 ei ole määritetty yhdistetyn Ultimaker 3 -tulostinryhmän isännäksi"
+
+#~ msgctxt "@action:button"
+#~ msgid "View print jobs"
+#~ msgstr "Näytä tulostustyöt"
+
+#~ msgctxt "@label:status"
+#~ msgid "Printing"
+#~ msgstr "Tulostetaan"
+
+#~ msgctxt "@label:status"
+#~ msgid "Available"
+#~ msgstr "Saatavilla"
+
+#~ msgctxt "@label:status"
+#~ msgid "Reserved"
+#~ msgstr "Varattu"
+
+#~ msgctxt "@label"
+#~ msgid "Preparing to print"
+#~ msgstr "Valmistellaan tulostusta"
+
+#~ msgctxt "@label:status"
+#~ msgid "Print aborted"
+#~ msgstr "Tulostus keskeytetty"
+
+#~ msgctxt "@label"
+#~ msgid "Not accepting print jobs"
+#~ msgstr "Ei hyväksy tulostustöitä"
+
+#~ msgctxt "@label"
+#~ msgid "Finishes at: "
+#~ msgstr "Päättyy: "
+
+#~ msgctxt "@label"
+#~ msgid "Clear build plate"
+#~ msgstr "Tyhjennä alusta"
+
+#~ msgctxt "@label"
+#~ msgid "Waiting for configuration change"
+#~ msgstr "Odotetaan määrityksen muutosta"
+
+#~ msgctxt "@title"
+#~ msgid "Print jobs"
+#~ msgstr "Tulostustyöt"
+
+#~ msgctxt "@label:title"
+#~ msgid "Printers"
+#~ msgstr "Tulostimet"
+
+#~ msgctxt "@action:button"
+#~ msgid "View printers"
+#~ msgstr "Näytä tulostimet"
+
+#~ msgctxt "@label:"
+#~ msgid "Pause"
+#~ msgstr "Keskeytä"
+
+#~ msgctxt "@label:"
+#~ msgid "Resume"
+#~ msgstr "Jatka"
+
+#~ msgctxt "@label:"
+#~ msgid "Abort Print"
+#~ msgstr "Keskeytä tulostus"
+
+#~ msgctxt "@option:openProject"
+#~ msgid "Always ask"
+#~ msgstr "Kysy aina"
+
+#~ msgctxt "@label"
+#~ msgid "Override Profile"
+#~ msgstr "Kumoa profiili"
+
+#~ msgctxt "@action:inmenu menubar:file"
+#~ msgid "&Save Selection to File"
+#~ msgstr "&Tallenna valinta tiedostoon"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save &As..."
+#~ msgstr "Tallenna &nimellä…"
+
+#~ msgctxt "description"
+#~ msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
+#~ msgstr "Hyväksyy GCode-määrittelyt ja lähettää ne Wi-Fi-yhteyden kautta Doodle3D WiFi-Boxiin."
+
+#~ msgctxt "name"
+#~ msgid "Doodle3D WiFi-Box"
+#~ msgstr "Doodle3D WiFi-Box"
+
#~ msgctxt "@item:inlistbox"
#~ msgid "SolidWorks part file"
#~ msgstr "SolidWorks-osatiedosto"
@@ -5307,10 +5602,6 @@ msgstr "Cura-profiilin lukija"
#~ msgid "This printer is the host for a group of %1 connected Ultimaker 3 printers"
#~ msgstr "Tämä tulostin on %1 tulostimen yhdistetyn Ultimaker 3 -ryhmän isäntä"
-#~ msgctxt "@label:status"
-#~ msgid "Preparing"
-#~ msgstr "Valmistellaan"
-
#~ msgctxt "@label"
#~ msgid "Completed on: "
#~ msgstr "Valmistunut: "
diff --git a/resources/i18n/fi_FI/fdmextruder.def.json.po b/resources/i18n/fi_FI/fdmextruder.def.json.po
index 02f11f3837..07ccc2502e 100644
--- a/resources/i18n/fi_FI/fdmextruder.def.json.po
+++ b/resources/i18n/fi_FI/fdmextruder.def.json.po
@@ -5,9 +5,9 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0000\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
"PO-Revision-Date: 2017-08-11 14:31+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Finnish\n"
@@ -166,6 +166,16 @@ msgctxt "extruder_prime_pos_z description"
msgid "The Z coordinate of the position where the nozzle primes at the start of printing."
msgstr "Z-koordinaatti sijainnille, jossa suutin esitäytetään tulostusta aloitettaessa."
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number label"
+msgid "Extruder Print Cooling Fan"
+msgstr ""
+
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number description"
+msgid "The number of the print cooling fan associated with this extruder. Only change this from the default value of 0 when you have a different print cooling fan for each extruder."
+msgstr ""
+
#: fdmextruder.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
diff --git a/resources/i18n/fi_FI/fdmprinter.def.json.po b/resources/i18n/fi_FI/fdmprinter.def.json.po
index 862dadaa15..6a4e7390ad 100644
--- a/resources/i18n/fi_FI/fdmprinter.def.json.po
+++ b/resources/i18n/fi_FI/fdmprinter.def.json.po
@@ -5,9 +5,9 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-03-29 08:36+0200\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
"PO-Revision-Date: 2017-09-27 12:27+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Finnish\n"
@@ -80,6 +80,16 @@ msgctxt "material_guid description"
msgid "GUID of the material. This is set automatically. "
msgstr "Materiaalin GUID. Tämä määritetään automaattisesti. "
+#: fdmprinter.def.json
+msgctxt "material_diameter label"
+msgid "Diameter"
+msgstr "Läpimitta"
+
+#: fdmprinter.def.json
+msgctxt "material_diameter description"
+msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
+msgstr "Säätää käytetyn tulostuslangan halkaisijaa. Määritä tämä arvo vastaamaan käytetyn tulostuslangan halkaisijaa."
+
#: fdmprinter.def.json
msgctxt "material_bed_temp_wait label"
msgid "Wait for Build Plate Heatup"
@@ -1055,6 +1065,16 @@ msgctxt "top_bottom_pattern_0 option zigzag"
msgid "Zig Zag"
msgstr "Siksak"
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons label"
+msgid "Connect Top/Bottom Polygons"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons description"
+msgid "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happen midway over infill this feature can reduce the top surface quality."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "skin_angles label"
msgid "Top/Bottom Line Directions"
@@ -1135,6 +1155,26 @@ msgctxt "travel_compensate_overlapping_walls_x_enabled description"
msgid "Compensate the flow for parts of an inner wall being printed where there is already a wall in place."
msgstr "Kompensoi tulostettaessa virtausta niiden sisäseinämien osien kohdalla, joissa on jo olemassa seinämä."
+#: fdmprinter.def.json
+msgctxt "wall_min_flow label"
+msgid "Minimum Wall Flow"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow description"
+msgid "Minimum allowed percentage flow for a wall line. The wall overlap compensation reduces a wall's flow when it lies close to an existing wall. Walls whose flow is less than this value will be replaced with a travel move. When using this setting, you must enable the wall overlap compensation and print the outer wall before inner walls."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract label"
+msgid "Prefer Retract"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract description"
+msgid "If enabled, retraction is used rather than combing for travel moves that replace walls whose flow is below the minimum flow threshold."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "fill_perimeter_gaps label"
msgid "Fill Gaps Between Walls"
@@ -1452,7 +1492,7 @@ msgstr "Täyttökuvio"
#: fdmprinter.def.json
msgctxt "infill_pattern description"
-msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
+msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Gyroid, cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
msgstr ""
#: fdmprinter.def.json
@@ -1500,11 +1540,6 @@ msgctxt "infill_pattern option concentric"
msgid "Concentric"
msgstr "Samankeskinen"
-#: fdmprinter.def.json
-msgctxt "infill_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Samankeskinen 3D"
-
#: fdmprinter.def.json
msgctxt "infill_pattern option zigzag"
msgid "Zig Zag"
@@ -1520,6 +1555,11 @@ msgctxt "infill_pattern option cross_3d"
msgid "Cross 3D"
msgstr "Risti 3D"
+#: fdmprinter.def.json
+msgctxt "infill_pattern option gyroid"
+msgid "Gyroid"
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "zig_zaggify_infill label"
msgid "Connect Infill Lines"
@@ -1530,6 +1570,16 @@ msgctxt "zig_zaggify_infill description"
msgid "Connect the ends where the infill pattern meets the inner wall using a line which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduce the effects of infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons label"
+msgid "Connect Infill Polygons"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons description"
+msgid "Connect infill paths where they run next to each other. For infill patterns which consist of several closed polygons, enabling this setting greatly reduces the travel time."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "infill_angles label"
msgid "Infill Line Directions"
@@ -1560,6 +1610,28 @@ msgctxt "infill_offset_y description"
msgid "The infill pattern is moved this distance along the Y axis."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "infill_multiplier label"
+msgid "Infill Line Multiplier"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "infill_multiplier description"
+msgid "Convert each infill line to this many lines. The extra lines do not cross over each other, but avoid each other. This makes the infill stiffer, but increases print time and material usage."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count label"
+msgid "Extra Infill Wall Count"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count description"
+msgid ""
+"Add extra walls around the infill area. Such walls can make top/bottom skin lines sag down less which means you need less top/bottom skin layers for the same quality at the cost of some extra material.\n"
+"This feature can combine with the Connect Infill Polygons to connect all the infill into a single extrusion path without the need for travels or retractions if configured right."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
msgid "Cubic Subdivision Shell"
@@ -1870,16 +1942,6 @@ msgctxt "material_bed_temperature_layer_0 description"
msgid "The temperature used for the heated build plate at the first layer."
msgstr "Lämmitettävän alustan lämpötila ensimmäistä kerrosta tulostettaessa."
-#: fdmprinter.def.json
-msgctxt "material_diameter label"
-msgid "Diameter"
-msgstr "Läpimitta"
-
-#: fdmprinter.def.json
-msgctxt "material_diameter description"
-msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
-msgstr "Säätää käytetyn tulostuslangan halkaisijaa. Määritä tämä arvo vastaamaan käytetyn tulostuslangan halkaisijaa."
-
#: fdmprinter.def.json
msgctxt "material_adhesion_tendency label"
msgid "Adhesion Tendency"
@@ -2717,8 +2779,8 @@ msgstr "Pyyhkäisytila"
#: fdmprinter.def.json
msgctxt "retraction_combing description"
-msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
-msgstr "Pyyhkäisy pitää suuttimen aiemmin tulostetuilla alueilla siirtoliikkeitä tehtäessä. Tämä johtaa hieman pidempiin siirtoliikkeisiin, mutta vähentää takaisinvedon tarvetta. Jos pyyhkäisy on poistettu käytöstä, materiaalille tehdään takaisinveto ja suutin liikkuu suoraan seuraavaan pisteeseen. On myös mahdollista välttää pyyhkäisy ylä- tai alapintakalvojen yli pyyhkäisemällä vain täytössä."
+msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas and also to only comb within the infill. Note that the 'Within Infill' option behaves exactly like the 'Not in Skin' option in earlier Cura releases."
+msgstr ""
#: fdmprinter.def.json
msgctxt "retraction_combing option off"
@@ -2735,6 +2797,11 @@ msgctxt "retraction_combing option noskin"
msgid "Not in Skin"
msgstr ""
+#: fdmprinter.def.json
+msgctxt "retraction_combing option infill"
+msgid "Within Infill"
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "retraction_combing_max_distance label"
msgid "Max Comb Distance With No Retract"
@@ -3115,11 +3182,6 @@ msgctxt "support_pattern option concentric"
msgid "Concentric"
msgstr "Samankeskinen"
-#: fdmprinter.def.json
-msgctxt "support_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Samankeskinen 3D"
-
#: fdmprinter.def.json
msgctxt "support_pattern option zigzag"
msgid "Zig Zag"
@@ -3180,6 +3242,56 @@ msgctxt "support_line_distance description"
msgid "Distance between the printed support structure lines. This setting is calculated by the support density."
msgstr "Tulostettujen tukirakenteiden linjojen välinen etäisyys. Tämä asetus lasketaan tuen tiheyden perusteella."
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance label"
+msgid "Initial Layer Support Line Distance"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance description"
+msgid "Distance between the printed initial layer support structure lines. This setting is calculated by the support density."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle label"
+msgid "Support Infill Line Direction"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle description"
+msgid "Orientation of the infill pattern for supports. The support infill pattern is rotated in the horizontal plane."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable label"
+msgid "Enable Support Brim"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable description"
+msgid "Generate a brim within the support infill regions of the first layer. This brim is printed underneath the support, not around it. Enabling this setting increases the adhesion of support to the build plate."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width label"
+msgid "Support Brim Width"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width description"
+msgid "The width of the brim to print underneath the support. A larger brim enhances adhesion to the build plate, at the cost of some extra material."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count label"
+msgid "Support Brim Line Count"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count description"
+msgid "The number of lines used for the support brim. More brim lines enhance adhesion to the build plate, at the cost of some extra material."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "support_z_distance label"
msgid "Support Z Distance"
@@ -3470,11 +3582,6 @@ msgctxt "support_interface_pattern option concentric"
msgid "Concentric"
msgstr "Samankeskinen"
-#: fdmprinter.def.json
-msgctxt "support_interface_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Samankeskinen 3D"
-
#: fdmprinter.def.json
msgctxt "support_interface_pattern option zigzag"
msgid "Zig Zag"
@@ -3510,11 +3617,6 @@ msgctxt "support_roof_pattern option concentric"
msgid "Concentric"
msgstr "Samankeskinen"
-#: fdmprinter.def.json
-msgctxt "support_roof_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Samankeskinen 3D"
-
#: fdmprinter.def.json
msgctxt "support_roof_pattern option zigzag"
msgid "Zig Zag"
@@ -3550,16 +3652,31 @@ msgctxt "support_bottom_pattern option concentric"
msgid "Concentric"
msgstr "Samankeskinen"
-#: fdmprinter.def.json
-msgctxt "support_bottom_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Samankeskinen 3D"
-
#: fdmprinter.def.json
msgctxt "support_bottom_pattern option zigzag"
msgid "Zig Zag"
msgstr "Siksak"
+#: fdmprinter.def.json
+msgctxt "support_fan_enable label"
+msgid "Fan Speed Override"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_fan_enable description"
+msgid "When enabled, the print cooling fan speed is altered for the skin regions immediately above the support."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed label"
+msgid "Supported Skin Fan Speed"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed description"
+msgid "Percentage fan speed to use when printing the skin regions immediately above the support. Using a high fan speed can make the support easier to remove."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "support_use_towers label"
msgid "Use Towers"
@@ -3742,6 +3859,16 @@ msgctxt "brim_line_count description"
msgid "The number of lines used for a brim. More brim lines enhance adhesion to the build plate, but also reduces the effective print area."
msgstr "Reunukseen käytettävien linjojen lukumäärä. Useampi reunuslinja parantaa kiinnitystä alustaan, mutta rajoittaa tehokasta tulostusaluetta."
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support label"
+msgid "Brim Replaces Support"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support description"
+msgid "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions of the first layer of support by brim regions."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "brim_outside_only label"
msgid "Brim Only on Outside"
@@ -3884,8 +4011,8 @@ msgstr "Pohjaristikon pohjakerroksen linjojen leveys. Näiden tulisi olla paksuj
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing label"
-msgid "Raft Line Spacing"
-msgstr "Pohjaristikon linjajako"
+msgid "Raft Base Line Spacing"
+msgstr ""
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing description"
@@ -4102,16 +4229,6 @@ msgctxt "prime_tower_min_volume description"
msgid "The minimum volume for each layer of the prime tower in order to purge enough material."
msgstr "Esitäyttötornin kunkin kerroksen minimitilavuus, jotta voidaan poistaa riittävästi materiaalia."
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness label"
-msgid "Prime Tower Thickness"
-msgstr "Esitäyttötornin paksuus"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness description"
-msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
-msgstr "Onton esitäyttötornin paksuus. Jos paksuus ylittää puolet esitäyttötornin minimitilavuudesta, tuloksena on tiheä esitäyttötorni."
-
#: fdmprinter.def.json
msgctxt "prime_tower_position_x label"
msgid "Prime Tower X Position"
@@ -4152,26 +4269,6 @@ msgctxt "prime_tower_wipe_enabled description"
msgid "After printing the prime tower with one nozzle, wipe the oozed material from the other nozzle off on the prime tower."
msgstr "Kun esitäyttötorni on tulostettu yhdellä suuttimella, pyyhi toisesta suuttimesta tihkunut materiaali pois esitäyttötornissa."
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe label"
-msgid "Wipe Nozzle After Switch"
-msgstr "Pyyhi suutin vaihdon jälkeen"
-
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe description"
-msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
-msgstr "Pyyhi suuttimen vaihdon jälkeen tihkunut materiaali pois suuttimesta, kun ensimmäinen kappale on tulostettu. Näin saadaan aikaan turvallinen ja hidas pyyhkäisyliike kohdassa, jossa tihkunut materiaali vaurioittaa mahdollisimman vähän tulostuksen pinnan laatua."
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume label"
-msgid "Prime Tower Purge Volume"
-msgstr "Esitäyttötornin poistoainemäärä"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume description"
-msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
-msgstr "Poistettavan tulostuslangan määrä esitäyttötornia pyyhittäessä. Poisto on hyödyllinen menetetyn tulostuslangan kompensointiin, silloin kun sitä tihkuu suuttimen ollessa ei-aktiivinen."
-
#: fdmprinter.def.json
msgctxt "ooze_shield_enabled label"
msgid "Enable Ooze Shield"
@@ -4657,6 +4754,16 @@ msgctxt "material_flow_temp_graph description"
msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
msgstr "Tiedot, jotka yhdistävät materiaalivirran (mm3 sekunnissa) lämpötilaan (celsiusastetta)."
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference label"
+msgid "Minimum Polygon Circumference"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference description"
+msgid "Polygons in sliced layers that have a circumference smaller than this amount will be filtered out. Lower values lead to higher resolution mesh at the cost of slicing time. It is meant mostly for high resolution SLA printers and very tiny 3D models with a lot of details."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "meshfix_maximum_resolution label"
msgid "Maximum Resolution"
@@ -5316,6 +5423,26 @@ msgctxt "adaptive_layer_height_threshold description"
msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle label"
+msgid "Overhanging Wall Angle"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle description"
+msgid "Walls that overhang more than this angle will be printed using overhanging wall settings. When the value is 90, no walls will be treated as overhanging."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor label"
+msgid "Overhanging Wall Speed"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor description"
+msgid "Overhanging walls will be printed at this percentage of their normal print speed."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "bridge_settings_enabled label"
msgid "Enable Bridge Settings"
@@ -5346,16 +5473,6 @@ msgctxt "bridge_skin_support_threshold description"
msgid "If a skin region is supported for less than this percentage of its area, print it using the bridge settings. Otherwise it is printed using the normal skin settings."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang label"
-msgid "Bridge Wall Max Overhang"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang description"
-msgid "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "bridge_wall_coast label"
msgid "Bridge Wall Coasting"
@@ -5576,6 +5693,58 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Mallissa käytettävä muunnosmatriisi, kun malli ladataan tiedostosta."
+#~ msgctxt "infill_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Samankeskinen 3D"
+
+#~ msgctxt "retraction_combing description"
+#~ msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
+#~ msgstr "Pyyhkäisy pitää suuttimen aiemmin tulostetuilla alueilla siirtoliikkeitä tehtäessä. Tämä johtaa hieman pidempiin siirtoliikkeisiin, mutta vähentää takaisinvedon tarvetta. Jos pyyhkäisy on poistettu käytöstä, materiaalille tehdään takaisinveto ja suutin liikkuu suoraan seuraavaan pisteeseen. On myös mahdollista välttää pyyhkäisy ylä- tai alapintakalvojen yli pyyhkäisemällä vain täytössä."
+
+#~ msgctxt "support_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Samankeskinen 3D"
+
+#~ msgctxt "support_interface_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Samankeskinen 3D"
+
+#~ msgctxt "support_roof_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Samankeskinen 3D"
+
+#~ msgctxt "support_bottom_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Samankeskinen 3D"
+
+#~ msgctxt "raft_base_line_spacing label"
+#~ msgid "Raft Line Spacing"
+#~ msgstr "Pohjaristikon linjajako"
+
+#~ msgctxt "prime_tower_wall_thickness label"
+#~ msgid "Prime Tower Thickness"
+#~ msgstr "Esitäyttötornin paksuus"
+
+#~ msgctxt "prime_tower_wall_thickness description"
+#~ msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
+#~ msgstr "Onton esitäyttötornin paksuus. Jos paksuus ylittää puolet esitäyttötornin minimitilavuudesta, tuloksena on tiheä esitäyttötorni."
+
+#~ msgctxt "dual_pre_wipe label"
+#~ msgid "Wipe Nozzle After Switch"
+#~ msgstr "Pyyhi suutin vaihdon jälkeen"
+
+#~ msgctxt "dual_pre_wipe description"
+#~ msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
+#~ msgstr "Pyyhi suuttimen vaihdon jälkeen tihkunut materiaali pois suuttimesta, kun ensimmäinen kappale on tulostettu. Näin saadaan aikaan turvallinen ja hidas pyyhkäisyliike kohdassa, jossa tihkunut materiaali vaurioittaa mahdollisimman vähän tulostuksen pinnan laatua."
+
+#~ msgctxt "prime_tower_purge_volume label"
+#~ msgid "Prime Tower Purge Volume"
+#~ msgstr "Esitäyttötornin poistoainemäärä"
+
+#~ msgctxt "prime_tower_purge_volume description"
+#~ msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
+#~ msgstr "Poistettavan tulostuslangan määrä esitäyttötornia pyyhittäessä. Poisto on hyödyllinen menetetyn tulostuslangan kompensointiin, silloin kun sitä tihkuu suuttimen ollessa ei-aktiivinen."
+
#~ msgctxt "optimize_wall_printing_order description"
#~ msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization."
#~ msgstr "Optimoi seinämien tulostusjärjestys takaisinvetojen ja kuljetun etäisyyden vähentämiseksi. Useimmat osat hyötyvät tämän asetuksen käytöstä, mutta joissakin saattaa kestää kauemmin, joten vertaa tulostusajan arvioita optimointia käytettäessä ja ilman sitä."
diff --git a/resources/i18n/fr_FR/cura.po b/resources/i18n/fr_FR/cura.po
index 8921c2c6c7..9b1fff4124 100644
--- a/resources/i18n/fr_FR/cura.po
+++ b/resources/i18n/fr_FR/cura.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0200\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0100\n"
+"PO-Revision-Date: 2018-09-28 14:59+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -40,6 +40,17 @@ msgctxt "@item:inlistbox"
msgid "G-code File"
msgstr "Fichier GCode"
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:67
+msgctxt "@error:not supported"
+msgid "GCodeWriter does not support non-text mode."
+msgstr "GCodeWriter ne prend pas en charge le mode non-texte."
+
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:73
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:89
+msgctxt "@warning:status"
+msgid "Please prepare G-code before exporting."
+msgstr "Veuillez préparer le G-Code avant d'exporter."
+
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
msgctxt "@info:title"
msgid "3D Model Assistant"
@@ -55,102 +66,51 @@ msgid ""
"
"
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
-msgctxt "@action:button"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Imprimer avec Doodle3D WiFi-Box"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:66
-msgctxt "@properties:tooltip"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Imprimer avec Doodle3D WiFi-Box"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:86
-msgctxt "@info:status"
-msgid "Connecting to Doodle3D Connect"
-msgstr "Connexion avec Doodle3D Connecter..."
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:258
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:204
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:398
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:88
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
-#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
-#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:275
-msgctxt "@action:button"
-msgid "Cancel"
-msgstr "Annuler"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:154
-msgctxt "@info:status"
-msgid "Sending data to Doodle3D Connect"
-msgstr "Envoi de données vers Doodle3D Connecter..."
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:161
-msgctxt "@info:status"
-msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
-msgstr "Impossible d'envoyer les données à Doodle3D Connect. Une autre tâche est-elle toujours active ?"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:175
-msgctxt "@info:status"
-msgid "Storing data on Doodle3D Connect"
-msgstr "Enregistrement de données dans Doodle3D Connecter..."
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:213
-msgctxt "@info:status"
-msgid "File sent to Doodle3D Connect"
-msgstr "Fichier envoyé vers Doodle3D Connecter"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@action:button"
-msgid "Open Connect..."
-msgstr "Ouvrir Connect..."
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@info:tooltip"
-msgid "Open the Doodle3D Connect web interface"
-msgstr "Ouvrir l'interface web Doodle3D Connecter"
-
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:32
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Afficher le récapitulatif des changements"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:20
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py:25
+msgctxt "@action"
+msgid "Update Firmware"
+msgstr "Mettre à jour le firmware"
+
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:23
msgctxt "@item:inmenu"
msgid "Flatten active settings"
msgstr "Aplatir les paramètres actifs"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:32
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:35
msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr "Le profil a été aplati et activé."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:40
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:32
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "Impression par USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:41
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:33
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "Imprimer via USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:42
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:34
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "Imprimer via USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:83
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:69
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Connecté via USB"
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:92
+msgctxt "@label"
+msgid "A USB print is in progress, closing Cura will stop this print. Are you sure?"
+msgstr "Une impression USB est en cours, la fermeture de Cura arrêtera cette impression. Êtes-vous sûr ?"
+
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/install/X3GWriter/__init__.py:15
#: /home/ruben/Projects/Cura/plugins/X3GWriter/__init__.py:15
msgctxt "X3G Writer File Description"
@@ -173,7 +133,12 @@ msgctxt "@item:inlistbox"
msgid "Compressed G-code File"
msgstr "Fichier G-Code compressé"
-#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/GCodeGzWriter.py:38
+msgctxt "@error:not supported"
+msgid "GCodeGzWriter does not support text mode."
+msgstr "GCodeGzWriter ne prend pas en charge le mode texte."
+
+#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:28
msgctxt "@item:inlistbox"
msgid "Ultimaker Format Package"
msgstr "Ultimaker Format Package"
@@ -195,7 +160,7 @@ msgid "Save to Removable Drive {0}"
msgstr "Enregistrer sur un lecteur amovible {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:64
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:133
msgctxt "@info:status"
msgid "There are no file formats available to write with!"
msgstr "Aucun format de fichier n'est disponible pour écriture !"
@@ -209,7 +174,7 @@ msgstr "Enregistrement sur le lecteur amovible {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:94
msgctxt "@info:title"
msgid "Saving"
-msgstr "Enregistrement..."
+msgstr "Enregistrement"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:104
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:107
@@ -234,7 +199,7 @@ msgstr "Impossible d'enregistrer sur le lecteur {0}: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:137
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:133
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:140
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1592
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1607
msgctxt "@info:title"
msgid "Error"
msgstr "Erreur"
@@ -263,8 +228,8 @@ msgstr "Ejecter le lecteur amovible {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:151
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:163
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1582
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1681
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1597
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1695
msgctxt "@info:title"
msgid "Warning"
msgstr "Avertissement"
@@ -291,259 +256,269 @@ msgctxt "@item:intext"
msgid "Removable Drive"
msgstr "Lecteur amovible"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:70
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:78
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:88
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "Imprimer sur le réseau"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:71
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:79
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:74
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:89
msgctxt "@properties:tooltip"
msgid "Print over network"
msgstr "Imprimer sur le réseau"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:84
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:87
msgctxt "@info:status"
msgid "Connected over the network."
msgstr "Connecté sur le réseau."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:87
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:90
msgctxt "@info:status"
msgid "Connected over the network. Please approve the access request on the printer."
msgstr "Connecté sur le réseau. Veuillez approuver la demande d'accès sur l'imprimante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:89
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:92
msgctxt "@info:status"
msgid "Connected over the network. No access to control the printer."
msgstr "Connecté sur le réseau. Pas d'accès pour commander l'imprimante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:94
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:97
msgctxt "@info:status"
msgid "Access to the printer requested. Please approve the request on the printer"
msgstr "Accès à l'imprimante demandé. Veuillez approuver la demande sur l'imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:97
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:100
msgctxt "@info:title"
msgid "Authentication status"
msgstr "Statut d'authentification"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:99
-msgctxt "@info:status"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:100
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:106
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:110
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:108
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:112
msgctxt "@info:title"
msgid "Authentication Status"
msgstr "Statut d'authentification"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:101
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:103
msgctxt "@action:button"
msgid "Retry"
msgstr "Réessayer"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:104
msgctxt "@info:tooltip"
msgid "Re-send the access request"
msgstr "Renvoyer la demande d'accès"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:105
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:107
msgctxt "@info:status"
msgid "Access to the printer accepted"
msgstr "Accès à l'imprimante accepté"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:109
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:111
msgctxt "@info:status"
msgid "No access to print with this printer. Unable to send print job."
msgstr "Aucun accès pour imprimer avec cette imprimante. Impossible d'envoyer la tâche d'impression."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:111
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:29
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:33
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:70
msgctxt "@action:button"
msgid "Request Access"
msgstr "Demande d'accès"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:113
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:28
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:72
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:115
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:34
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:71
msgctxt "@info:tooltip"
msgid "Send access request to the printer"
msgstr "Envoyer la demande d'accès à l'imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:198
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:200
msgctxt "@label"
msgid "Unable to start a new print job."
msgstr "Impossible de démarrer une nouvelle tâche d'impression."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:200
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:202
msgctxt "@label"
msgid "There is an issue with the configuration of your Ultimaker, which makes it impossible to start the print. Please resolve this issues before continuing."
msgstr "Un problème avec la configuration de votre Ultimaker empêche le démarrage de l'impression. Veuillez résoudre ce problème avant de continuer."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:206
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:228
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:208
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:230
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "Configuration différente"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:220
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:222
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "Êtes-vous sûr(e) de vouloir imprimer avec la configuration sélectionnée ?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:222
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:224
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Problème de compatibilité entre la configuration ou l'étalonnage de l'imprimante et Cura. Pour un résultat optimal, découpez toujours pour les PrintCores et matériaux insérés dans votre imprimante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:249
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:166
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:251
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:199
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "Envoi de nouvelles tâches (temporairement) bloqué, envoi de la tâche d'impression précédente en cours."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:256
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:185
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:202
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:258
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:234
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "Envoi des données à l'imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:257
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:186
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:203
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:259
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:219
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:235
msgctxt "@info:title"
msgid "Sending Data"
-msgstr "Envoi des données..."
+msgstr "Envoi des données"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:321
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:260
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:236
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:381
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:20
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
+#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:279
+msgctxt "@action:button"
+msgid "Cancel"
+msgstr "Annuler"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:323
#, python-brace-format
msgctxt "@info:status"
msgid "No Printcore loaded in slot {slot_number}"
msgstr "Pas de PrintCore inséré dans la fente {slot_number}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:327
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:329
#, python-brace-format
msgctxt "@info:status"
msgid "No material loaded in slot {slot_number}"
msgstr "Aucun matériau inséré dans la fente {slot_number}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:350
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:352
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {cura_printcore_name}, Printer: {remote_printcore_name}) selected for extruder {extruder_id}"
msgstr "PrintCore différent (Cura : {cura_printcore_name}, Imprimante : {remote_printcore_name}) sélectionné pour l'extrudeuse {extruder_id}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:359
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:361
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "Matériau différent (Cura : {0}, Imprimante : {1}) sélectionné pour l'extrudeuse {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:545
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:547
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "Synchroniser avec votre imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:547
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:549
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "Voulez-vous utiliser votre configuration d'imprimante actuelle dans Cura ?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:549
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:551
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Les PrintCores et / ou matériaux sur votre imprimante diffèrent de ceux de votre projet actuel. Pour un résultat optimal, découpez toujours pour les PrintCores et matériaux insérés dans votre imprimante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:81
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:91
msgctxt "@info:status"
msgid "Connected over the network"
-msgstr "Connecté sur le réseau."
+msgstr "Connecté sur le réseau"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:303
msgctxt "@info:status"
msgid "Print job was successfully sent to the printer."
msgstr "L'envoi de la tâche d'impression à l'imprimante a réussi."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:264
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:305
msgctxt "@info:title"
msgid "Data Sent"
msgstr "Données envoyées"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:265
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:306
msgctxt "@action:button"
msgid "View in Monitor"
msgstr "Afficher sur le moniteur"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:353
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:422
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "{printer_name} a terminé d'imprimer '{job_name}'."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:355
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:424
#, python-brace-format
msgctxt "@info:status"
msgid "The print job '{job_name}' was finished."
msgstr "La tâche d'impression '{job_name}' est terminée."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:356
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:425
msgctxt "@info:status"
msgid "Print finished"
msgstr "Impression terminée"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:20
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py:26
msgctxt "@action"
msgid "Connect via Network"
msgstr "Connecter via le réseau"
-#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:13
msgctxt "@item:inmenu"
msgid "Monitor"
msgstr "Surveiller"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:69
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:119
+msgctxt "@info"
+msgid "Could not access update information."
+msgstr "Impossible d'accéder aux informations de mise à jour."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:17
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr "De nouvelles fonctionnalités sont disponibles pour votre {machine_name} ! Il est recommandé de mettre à jour le firmware sur votre imprimante."
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:73
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:21
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "Nouveau firmware %s disponible"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:76
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:27
msgctxt "@action:button"
msgid "How to update"
msgstr "Comment effectuer la mise à jour"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:92
-msgctxt "@info"
-msgid "Could not access update information."
-msgstr "Impossible d'accéder aux informations de mise à jour."
-
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Layer view"
msgstr "Vue en couches"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:113
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "Cura n'affiche pas les couches avec précision lorsque l'impression filaire est activée"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:114
msgctxt "@info:title"
msgid "Simulation View"
msgstr "Vue simulation"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:27
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:35
msgid "Modify G-Code"
msgstr "Modifier le G-Code"
@@ -557,32 +532,32 @@ msgctxt "@info:tooltip"
msgid "Create a volume in which supports are not printed."
msgstr "Créer un volume dans lequel les supports ne sont pas imprimés."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:44
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
msgctxt "@info"
msgid "Cura collects anonymized usage statistics."
msgstr "Cura recueille des statistiques d'utilisation anonymes."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:47
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:55
msgctxt "@info:title"
msgid "Collecting Data"
-msgstr "Collecte des données..."
+msgstr "Collecte des données"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:57
msgctxt "@action:button"
msgid "More info"
msgstr "Plus d'informations"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:58
msgctxt "@action:tooltip"
msgid "See more information on what data Cura sends."
msgstr "Voir plus d'informations sur les données envoyées par Cura."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:60
msgctxt "@action:button"
msgid "Allow"
msgstr "Autoriser"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:53
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:61
msgctxt "@action:tooltip"
msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
msgstr "Autoriser Cura à envoyer des statistiques d'utilisation anonymes pour mieux prioriser les améliorations futures apportées à Cura. Certaines de vos préférences et paramètres sont envoyés, ainsi que la version du logiciel Cura et un hachage des modèles que vous découpez."
@@ -592,18 +567,6 @@ msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Profils Cura 15.04"
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
-msgctxt "@item:inlistbox"
-msgid "Blender file"
-msgstr "Fichier Blender"
-
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
-msgctxt "@info:status"
-msgid ""
-"Could not export using \"{}\" quality!\n"
-"Felt back to \"{}\"."
-msgstr "Impossible d'exporter avec la qualité \"{}\" !\nQualité redéfinie sur \"{}\"."
-
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "JPG Image"
@@ -629,49 +592,56 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "Image GIF"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "Impossible de découper le matériau actuel, car celui-ci est incompatible avec la machine ou la configuration sélectionnée."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:344
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:367
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:376
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:363
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:387
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:396
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:405
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:414
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "Impossible de découper"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:343
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:362
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "Impossible de couper avec les paramètres actuels. Les paramètres suivants contiennent des erreurs : {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr "Impossible de couper en raison de certains paramètres par modèle. Les paramètres suivants contiennent des erreurs sur un ou plusieurs modèles : {error_labels}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:375
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:395
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "Impossible de couper car la tour primaire ou la (les) position(s) d'amorçage ne sont pas valides."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:385
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:404
+#, python-format
+msgctxt "@info:status"
+msgid "Unable to slice because there are objects associated with disabled Extruder %s."
+msgstr "Impossible de couper car il existe des objets associés à l'extrudeuse désactivée %s."
+
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:413
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "Rien à couper car aucun des modèles ne convient au volume d'impression. Mettez à l'échelle ou faites pivoter les modèles pour les faire correspondre."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "Traitement des couches"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:title"
msgid "Information"
msgstr "Informations"
@@ -687,29 +657,40 @@ msgid "Configure Per Model Settings"
msgstr "Configurer les paramètres par modèle"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:175
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:575
msgctxt "@title:tab"
msgid "Recommended"
msgstr "Recommandé"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:177
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:580
msgctxt "@title:tab"
msgid "Custom"
msgstr "Personnalisé"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:32
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:28
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:34
msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "Fichier 3MF"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:199
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:695
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:714
msgctxt "@label"
msgid "Nozzle"
msgstr "Buse"
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:468
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Project file {0} contains an unknown machine type {1}. Cannot import the machine. Models will be imported instead."
+msgstr "Le fichier projet {0} contient un type de machine inconnu {1}. Impossible d'importer la machine. Les modèles seront importés à la place."
+
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:471
+msgctxt "@info:title"
+msgid "Open Project File"
+msgstr "Ouvrir un fichier de projet"
+
#: /home/ruben/Projects/Cura/plugins/SolidView/__init__.py:12
msgctxt "@item:inmenu"
msgid "Solid view"
@@ -720,18 +701,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "Fichier G"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:322
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "Analyse du G-Code"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:470
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:326
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:474
msgctxt "@info:title"
msgid "G-code Details"
msgstr "Détails G-Code"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:468
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:472
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "Assurez-vous que le g-code est adapté à votre imprimante et à la configuration de l'imprimante avant d'y envoyer le fichier. La représentation du g-code peut ne pas être exacte."
@@ -742,27 +723,27 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Profil Cura"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:26
msgctxt "@item:inlistbox"
msgid "3MF file"
msgstr "Fichier 3MF"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:34
msgctxt "@item:inlistbox"
msgid "Cura Project 3MF file"
msgstr "Projet Cura fichier 3MF"
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/ThreeMFWriter.py:179
+msgctxt "@error:zip"
+msgid "Error writing 3mf file."
+msgstr "Erreur d'écriture du fichier 3MF."
+
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelection.py:17
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelection.py:18
msgctxt "@action"
msgid "Select upgrades"
msgstr "Sélectionner les mises à niveau"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py:12
-msgctxt "@action"
-msgid "Upgrade Firmware"
-msgstr "Mise à niveau du firmware"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py:14
msgctxt "@action"
msgid "Checkup"
@@ -773,79 +754,79 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "Nivellement du plateau"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:82
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "Paroi externe"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:83
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "Parois internes"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:84
msgctxt "@tooltip"
msgid "Skin"
msgstr "Couche extérieure"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:85
msgctxt "@tooltip"
msgid "Infill"
msgstr "Remplissage"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:86
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "Remplissage du support"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:87
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "Interface du support"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:88
msgctxt "@tooltip"
msgid "Support"
msgstr "Support"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
msgctxt "@tooltip"
msgid "Skirt"
msgstr "Jupe"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
msgctxt "@tooltip"
msgid "Travel"
msgstr "Déplacement"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
msgctxt "@tooltip"
msgid "Retractions"
msgstr "Rétractions"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
msgctxt "@tooltip"
msgid "Other"
msgstr "Autre"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:229
-msgctxt "@label unknown material"
-msgid "Unknown"
-msgstr "Inconnu"
-
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:313
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:310
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "Fichier {0} prédécoupé"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:235
+#: /home/ruben/Projects/Cura/cura/API/Account.py:71
+msgctxt "@info:title"
+msgid "Login failed"
+msgstr "La connexion a échoué"
+
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:201
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "Le fichier existe déjà"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:236
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:202
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:122
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
@@ -857,23 +838,23 @@ msgctxt "@menuitem"
msgid "Not overridden"
msgstr "Pas écrasé"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:119
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "Le matériau sélectionné est incompatible avec la machine ou la configuration sélectionnée."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:120
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
msgctxt "@info:title"
msgid "Incompatible Material"
msgstr "Matériau incompatible"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:842
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:866
#, python-format
msgctxt "@info:generic"
msgid "Settings have been changed to match the current availability of extruders: [%s]"
msgstr "Les paramètres ont été modifiés pour correspondre aux extrudeuses actuellement disponibles : [%s]"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:844
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:868
msgctxt "@info:title"
msgid "Settings updated"
msgstr "Paramètres mis à jour"
@@ -902,8 +883,6 @@ msgid "Export succeeded"
msgstr "L'exportation a réussi"
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:170
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
@@ -911,58 +890,70 @@ msgstr "Échec de l'importation du profil depuis le fichier {0} or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "No custom profile to import in file {0}"
msgstr "Aucun profil personnalisé à importer dans le fichier {0}"
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags !"
+msgid "Failed to import profile from {0}:"
+msgstr "Échec de l'importation du profil depuis le fichier {0} :"
+
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:218
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:228
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "This profile {0} contains incorrect data, could not import it."
msgstr "Le profil {0} contient des données incorrectes ; échec de l'importation."
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:241
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it."
msgstr "La machine définie dans le profil {0} ({1}) ne correspond pas à votre machine actuelle ({2}) ; échec de l'importation."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:316
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:312
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Failed to import profile from {0}:"
+msgstr "Échec de l'importation du profil depuis le fichier {0} :"
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:315
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "Importation du profil {0} réussie"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:319
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:318
#, python-brace-format
msgctxt "@info:status"
msgid "File {0} does not contain any valid profile."
msgstr "Le fichier {0} ne contient pas de profil valide."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:322
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "Le profil {0} est un type de fichier inconnu ou est corrompu."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:340
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:339
msgctxt "@label"
msgid "Custom profile"
msgstr "Personnaliser le profil"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:356
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:355
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "Il manque un type de qualité au profil."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:368
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:369
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "Impossible de trouver un type de qualité {0} pour la configuration actuelle."
-#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:60
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:63
#, python-brace-format
msgctxt "@label"
msgid "Group #{group_nr}"
@@ -989,42 +980,42 @@ msgctxt "@item:inlistbox"
msgid "All Files (*)"
msgstr "Tous les fichiers (*)"
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:544
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:636
msgctxt "@label"
msgid "Custom Material"
msgstr "Matériau personnalisé"
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:545
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:637
msgctxt "@label"
msgid "Custom"
msgstr "Personnalisé"
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:80
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:81
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
msgstr "La hauteur du volume d'impression a été réduite en raison de la valeur du paramètre « Séquence d'impression » afin d'éviter que le portique ne heurte les modèles imprimés."
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:82
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:83
msgctxt "@info:title"
msgid "Build Volume"
msgstr "Volume d'impression"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:98
msgctxt "@info:backup_failed"
msgid "Could not create archive from user data directory: {}"
msgstr "Impossible de créer une archive à partir du répertoire de données de l'utilisateur : {}"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:103
msgctxt "@info:title"
msgid "Backup"
msgstr "Sauvegarde"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:113
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup without having proper data or meta data."
msgstr "A essayé de restaurer une sauvegarde Cura sans disposer de données ou de métadonnées appropriées."
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:123
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup that does not match your current version."
msgstr "A essayé de restaurer une sauvegarde Cura qui ne correspond pas à votre version actuelle."
@@ -1035,32 +1026,32 @@ msgid "Multiplying and placing objects"
msgstr "Multiplication et placement d'objets"
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:28
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
msgctxt "@info:title"
msgid "Placing Object"
-msgstr "Placement de l'objet..."
+msgstr "Placement de l'objet"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:96
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:149
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
msgctxt "@info:status"
msgid "Unable to find a location within the build volume for all objects"
msgstr "Impossible de trouver un emplacement dans le volume d'impression pour tous les objets"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:30
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:66
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:67
msgctxt "@info:status"
msgid "Finding new location for objects"
msgstr "Recherche d'un nouvel emplacement pour les objets"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:34
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:70
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:71
msgctxt "@info:title"
msgid "Finding Location"
-msgstr "Recherche d'emplacement..."
+msgstr "Recherche d'emplacement"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:97
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:151
msgctxt "@info:title"
msgid "Can't Find Location"
msgstr "Impossible de trouver un emplacement"
@@ -1191,223 +1182,233 @@ msgctxt "@action:button"
msgid "Send report"
msgstr "Envoyer rapport"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:328
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:473
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Chargement des machines..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:756
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:775
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "Préparation de la scène..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:789
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:811
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Chargement de l'interface..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1023
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1037
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1581
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1596
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "Un seul fichier G-Code peut être chargé à la fois. Importation de {0} sautée"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1591
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1606
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "Impossible d'ouvrir un autre fichier si le G-Code est en cours de chargement. Importation de {0} sautée"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1680
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1694
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr "Le modèle sélectionné était trop petit pour être chargé."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:59
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:61
msgctxt "@title"
msgid "Machine Settings"
msgstr "Paramètres de la machine"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:78
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:80
msgctxt "@title:tab"
msgid "Printer"
msgstr "Imprimante"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:97
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:99
msgctxt "@label"
msgid "Printer Settings"
msgstr "Paramètres de l'imprimante"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:108
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:110
msgctxt "@label"
msgid "X (Width)"
msgstr "X (Largeur)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:109
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:384
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:418
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:430
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:855
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:111
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:121
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:131
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:237
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:386
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:402
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:428
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:440
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:896
msgctxt "@label"
msgid "mm"
msgstr "mm"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:118
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:120
msgctxt "@label"
msgid "Y (Depth)"
msgstr "Y (Profondeur)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:128
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:130
msgctxt "@label"
msgid "Z (Height)"
msgstr "Z (Hauteur)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:140
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:142
msgctxt "@label"
msgid "Build plate shape"
msgstr "Forme du plateau"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:149
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:151
msgctxt "@option:check"
msgid "Origin at center"
msgstr "Origine au centre"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:157
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:159
msgctxt "@option:check"
msgid "Heated bed"
msgstr "Plateau chauffant"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:168
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:170
msgctxt "@label"
msgid "G-code flavor"
msgstr "Parfum G-Code"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:181
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:183
msgctxt "@label"
msgid "Printhead Settings"
msgstr "Paramètres de la tête d'impression"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:191
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:193
msgctxt "@label"
msgid "X min"
msgstr "X min"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:192
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:194
msgctxt "@tooltip"
msgid "Distance from the left of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distance entre la gauche de la tête d'impression et le centre de la buse. Permet d'empêcher les collisions entre les impressions précédentes et la tête d'impression lors d'une impression « Un à la fois »."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:201
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:203
msgctxt "@label"
msgid "Y min"
msgstr "Y min"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:202
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:204
msgctxt "@tooltip"
msgid "Distance from the front of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distance entre le devant de la tête d'impression et le centre de la buse. Permet d'empêcher les collisions entre les impressions précédentes et la tête d'impression lors d'une impression « Un à la fois »."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:211
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:213
msgctxt "@label"
msgid "X max"
msgstr "X max"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:212
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:214
msgctxt "@tooltip"
msgid "Distance from the right of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distance entre la droite de la tête d'impression et le centre de la buse. Permet d'empêcher les collisions entre les impressions précédentes et la tête d'impression lors d'une impression « Un à la fois »."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:221
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:223
msgctxt "@label"
msgid "Y max"
msgstr "Y max"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:222
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:224
msgctxt "@tooltip"
msgid "Distance from the rear of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distance entre le dos de la tête d'impression et le centre de la buse. Permet d'empêcher les collisions entre les impressions précédentes et la tête d'impression lors d'une impression « Un à la fois »."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:234
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
msgctxt "@label"
msgid "Gantry height"
msgstr "Hauteur du portique"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:238
msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "La différence de hauteur entre la pointe de la buse et le système de portique (axes X et Y). Permet d'empêcher les collisions entre les impressions précédentes et le portique lors d'une impression « Un à la fois »."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:257
msgctxt "@label"
msgid "Number of Extruders"
msgstr "Nombre d'extrudeuses"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:311
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:313
msgctxt "@label"
msgid "Start G-code"
msgstr "G-Code de démarrage"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:321
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:323
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very start."
msgstr "Commandes G-Code à exécuter au tout début."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:330
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:332
msgctxt "@label"
msgid "End G-code"
msgstr "G-Code de fin"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:340
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:342
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very end."
msgstr "Commandes G-Code à exécuter tout à la fin."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:371
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:373
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Paramètres de la buse"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:385
msgctxt "@label"
msgid "Nozzle size"
msgstr "Taille de la buse"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:399
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
msgctxt "@label"
msgid "Compatible material diameter"
msgstr "Diamètre du matériau compatible"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:403
msgctxt "@tooltip"
msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
msgstr "Le diamètre nominal de filament pris en charge par l'imprimante. Le diamètre exact sera remplacé par le matériau et / ou le profil."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:417
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:427
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "Décalage buse X"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:429
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:439
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Décalage buse Y"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:450
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+msgctxt "@label"
+msgid "Cooling Fan Number"
+msgstr "Numéro du ventilateur de refroidissement"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:452
+msgctxt "@label"
+msgid ""
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:472
msgctxt "@label"
msgid "Extruder Start G-code"
msgstr "Extrudeuse G-Code de démarrage"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:468
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:490
msgctxt "@label"
msgid "Extruder End G-code"
msgstr "Extrudeuse G-Code de fin"
@@ -1427,12 +1428,20 @@ msgctxt "@info"
msgid "Could not connect to the Cura Package database. Please check your connection."
msgstr "Impossible de se connecter à la base de données Cura Package. Veuillez vérifier votre connexion."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:38
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:28
msgctxt "@title:tab"
msgid "Plugins"
msgstr "Plug-ins"
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:75
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:66
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:551
+msgctxt "@title:tab"
+msgid "Materials"
+msgstr "Matériaux"
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
msgctxt "@label"
msgid "Version"
@@ -1448,8 +1457,14 @@ msgctxt "@label"
msgid "Author"
msgstr "Auteur"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:97
+msgctxt "@label"
+msgid "Downloads"
+msgstr "Téléchargements"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:116
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:158
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:258
msgctxt "@label"
msgid "Unknown"
msgstr "Inconnu"
@@ -1482,17 +1497,57 @@ msgctxt "@action:button"
msgid "Back"
msgstr "Précédent"
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:20
+msgctxt "@title:window"
+msgid "Confirm uninstall"
+msgstr "Confirmer la désinstallation"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:50
+msgctxt "@text:window"
+msgid "You are uninstalling materials and/or profiles that are still in use. Confirming will reset the following materials/profiles to their defaults."
+msgstr "Vous désinstallez des matériaux et/ou des profils qui sont encore en cours d'utilisation. La confirmation réinitialisera les matériaux / profils suivants à leurs valeurs par défaut."
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:51
+msgctxt "@text:window"
+msgid "Materials"
+msgstr "Matériaux"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:52
+msgctxt "@text:window"
+msgid "Profiles"
+msgstr "Profils"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:89
+msgctxt "@action:button"
+msgid "Confirm"
+msgstr "Confirmer"
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
msgctxt "@info"
msgid "You will need to restart Cura before changes in packages have effect."
msgstr "Vous devez redémarrer Cura pour que les changements apportés aux paquets ne prennent effet."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:34
msgctxt "@info:button"
msgid "Quit Cura"
msgstr "Quitter Cura"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Contributions"
+msgstr "Contributions de la communauté"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Plugins"
+msgstr "Plug-ins de la communauté"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:43
+msgctxt "@label"
+msgid "Generic Materials"
+msgstr "Matériaux génériques"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:56
msgctxt "@title:tab"
msgid "Installed"
msgstr "Installé"
@@ -1535,12 +1590,12 @@ msgctxt "@action:button"
msgid "Decline"
msgstr "Refuser"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:23
msgctxt "@label"
msgid "Featured"
msgstr "Fonctionnalités"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:31
msgctxt "@label"
msgid "Compatibility"
msgstr "Compatibilité"
@@ -1550,10 +1605,15 @@ msgctxt "@info"
msgid "Fetching packages..."
msgstr "Récupération des paquets..."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:88
msgctxt "@label"
-msgid "Contact"
-msgstr "Contact"
+msgid "Website"
+msgstr "Site Internet"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:94
+msgctxt "@label"
+msgid "Email"
+msgstr "E-mail"
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
msgctxt "@info:tooltip"
@@ -1566,48 +1626,88 @@ msgid "Changelog"
msgstr "Récapitulatif des changements"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:84
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:56
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:464
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:509
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:185
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:53
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:467
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:514
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:121
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:166
#: /home/ruben/Projects/Cura/resources/qml/EngineLog.qml:38
msgctxt "@action:button"
msgid "Close"
msgstr "Fermer"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:22
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:31
+msgctxt "@title"
+msgid "Update Firmware"
+msgstr "Mettre à jour le firmware"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:39
+msgctxt "@label"
+msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
+msgstr "Le firmware est le logiciel fonctionnant directement dans votre imprimante 3D. Ce firmware contrôle les moteurs pas à pas, régule la température et surtout, fait que votre machine fonctionne."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:46
+msgctxt "@label"
+msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
+msgstr "Le firmware fourni avec les nouvelles imprimantes fonctionne, mais les nouvelles versions ont tendance à fournir davantage de fonctionnalités ainsi que des améliorations."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:58
+msgctxt "@action:button"
+msgid "Automatically upgrade Firmware"
+msgstr "Mise à niveau automatique du firmware"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:69
+msgctxt "@action:button"
+msgid "Upload custom Firmware"
+msgstr "Charger le firmware personnalisé"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:83
+msgctxt "@label"
+msgid "Firmware can not be updated because there is no connection with the printer."
+msgstr "Impossible de se connecter à l'imprimante ; échec de la mise à jour du firmware."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:91
+msgctxt "@label"
+msgid "Firmware can not be updated because the connection with the printer does not support upgrading firmware."
+msgstr "Échec de la mise à jour du firmware, car cette fonctionnalité n'est pas prise en charge par la connexion avec l'imprimante."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:98
+msgctxt "@title:window"
+msgid "Select custom firmware"
+msgstr "Sélectionner le firmware personnalisé"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:119
msgctxt "@title:window"
msgid "Firmware Update"
msgstr "Mise à jour du firmware"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:42
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:143
msgctxt "@label"
msgid "Updating firmware."
msgstr "Mise à jour du firmware en cours."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:44
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:145
msgctxt "@label"
msgid "Firmware update completed."
msgstr "Mise à jour du firmware terminée."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:46
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:147
msgctxt "@label"
msgid "Firmware update failed due to an unknown error."
msgstr "Échec de la mise à jour du firmware en raison d'une erreur inconnue."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:48
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:149
msgctxt "@label"
msgid "Firmware update failed due to an communication error."
msgstr "Échec de la mise à jour du firmware en raison d'une erreur de communication."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:50
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:151
msgctxt "@label"
msgid "Firmware update failed due to an input/output error."
msgstr "Échec de la mise à jour du firmware en raison d'une erreur d'entrée/de sortie."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:52
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:153
msgctxt "@label"
msgid "Firmware update failed due to missing firmware."
msgstr "Échec de la mise à jour du firmware en raison du firmware manquant."
@@ -1617,22 +1717,22 @@ msgctxt "@title:window"
msgid "User Agreement"
msgstr "Accord utilisateur"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:57
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:46
msgctxt "@window:title"
msgid "Existing Connection"
msgstr "Connexion existante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:59
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:48
msgctxt "@message:text"
msgid "This printer/group is already added to Cura. Please select another printer/group."
msgstr "Ce groupe / cette imprimante a déjà été ajouté à Cura. Veuillez sélectionner un autre groupe / imprimante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:76
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:65
msgctxt "@title:window"
msgid "Connect to Networked Printer"
msgstr "Connecter à l'imprimante en réseau"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:86
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:75
msgctxt "@label"
msgid ""
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
@@ -1640,333 +1740,395 @@ msgid ""
"Select your printer from the list below:"
msgstr "Pour imprimer directement sur votre imprimante sur le réseau, assurez-vous que votre imprimante est connectée au réseau via un câble réseau ou en connectant votre imprimante à votre réseau Wi-Fi. Si vous ne connectez pas Cura avec votre imprimante, vous pouvez utiliser une clé USB pour transférer les fichiers g-code sur votre imprimante.\n\nSélectionnez votre imprimante dans la liste ci-dessous :"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:85
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
msgctxt "@action:button"
msgid "Add"
msgstr "Ajouter"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:95
msgctxt "@action:button"
msgid "Edit"
msgstr "Modifier"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:128
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:48
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:117
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:132
msgctxt "@action:button"
msgid "Remove"
msgstr "Supprimer"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:125
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:114
msgctxt "@action:button"
msgid "Refresh"
msgstr "Rafraîchir"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:207
msgctxt "@label"
msgid "If your printer is not listed, read the network printing troubleshooting guide"
msgstr "Si votre imprimante n'apparaît pas dans la liste, lisez le guide de dépannage de l'impression en réseau"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:245
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:234
msgctxt "@label"
msgid "Type"
msgstr "Type"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:282
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:271
msgctxt "@label"
msgid "Firmware version"
msgstr "Version du firmware"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:294
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:283
msgctxt "@label"
msgid "Address"
msgstr "Adresse"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:316
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:305
msgctxt "@label"
-msgid "This printer is not set up to host a group of Ultimaker 3 printers."
-msgstr "L'imprimante n'est pas configurée pour héberger un groupe d'imprimantes Ultimaker 3."
+msgid "This printer is not set up to host a group of printers."
+msgstr "Cette imprimante n'est pas configurée pour héberger un groupe d'imprimantes."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:320
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:309
msgctxt "@label"
-msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
-msgstr "L'imprimante est le patron pour un groupe de %1 imprimantes Ultimaker 3."
+msgid "This printer is the host for a group of %1 printers."
+msgstr "Cette imprimante est l'hôte d'un groupe d'imprimantes %1."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:330
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:319
msgctxt "@label"
msgid "The printer at this address has not yet responded."
msgstr "L'imprimante à cette adresse n'a pas encore répondu."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:335
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:39
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:324
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:42
msgctxt "@action:button"
msgid "Connect"
msgstr "Connecter"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:349
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:338
msgctxt "@title:window"
msgid "Printer Address"
msgstr "Adresse de l'imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:377
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:361
msgctxt "@alabel"
msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Saisissez l'adresse IP ou le nom d'hôte de votre imprimante sur le réseau."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:407
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:390
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:132
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
msgstr "OK"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:30
-msgctxt "@title:window"
-msgid "Print over network"
-msgstr "Imprimer sur le réseau"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:61
-msgctxt "@label"
-msgid "Printer selection"
-msgstr "Sélection d'imprimantes"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:100
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:44
msgctxt "@action:button"
msgid "Print"
msgstr "Imprimer"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:36
-msgctxt "@label: arg 1 is group name"
-msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
-msgstr "%1 n'est pas configurée pour héberger un groupe d'imprimantes connectées Ultimaker 3."
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:47
+msgctxt "@title:window"
+msgid "Print over network"
+msgstr "Imprimer sur le réseau"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
-msgctxt "@label link to connect manager"
-msgid "Add/Remove printers"
-msgstr "Ajouter / supprimer une imprimante"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:79
+msgctxt "@label"
+msgid "Printer selection"
+msgstr "Sélection d'imprimantes"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
-msgctxt "@info:tooltip"
-msgid "Opens the print jobs page with your default web browser."
-msgstr "Ouvre la page des tâches d'impression avec votre navigateur web."
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:173
+msgctxt "@label"
+msgid "Not available"
+msgstr "Non disponible"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:15
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:130
-msgctxt "@action:button"
-msgid "View print jobs"
-msgstr "Afficher les tâches d'impression"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:175
+msgctxt "@label"
+msgid "Unreachable"
+msgstr "Injoignable"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:37
-msgctxt "@label:status"
-msgid "Preparing to print"
-msgstr "Préparation..."
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:39
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:263
-msgctxt "@label:status"
-msgid "Printing"
-msgstr "Impression..."
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:41
-msgctxt "@label:status"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:180
+msgctxt "@label"
msgid "Available"
msgstr "Disponible"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:37
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:44
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:46
msgctxt "@label:status"
-msgid "Lost connection with the printer"
-msgstr "Connexion avec l'imprimante perdue"
+msgid "Aborted"
+msgstr "Abandonné"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:45
-msgctxt "@label:status"
-msgid "Unavailable"
-msgstr "Indisponible"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label:status"
-msgid "Unknown"
-msgstr "Inconnu"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:249
-msgctxt "@label:status"
-msgid "Disabled"
-msgstr "Désactivé"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:265
-msgctxt "@label:status"
-msgid "Reserved"
-msgstr "Réservée"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:268
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:39
msgctxt "@label:status"
msgid "Finished"
msgstr "Terminé"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:271
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:392
-msgctxt "@label"
-msgid "Preparing to print"
-msgstr "Préparation de l'impression..."
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:273
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:42
msgctxt "@label:status"
-msgid "Action required"
-msgstr "Action requise"
+msgid "Preparing"
+msgstr "Préparation"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:276
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:48
msgctxt "@label:status"
-msgid "Paused"
-msgstr "En pause"
+msgid "Pausing"
+msgstr "Mise en pause"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:52
msgctxt "@label:status"
msgid "Resuming"
msgstr "Reprise"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:280
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:54
msgctxt "@label:status"
-msgid "Print aborted"
-msgstr "Abandon de l'impression"
+msgid "Action required"
+msgstr "Action requise"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:373
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:394
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:213
msgctxt "@label"
-msgid "Not accepting print jobs"
-msgstr "Non acceptation des tâches d'impression"
+msgid "Waiting for: Unavailable printer"
+msgstr "En attente : imprimante non disponible"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:387
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:215
msgctxt "@label"
-msgid "Finishes at: "
-msgstr "Complète a: "
+msgid "Waiting for: First available"
+msgstr "En attente : première imprimante disponible"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:389
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:217
msgctxt "@label"
-msgid "Clear build plate"
-msgstr "Enlever les objets du plateau"
+msgid "Waiting for: "
+msgstr "En attente : "
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:396
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:299
msgctxt "@label"
-msgid "Waiting for configuration change"
-msgstr "En attente de modification de configuration"
+msgid "Configuration change"
+msgstr "Modification des configurations"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:63
-msgctxt "@title"
-msgid "Print jobs"
-msgstr "Imprimer les tâches"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:93
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:365
msgctxt "@label"
-msgid "Printing"
-msgstr "Impression..."
+msgid "The assigned printer, %1, requires the following configuration change(s):"
+msgstr "L'imprimante assignée, %1, nécessite d'apporter la ou les modifications suivantes à la configuration :"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:111
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:367
+msgctxt "@label"
+msgid "The printer %1 is assigned, but the job contains an unknown material configuration."
+msgstr "L'imprimante %1 est assignée, mais le projet contient une configuration matérielle inconnue."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:375
+msgctxt "@label"
+msgid "Change material %1 from %2 to %3."
+msgstr "Changer le matériau %1 de %2 à %3."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:378
+msgctxt "@label"
+msgid "Load %3 as material %1 (This cannot be overridden)."
+msgstr "Charger %3 comme matériau %1 (Ceci ne peut pas être remplacé)."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:381
+msgctxt "@label"
+msgid "Change print core %1 from %2 to %3."
+msgstr "Changer le print core %1 de %2 à %3."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:384
+msgctxt "@label"
+msgid "Change build plate to %1 (This cannot be overridden)."
+msgstr "Changer le plateau en %1 (Ceci ne peut pas être remplacé)."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:404
+msgctxt "@label"
+msgid "Override"
+msgstr "Remplacer"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:432
+msgctxt "@label"
+msgid "Starting a print job with an incompatible configuration could damage your 3D printer. Are you sure you want to override the configuration and print %1?"
+msgstr "Le fait de démarrer un travail d'impression avec une configuration incompatible peut endommager votre imprimante 3D. Êtes-vous sûr de vouloir remplacer la configuration et imprimer %1 ?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:435
+msgctxt "@window:title"
+msgid "Override configuration configuration and start print"
+msgstr "Remplacer la configuration et lancer l'impression"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:466
+msgctxt "@label"
+msgid "Glass"
+msgstr "Verre"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:469
+msgctxt "@label"
+msgid "Aluminum"
+msgstr "Aluminium"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:39
+msgctxt "@label link to connect manager"
+msgid "Manage queue"
+msgstr "Gérer la file d'attente"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:60
msgctxt "@label"
msgid "Queued"
msgstr "Mis en file d'attente"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:170
-msgctxt "@label:title"
-msgid "Printers"
-msgstr "Imprimantes"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:36
+msgctxt "@label"
+msgid "Printing"
+msgstr "Impression"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:224
-msgctxt "@action:button"
-msgid "View printers"
-msgstr "Afficher les imprimantes"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:49
+msgctxt "@label link to connect manager"
+msgid "Manage printers"
+msgstr "Gérer les imprimantes"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:38
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:115
+msgctxt "@label"
+msgid "Move to top"
+msgstr "Déplacer l'impression en haut"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:124
+msgctxt "@label"
+msgid "Delete"
+msgstr "Effacer"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
+msgctxt "@label"
+msgid "Resume"
+msgstr "Reprendre"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
+msgctxt "@label"
+msgid "Pause"
+msgstr "Pause"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:146
+msgctxt "@label"
+msgid "Abort"
+msgstr "Abandonner"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:178
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to move %1 to the top of the queue?"
+msgstr "Êtes-vous sûr de vouloir déplacer %1 en haut de la file d'attente ?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:179
+msgctxt "@window:title"
+msgid "Move print job to top"
+msgstr "Déplacer l'impression en haut de la file d'attente"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:188
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to delete %1?"
+msgstr "Êtes-vous sûr de vouloir supprimer %1 ?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:189
+msgctxt "@window:title"
+msgid "Delete print job"
+msgstr "Supprimer l'impression"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:198
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to abort %1?"
+msgstr "Êtes-vous sûr de vouloir annuler %1 ?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
+msgctxt "@window:title"
+msgid "Abort print"
+msgstr "Abandonner l'impression"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:43
msgctxt "@info:tooltip"
msgid "Connect to a printer"
msgstr "Connecter à une imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:117
-msgctxt "@info:tooltip"
-msgid "Load the configuration of the printer into Cura"
-msgstr "Charger la configuration de l'imprimante dans Cura"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:118
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:121
msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Activer la configuration"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:122
+msgctxt "@info:tooltip"
+msgid "Load the configuration of the printer into Cura"
+msgstr "Charger la configuration de l'imprimante dans Cura"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:130
msgctxt "@label"
msgid "Color scheme"
msgstr "Modèle de couleurs"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:145
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Couleur du matériau"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:149
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Type de ligne"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:153
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr "Taux d'alimentation"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:157
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr "Épaisseur de la couche"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:198
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Mode de compatibilité"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:284
msgctxt "@label"
msgid "Show Travels"
msgstr "Afficher les déplacements"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:290
msgctxt "@label"
msgid "Show Helpers"
msgstr "Afficher les aides"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:296
msgctxt "@label"
msgid "Show Shell"
msgstr "Afficher la coque"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:302
msgctxt "@label"
msgid "Show Infill"
msgstr "Afficher le remplissage"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:355
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Afficher uniquement les couches supérieures"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:366
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "Afficher 5 niveaux détaillés en haut"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:379
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Haut / bas"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:383
msgctxt "@label"
msgid "Inner Wall"
msgstr "Paroi interne"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:448
msgctxt "@label"
msgid "min"
msgstr "min."
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:500
msgctxt "@label"
msgid "max"
msgstr "max."
@@ -1981,17 +2143,17 @@ msgctxt "@label"
msgid "Post Processing Scripts"
msgstr "Scripts de post-traitement"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:225
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:227
msgctxt "@action"
msgid "Add a script"
msgstr "Ajouter un script"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:271
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:273
msgctxt "@label"
msgid "Settings"
msgstr "Paramètres"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:474
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:477
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "Modifier les scripts de post-traitement actifs"
@@ -2086,53 +2248,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Lissage"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:37
msgctxt "@label"
msgid "Mesh Type"
msgstr "Type de maille"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:68
msgctxt "@label"
msgid "Normal model"
msgstr "Modèle normal"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:75
msgctxt "@label"
msgid "Print as support"
msgstr "Imprimer comme support"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:83
msgctxt "@label"
msgid "Don't support overlap with other models"
msgstr "Ne pas prendre en charge le chevauchement avec d'autres modèles"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:91
msgctxt "@label"
msgid "Modify settings for overlap with other models"
msgstr "Modifier les paramètres de chevauchement avec d'autres modèles"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:99
msgctxt "@label"
msgid "Modify settings for infill of other models"
msgstr "Modifier les paramètres de remplissage d'autres modèles"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:342
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:347
msgctxt "@action:button"
msgid "Select settings"
msgstr "Sélectionner les paramètres"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:384
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:389
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Sélectionner les paramètres pour personnaliser ce modèle"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:432
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:437
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:98
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Filtrer..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:446
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:451
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Afficher tout"
@@ -2154,13 +2316,13 @@ msgid "Create new"
msgstr "Créer"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:70
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:68
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:72
msgctxt "@action:title"
msgid "Summary - Cura Project"
msgstr "Résumé - Projet Cura"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:92
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:96
msgctxt "@action:label"
msgid "Printer settings"
msgstr "Paramètres de l'imprimante"
@@ -2177,18 +2339,19 @@ msgid "Update"
msgstr "Mise à jour"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:143
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:105
msgctxt "@action:label"
msgid "Type"
msgstr "Type"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:159
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
msgctxt "@action:label"
msgid "Printer Group"
msgstr "Groupe d'imprimantes"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:180
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:192
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:196
msgctxt "@action:label"
msgid "Profile settings"
msgstr "Paramètres de profil"
@@ -2200,19 +2363,20 @@ msgstr "Comment le conflit du profil doit-il être résolu ?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:216
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:308
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:216
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:220
msgctxt "@action:label"
msgid "Name"
msgstr "Nom"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:231
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:200
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:204
msgctxt "@action:label"
msgid "Not in profile"
msgstr "Absent du profil"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:236
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:209
msgctxt "@action:label"
msgid "%1 override"
msgid_plural "%1 overrides"
@@ -2242,7 +2406,7 @@ msgid "How should the conflict in the material be resolved?"
msgstr "Comment le conflit du matériau doit-il être résolu ?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:327
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:233
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:237
msgctxt "@action:label"
msgid "Setting visibility"
msgstr "Visibilité des paramètres"
@@ -2253,13 +2417,13 @@ msgid "Mode"
msgstr "Mode"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:352
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:246
msgctxt "@action:label"
msgid "Visible settings:"
msgstr "Paramètres visibles :"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:357
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:247
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:251
msgctxt "@action:label"
msgid "%1 out of %2"
msgstr "%1 sur %2"
@@ -2315,36 +2479,6 @@ msgctxt "@action:button"
msgid "Move to Next Position"
msgstr "Aller à la position suivante"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:30
-msgctxt "@title"
-msgid "Upgrade Firmware"
-msgstr "Mise à niveau du firmware"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:41
-msgctxt "@label"
-msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
-msgstr "Le firmware est le logiciel fonctionnant directement dans votre imprimante 3D. Ce firmware contrôle les moteurs pas à pas, régule la température et surtout, fait que votre machine fonctionne."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:51
-msgctxt "@label"
-msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
-msgstr "Le firmware fourni avec les nouvelles imprimantes fonctionne, mais les nouvelles versions ont tendance à fournir davantage de fonctionnalités ainsi que des améliorations."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:65
-msgctxt "@action:button"
-msgid "Automatically upgrade Firmware"
-msgstr "Mise à niveau automatique du firmware"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:75
-msgctxt "@action:button"
-msgid "Upload custom Firmware"
-msgstr "Charger le firmware personnalisé"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:87
-msgctxt "@title:window"
-msgid "Select custom firmware"
-msgstr "Sélectionner le firmware personnalisé"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:37
msgctxt "@label"
msgid "Please select any upgrades made to this Ultimaker Original"
@@ -2492,27 +2626,11 @@ msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr "Supprimez l'imprimante"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
-msgctxt "@label:"
-msgid "Pause"
-msgstr "Pause"
-
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
-msgctxt "@label:"
-msgid "Resume"
-msgstr "Reprendre"
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:325
-msgctxt "@label:"
+msgctxt "@label"
msgid "Abort Print"
msgstr "Abandonner l'impression"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
-msgctxt "@window:title"
-msgid "Abort print"
-msgstr "Abandonner l'impression"
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:337
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
@@ -2546,19 +2664,17 @@ msgid "Customized"
msgstr "Personnalisé"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:634
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:637
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Toujours me demander"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:635
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Annuler et ne plus me demander"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Conserver et ne plus me demander"
@@ -2578,101 +2694,179 @@ msgctxt "@action:button"
msgid "Create New Profile"
msgstr "Créer un nouveau profil"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:65
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:71
msgctxt "@title"
msgid "Information"
msgstr "Informations"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:94
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:100
msgctxt "@title:window"
msgid "Confirm Diameter Change"
msgstr "Confirmer le changement de diamètre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:101
msgctxt "@label (%1 is a number)"
msgid "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?"
msgstr "Le nouveau diamètre de filament est réglé sur %1 mm, ce qui n'est pas compatible avec l'extrudeuse actuelle. Souhaitez-vous poursuivre ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:133
msgctxt "@label"
msgid "Display Name"
msgstr "Afficher le nom"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:143
msgctxt "@label"
msgid "Brand"
msgstr "Marque"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:153
msgctxt "@label"
msgid "Material Type"
msgstr "Type de matériau"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:162
msgctxt "@label"
msgid "Color"
msgstr "Couleur"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:212
msgctxt "@label"
msgid "Properties"
msgstr "Propriétés"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:214
msgctxt "@label"
msgid "Density"
msgstr "Densité"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:229
msgctxt "@label"
msgid "Diameter"
msgstr "Diamètre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:253
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:263
msgctxt "@label"
msgid "Filament Cost"
msgstr "Coût du filament"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:269
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:280
msgctxt "@label"
msgid "Filament weight"
msgstr "Poids du filament"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:298
msgctxt "@label"
msgid "Filament length"
msgstr "Longueur du filament"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:295
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:307
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Coût au mètre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:309
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:321
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "Ce matériau est lié à %1 et partage certaines de ses propriétés."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:316
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:328
msgctxt "@label"
msgid "Unlink Material"
msgstr "Délier le matériau"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:327
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:339
msgctxt "@label"
msgid "Description"
msgstr "Description"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:352
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Informations d'adhérence"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:366
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:378
msgctxt "@label"
msgid "Print settings"
msgstr "Paramètres d'impression"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:84
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
+msgctxt "@action:button"
+msgid "Activate"
+msgstr "Activer"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:101
+msgctxt "@action:button"
+msgid "Create"
+msgstr "Créer"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:114
+msgctxt "@action:button"
+msgid "Duplicate"
+msgstr "Dupliquer"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
+msgctxt "@action:button"
+msgid "Import"
+msgstr "Importer"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
+msgctxt "@action:button"
+msgid "Export"
+msgstr "Exporter"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:203
+msgctxt "@action:label"
+msgid "Printer"
+msgstr "Imprimante"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:262
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
+msgctxt "@title:window"
+msgid "Confirm Remove"
+msgstr "Confirmer la suppression"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:263
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
+msgctxt "@label (%1 is object name)"
+msgid "Are you sure you wish to remove %1? This cannot be undone!"
+msgstr "Êtes-vous sûr de vouloir supprimer l'objet %1 ? Vous ne pourrez pas revenir en arrière !"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:285
+msgctxt "@title:window"
+msgid "Import Material"
+msgstr "Importer un matériau"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:286
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Could not import material %1: %2"
+msgstr "Impossible d'importer le matériau %1 : %2"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:290
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully imported material %1"
+msgstr "Matériau %1 importé avec succès"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:308
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:316
+msgctxt "@title:window"
+msgid "Export Material"
+msgstr "Exporter un matériau"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:320
+msgctxt "@info:status Don't translate the XML tags and !"
+msgid "Failed to export material to %1: %2"
+msgstr "Échec de l'exportation de matériau vers %1 : %2"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:326
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully exported material to %1"
+msgstr "Matériau exporté avec succès vers %1"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:14
msgctxt "@title:tab"
msgid "Setting Visibility"
@@ -2709,289 +2903,287 @@ msgid "Unit"
msgstr "Unité"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:544
msgctxt "@title:tab"
msgid "General"
msgstr "Général"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:142
msgctxt "@label"
msgid "Interface"
msgstr "Interface"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:153
msgctxt "@label"
msgid "Language:"
msgstr "Langue :"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:220
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Currency:"
msgstr "Devise :"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:235
msgctxt "@label"
msgid "Theme:"
msgstr "Thème :"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:294
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:292
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Vous devez redémarrer l'application pour que ces changements prennent effet."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:311
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:309
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Découper automatiquement si les paramètres sont modifiés."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:317
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Découper automatiquement"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:333
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:331
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Comportement Viewport"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:339
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Surligne les parties non supportées du modèle en rouge. Sans ajouter de support, ces zones ne s'imprimeront pas correctement."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:348
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Mettre en surbrillance les porte-à-faux"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:357
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:355
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
-msgstr "Déplace la caméra afin que le modèle sélectionné se trouve au centre de la vue."
+msgstr "Déplace la caméra afin que le modèle sélectionné se trouve au centre de la vue"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:360
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "Centrer la caméra lorsqu'un élément est sélectionné"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:371
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:369
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "Le comportement de zoom par défaut de Cura doit-il être inversé ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:376
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:374
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "Inverser la direction du zoom de la caméra."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "Le zoom doit-il se faire dans la direction de la souris ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "Zoomer vers la direction de la souris"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:401
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "Les modèles dans la zone d'impression doivent-ils être déplacés afin de ne plus se croiser ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:406
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "Veillez à ce que les modèles restent séparés"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:413
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "Les modèles dans la zone d'impression doivent-ils être abaissés afin de toucher le plateau ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:420
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:418
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Abaisser automatiquement les modèles sur le plateau"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
msgctxt "@info:tooltip"
msgid "Show caution message in g-code reader."
msgstr "Afficher le message d'avertissement dans le lecteur G-Code."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:439
msgctxt "@option:check"
msgid "Caution message in g-code reader"
msgstr "Message d'avertissement dans le lecteur G-Code"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:449
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:447
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "La couche doit-elle être forcée en mode de compatibilité ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:452
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "Forcer l'affichage de la couche en mode de compatibilité (redémarrage requis)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:470
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:468
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Ouvrir et enregistrer des fichiers"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:477
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:475
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "Les modèles doivent-ils être mis à l'échelle du volume d'impression s'ils sont trop grands ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:482
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:480
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Réduire la taille des modèles trop grands"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Un modèle peut apparaître en tout petit si son unité est par exemple en mètres plutôt qu'en millimètres. Ces modèles doivent-ils être agrandis ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:497
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:495
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Mettre à l'échelle les modèles extrêmement petits"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@info:tooltip"
msgid "Should models be selected after they are loaded?"
msgstr "Les modèles doivent-ils être sélectionnés après leur chargement ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:510
msgctxt "@option:check"
msgid "Select models when loaded"
msgstr "Sélectionner les modèles lorsqu'ils sont chargés"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "Un préfixe basé sur le nom de l'imprimante doit-il être automatiquement ajouté au nom de la tâche d'impression ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:527
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:525
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "Ajouter le préfixe de la machine au nom de la tâche"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "Un résumé doit-il être affiché lors de l'enregistrement d'un fichier de projet ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:541
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:539
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Afficher la boîte de dialogue du résumé lors de l'enregistrement du projet"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:551
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:549
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Comportement par défaut lors de l'ouverture d'un fichier de projet"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:559
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:557
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Comportement par défaut lors de l'ouverture d'un fichier de projet : "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
msgctxt "@option:openProject"
-msgid "Always ask"
-msgstr "Toujours demander"
+msgid "Always ask me this"
+msgstr "Toujours me demander"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:574
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:572
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Toujours ouvrir comme projet"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:575
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Toujours importer les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:611
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:609
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Lorsque vous apportez des modifications à un profil puis passez à un autre profil, une boîte de dialogue apparaît, vous demandant si vous souhaitez conserver les modifications. Vous pouvez aussi choisir une option par défaut, et le dialogue ne s'affichera plus."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:618
msgctxt "@label"
-msgid "Override Profile"
-msgstr "Écraser le profil"
+msgid "Profiles"
+msgstr "Profils"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:670
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:623
+msgctxt "@window:text"
+msgid "Default behavior for changed setting values when switching to a different profile: "
+msgstr "Comportement par défaut pour les valeurs de paramètres modifiées lors du passage à un profil différent : "
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
+msgctxt "@option:discardOrKeep"
+msgid "Always discard changed settings"
+msgstr "Toujours rejeter les paramètres modifiés"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:639
+msgctxt "@option:discardOrKeep"
+msgid "Always transfer changed settings to new profile"
+msgstr "Toujours transférer les paramètres modifiés dans le nouveau profil"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:673
msgctxt "@label"
msgid "Privacy"
msgstr "Confidentialité"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:678
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:681
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "Cura doit-il vérifier les mises à jour au démarrage du programme ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:683
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:686
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Vérifier les mises à jour au démarrage"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:697
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "Les données anonymes de votre impression doivent-elles être envoyées à Ultimaker ? Notez qu'aucun modèle, aucune adresse IP ni aucune autre information permettant de vous identifier personnellement ne seront envoyés ou stockés."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:702
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "Envoyer des informations (anonymes) sur l'impression"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:711
msgctxt "@action:button"
msgid "More information"
msgstr "Plus d'informations"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:729
msgctxt "@label"
msgid "Experimental"
msgstr "Expérimental"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:733
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:736
msgctxt "@info:tooltip"
msgid "Use multi build plate functionality"
msgstr "Utiliser la fonctionnalité multi-plateau"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:738
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:741
msgctxt "@option:check"
msgid "Use multi build plate functionality (restart required)"
msgstr "Utiliser la fonctionnalité multi-plateau (redémarrage requis)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:748
-msgctxt "@info:tooltip"
-msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
-msgstr "Les modèles nouvellement chargés doivent-ils être disposés sur le plateau ? Utilisé en conjonction avec le multi-plateau (EXPÉRIMENTAL)"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:753
-msgctxt "@option:check"
-msgid "Do not arrange objects on load"
-msgstr "Ne pas réorganiser les objets lors du chargement"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:16
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:536
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:549
msgctxt "@title:tab"
msgid "Printers"
msgstr "Imprimantes"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:90
-msgctxt "@action:button"
-msgid "Activate"
-msgstr "Activer"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:55
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:129
msgctxt "@action:button"
@@ -3009,7 +3201,7 @@ msgid "Connection:"
msgstr "Connexion :"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:162
-#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:47
+#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:55
msgctxt "@info:status"
msgid "The printer is not connected."
msgstr "L'imprimante n'est pas connectée."
@@ -3035,7 +3227,7 @@ msgid "Aborting print..."
msgstr "Abandon de l'impression..."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:540
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:553
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Profils"
@@ -3050,18 +3242,6 @@ msgctxt "@label"
msgid "Duplicate"
msgstr "Dupliquer"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:145
-msgctxt "@action:button"
-msgid "Import"
-msgstr "Importer"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:158
-msgctxt "@action:button"
-msgid "Export"
-msgstr "Exporter"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:174
msgctxt "@title:window"
msgid "Create Profile"
@@ -3072,18 +3252,6 @@ msgctxt "@title:window"
msgid "Duplicate Profile"
msgstr "Dupliquer un profil"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:221
-msgctxt "@title:window"
-msgid "Confirm Remove"
-msgstr "Confirmer la suppression"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:222
-msgctxt "@label (%1 is object name)"
-msgid "Are you sure you wish to remove %1? This cannot be undone!"
-msgstr "Êtes-vous sûr de vouloir supprimer l'objet %1 ? Vous ne pourrez pas revenir en arrière !"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:256
msgctxt "@title:window"
msgid "Rename Profile"
@@ -3104,228 +3272,200 @@ msgctxt "@label %1 is printer name"
msgid "Printer: %1"
msgstr "Imprimante : %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Protected profiles"
msgstr "Profils protégés"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Custom profiles"
msgstr "Personnaliser les profils"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:468
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:480
msgctxt "@action:button"
msgid "Update profile with current settings/overrides"
msgstr "Mettre à jour le profil à l'aide des paramètres / forçages actuels"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:475
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:487
msgctxt "@action:button"
msgid "Discard current changes"
msgstr "Ignorer les modifications actuelles"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:504
msgctxt "@action:label"
msgid "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below."
msgstr "Ce profil utilise les paramètres par défaut spécifiés par l'imprimante, de sorte qu'aucun paramètre / forçage n'apparaît dans la liste ci-dessous."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:511
msgctxt "@action:label"
msgid "Your current settings match the selected profile."
msgstr "Vos paramètres actuels correspondent au profil sélectionné."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:518
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:530
msgctxt "@title:tab"
msgid "Global Settings"
msgstr "Paramètres généraux"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:40
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:538
-msgctxt "@title:tab"
-msgid "Materials"
-msgstr "Matériaux"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:105
-msgctxt "@action:button"
-msgid "Create"
-msgstr "Créer"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:118
-msgctxt "@action:button"
-msgid "Duplicate"
-msgstr "Dupliquer"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:235
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:243
-msgctxt "@title:window"
-msgid "Import Material"
-msgstr "Importer un matériau"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:244
-msgctxt "@info:status Don't translate the XML tags or !"
-msgid "Could not import material %1: %2"
-msgstr "Impossible d'importer le matériau %1 : %2"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:248
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully imported material %1"
-msgstr "Matériau %1 importé avec succès"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:266
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:274
-msgctxt "@title:window"
-msgid "Export Material"
-msgstr "Exporter un matériau"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:278
-msgctxt "@info:status Don't translate the XML tags and !"
-msgid "Failed to export material to %1: %2"
-msgstr "Échec de l'exportation de matériau vers %1 : %2"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:284
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully exported material to %1"
-msgstr "Matériau exporté avec succès vers %1"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:337
-msgctxt "@action:label"
-msgid "Printer"
-msgstr "Imprimante"
-
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:896
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:953
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Ajouter une imprimante"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:195
msgctxt "@label"
msgid "Printer Name:"
msgstr "Nom de l'imprimante :"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:219
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Ajouter une imprimante"
+#: /home/ruben/Projects/Cura/resources/qml/JobSpecs.qml:84
+msgctxt "@text Print job name"
+msgid "Untitled"
+msgstr "Sans titre"
+
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:15
msgctxt "@title:window"
msgid "About Cura"
msgstr "À propos de Cura"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:55
msgctxt "@label"
msgid "version: %1"
msgstr "version : %1"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:56
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
msgctxt "@label"
msgid "End-to-end solution for fused filament 3D printing."
msgstr "Solution complète pour l'impression 3D par dépôt de filament fondu."
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:82
msgctxt "@info:credit"
msgid ""
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
"Cura proudly uses the following open source projects:"
msgstr "Cura a été développé par Ultimaker B.V. en coopération avec la communauté Ultimaker.\nCura est fier d'utiliser les projets open source suivants :"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
msgctxt "@label"
msgid "Graphical user interface"
msgstr "Interface utilisateur graphique"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
msgctxt "@label"
msgid "Application framework"
msgstr "Cadre d'application"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
msgctxt "@label"
msgid "G-code generator"
msgstr "Générateur G-Code"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
msgctxt "@label"
msgid "Interprocess communication library"
msgstr "Bibliothèque de communication interprocess"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
msgctxt "@label"
msgid "Programming language"
msgstr "Langage de programmation"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:124
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
msgctxt "@label"
msgid "GUI framework"
msgstr "Cadre IUG"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:125
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
msgctxt "@label"
msgid "GUI framework bindings"
msgstr "Liens cadre IUG"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:126
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:140
msgctxt "@label"
msgid "C/C++ Binding library"
msgstr "Bibliothèque C/C++ Binding"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:141
msgctxt "@label"
msgid "Data interchange format"
msgstr "Format d'échange de données"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:142
msgctxt "@label"
msgid "Support library for scientific computing"
msgstr "Prise en charge de la bibliothèque pour le calcul scientifique"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:129
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:143
msgctxt "@label"
msgid "Support library for faster math"
msgstr "Prise en charge de la bibliothèque pour des maths plus rapides"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:130
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:144
msgctxt "@label"
msgid "Support library for handling STL files"
msgstr "Prise en charge de la bibliothèque pour le traitement des fichiers STL"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:145
+msgctxt "@label"
+msgid "Support library for handling planar objects"
+msgstr "Prise en charge de la bibliothèque pour le traitement des objets planaires"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:146
+msgctxt "@label"
+msgid "Support library for handling triangular meshes"
+msgstr "Prise en charge de la bibliothèque pour le traitement des mailles triangulaires"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
+msgctxt "@label"
+msgid "Support library for analysis of complex networks"
+msgstr "Prise en charge de la bibliothèque pour l'analyse de réseaux complexes"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
msgctxt "@label"
msgid "Support library for handling 3MF files"
msgstr "Prise en charge de la bibliothèque pour le traitement des fichiers 3MF"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:149
+msgctxt "@label"
+msgid "Support library for file metadata and streaming"
+msgstr "Prise en charge de la bibliothèque pour les métadonnées et le streaming de fichiers"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:150
msgctxt "@label"
msgid "Serial communication library"
msgstr "Bibliothèque de communication série"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:151
msgctxt "@label"
msgid "ZeroConf discovery library"
msgstr "Bibliothèque de découverte ZeroConf"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:152
msgctxt "@label"
msgid "Polygon clipping library"
msgstr "Bibliothèque de découpe polygone"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:153
msgctxt "@Label"
msgid "Python HTTP library"
msgstr "Bibliothèque Python HTTP"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:155
msgctxt "@label"
msgid "Font"
msgstr "Police"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:156
msgctxt "@label"
msgid "SVG icons"
msgstr "Icônes SVG"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:157
msgctxt "@label"
msgid "Linux cross-distribution application deployment"
msgstr "Déploiement d'applications sur multiples distributions Linux"
@@ -3335,7 +3475,7 @@ msgctxt "@label"
msgid "Profile:"
msgstr "Profil :"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
@@ -3343,53 +3483,53 @@ msgid ""
"Click to open the profile manager."
msgstr "Certaines valeurs de paramètre / forçage sont différentes des valeurs enregistrées dans le profil. \n\nCliquez pour ouvrir le gestionnaire de profils."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:200
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Rechercher..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:544
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:545
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Copier la valeur vers tous les extrudeurs"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:553
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:554
msgctxt "@action:menu"
msgid "Copy all changed values to all extruders"
msgstr "Copier toutes les valeurs modifiées vers toutes les extrudeuses"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:568
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:591
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Masquer ce paramètre"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:609
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "Masquer ce paramètre"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:590
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:613
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Afficher ce paramètre"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:614
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:426
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:637
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Configurer la visibilité des paramètres..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:644
msgctxt "@action:inmenu"
msgid "Collapse All"
msgstr "Réduire tout"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:649
msgctxt "@action:inmenu"
msgid "Expand All"
msgstr "Développer tout"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:253
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
@@ -3407,17 +3547,17 @@ msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Touché par"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:155
msgctxt "@label"
msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
msgstr "Ce paramètre est toujours partagé par toutes les extrudeuses. Le modifier ici entraînera la modification de la valeur pour toutes les extrudeuses."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:158
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "La valeur est résolue à partir des valeurs par extrudeur "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:189
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
@@ -3425,7 +3565,7 @@ msgid ""
"Click to restore the value of the profile."
msgstr "Ce paramètre possède une valeur qui est différente du profil.\n\nCliquez pour restaurer la valeur du profil."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:281
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
@@ -3469,7 +3609,7 @@ msgid "Send a custom G-code command to the connected printer. Press 'enter' to s
msgstr "Envoyer une commande G-Code personnalisée à l'imprimante connectée. Appuyez sur « Entrée » pour envoyer la commande."
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ExtruderBox.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:268
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:272
msgctxt "@label"
msgid "Extruder"
msgstr "Extrudeuse"
@@ -3522,7 +3662,7 @@ msgid "The nozzle inserted in this extruder."
msgstr "Buse insérée dans cet extrudeur."
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/HeatedBedBox.qml:25
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:489
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:493
msgctxt "@label"
msgid "Build plate"
msgstr "Plateau"
@@ -3547,6 +3687,21 @@ msgctxt "@tooltip of pre-heat"
msgid "Heat the bed in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the bed to heat up when you're ready to print."
msgstr "Préchauffez le plateau avant l'impression. Vous pouvez continuer à ajuster votre impression pendant qu'il chauffe, et vous n'aurez pas à attendre que le plateau chauffe lorsque vous serez prêt à lancer l'impression."
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:13
+msgctxt "@label:category menu label"
+msgid "Material"
+msgstr "Matériau"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:37
+msgctxt "@label:category menu label"
+msgid "Favorites"
+msgstr "Favoris"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:61
+msgctxt "@label:category menu label"
+msgid "Generic"
+msgstr "Générique"
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/PrinterMenu.qml:25
msgctxt "@label:category menu label"
msgid "Network enabled printers"
@@ -3562,12 +3717,12 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Visualisation"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:39
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:42
msgctxt "@action:inmenu menubar:view"
msgid "&Camera position"
msgstr "Position de la &caméra"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:58
msgctxt "@action:inmenu menubar:view"
msgid "&Build plate"
msgstr "&Plateau"
@@ -3577,15 +3732,15 @@ msgctxt "@action:inmenu"
msgid "Visible Settings"
msgstr "Paramètres visibles"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:42
msgctxt "@action:inmenu"
msgid "Show All Settings"
msgstr "Afficher tous les paramètres"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:53
msgctxt "@action:inmenu"
msgid "Manage Setting Visibility..."
-msgstr "Gérer la visibilité des paramètres"
+msgstr "Gérer la visibilité des paramètres..."
#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:27
msgctxt "@label"
@@ -3609,7 +3764,7 @@ msgstr "Nombre de copies"
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml:33
msgctxt "@label:header configurations"
msgid "Available configurations"
-msgstr "Configurations disponibles :"
+msgstr "Configurations disponibles"
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/PrintCoreConfiguration.qml:28
msgctxt "@label:extruder label"
@@ -3643,347 +3798,346 @@ msgid ""
"G-code files cannot be modified"
msgstr "Configuration de l'impression désactivée\nLes fichiers G-Code ne peuvent pas être modifiés"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:340
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr "00h 00min"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:359
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:358
msgctxt "@tooltip"
msgid "Time specification"
msgstr "Spécification de temps"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:440
msgctxt "@label"
msgid "Cost specification"
msgstr "Spécification de coût"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
msgctxt "@label m for meter"
msgid "%1m"
msgstr "%1m"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:447
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:456
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
msgctxt "@label g for grams"
msgid "%1g"
msgstr "%1g"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:453
msgctxt "@label"
msgid "Total:"
msgstr "Total :"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:577
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Configuration de l'impression recommandée
Imprimer avec les paramètres recommandés pour l'imprimante, le matériau et la qualité sélectionnés."
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:582
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "Configuration de l'impression personnalisée
Imprimer avec un contrôle fin de chaque élément du processus de découpe."
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:106
msgctxt "@label"
msgid "Active print"
msgstr "Activer l'impression"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:115
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:114
msgctxt "@label"
msgid "Job Name"
msgstr "Nom de la tâche"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:122
msgctxt "@label"
msgid "Printing Time"
msgstr "Durée d'impression"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:130
msgctxt "@label"
msgid "Estimated time left"
msgstr "Durée restante estimée"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:78
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
-msgid "Toggle Fu&ll Screen"
-msgstr "Passer en P&lein écran"
+msgid "Toggle Full Screen"
+msgstr "Passer en Plein écran"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:85
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "&Annuler"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "&Rétablir"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "&Quitter"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:113
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
msgctxt "@action:inmenu menubar:view"
-msgid "&3D View"
-msgstr "Vue &3D"
+msgid "3D View"
+msgstr "Vue 3D"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
msgctxt "@action:inmenu menubar:view"
-msgid "&Front View"
-msgstr "Vue de &face"
+msgid "Front View"
+msgstr "Vue de face"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
msgctxt "@action:inmenu menubar:view"
-msgid "&Top View"
-msgstr "Vue du dess&us"
+msgid "Top View"
+msgstr "Vue du dessus"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
msgctxt "@action:inmenu menubar:view"
-msgid "&Left Side View"
-msgstr "Vue latérale &gauche"
+msgid "Left Side View"
+msgstr "Vue latérale gauche"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
msgctxt "@action:inmenu menubar:view"
-msgid "&Right Side View"
-msgstr "Vue latérale &droite"
+msgid "Right Side View"
+msgstr "Vue latérale droite"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Configurer Cura..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:155
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "&Ajouter une imprimante..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:161
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
msgstr "Gérer les &imprimantes..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Gérer les matériaux..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:176
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Mettre à jour le profil à l'aide des paramètres / forçages actuels"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Ignorer les modifications actuelles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "&Créer un profil à partir des paramètres / forçages actuels..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:202
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Gérer les profils..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:209
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Afficher la &documentation en ligne"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "Notifier un &bug"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:225
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
-msgid "&About..."
-msgstr "&À propos de..."
+msgid "About..."
+msgstr "À propos de..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:232
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
-msgid "Delete &Selected Model"
-msgid_plural "Delete &Selected Models"
-msgstr[0] "Supprimer le modèle &sélectionné"
-msgstr[1] "Supprimer les modèles &sélectionnés"
+msgid "Delete Selected Model"
+msgid_plural "Delete Selected Models"
+msgstr[0] "Supprimer le modèle sélectionné"
+msgstr[1] "Supprimer les modèles sélectionnés"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Centrer le modèle sélectionné"
msgstr[1] "Centrer les modèles sélectionnés"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Multiplier le modèle sélectionné"
msgstr[1] "Multiplier les modèles sélectionnés"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Supprimer le modèle"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:278
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "Ce&ntrer le modèle sur le plateau"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "&Grouper les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Dégrouper les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "&Fusionner les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
msgstr "&Multiplier le modèle..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:331
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
-msgid "&Select All Models"
-msgstr "&Sélectionner tous les modèles"
+msgid "Select All Models"
+msgstr "Sélectionner tous les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
-msgid "&Clear Build Plate"
-msgstr "&Supprimer les objets du plateau"
+msgid "Clear Build Plate"
+msgstr "Supprimer les objets du plateau"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
+msgctxt "@action:inmenu menubar:file"
+msgid "Reload All Models"
+msgstr "Recharger tous les modèles"
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
-msgctxt "@action:inmenu menubar:file"
-msgid "Re&load All Models"
-msgstr "Rechar&ger tous les modèles"
-
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:360
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models To All Build Plates"
msgstr "Réorganiser tous les modèles sur tous les plateaux"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:367
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Réorganiser tous les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Réorganiser la sélection"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:382
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Réinitialiser toutes les positions des modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
-msgid "Reset All Model &Transformations"
+msgid "Reset All Model Transformations"
msgstr "Réinitialiser tous les modèles et transformations"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:396
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Ouvrir le(s) fichier(s)..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:404
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Nouveau projet..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:411
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
msgstr "Afficher le &journal du moteur..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:419
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Afficher le dossier de configuration"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:433
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse packages..."
msgstr "Parcourir les paquets..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:440
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:inmenu menubar:view"
msgid "Expand/Collapse Sidebar"
msgstr "Déplier / replier la barre latérale"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:27
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Veuillez charger un modèle 3D"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:37
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Prêt à découper"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:39
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "Découpe en cours..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:41
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Prêt à %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:43
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
msgstr "Impossible de découper"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:45
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "Découpe indisponible"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Slice current printjob"
msgstr "Découper la tâche d'impression en cours"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Cancel slicing process"
msgstr "Annuler le processus de découpe"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Préparer"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Annuler"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:320
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "Sélectionner le périphérique de sortie actif"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:713
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:767
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Ouvrir le(s) fichier(s)"
@@ -4003,129 +4157,145 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr "Ultimaker Cura"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:103
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "&Fichier"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:121
+msgctxt "@title:menu menubar:file"
+msgid "&Save..."
+msgstr "Enregi&strer..."
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:142
+msgctxt "@title:menu menubar:file"
+msgid "&Export..."
+msgstr "&Exporter..."
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:153
msgctxt "@action:inmenu menubar:file"
-msgid "&Save Selection to File"
-msgstr "Enregi&strer la sélection dans un fichier"
+msgid "Export Selection..."
+msgstr "Exporter la sélection..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
-msgctxt "@title:menu menubar:file"
-msgid "Save &As..."
-msgstr "Enregistrer &sous..."
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
-msgctxt "@title:menu menubar:file"
-msgid "Save &Project..."
-msgstr "Enregistrer le &projet..."
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:174
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Modifier"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:191
msgctxt "@title:menu"
msgid "&View"
msgstr "&Visualisation"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
msgctxt "@title:menu"
msgid "&Settings"
msgstr "&Paramètres"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
-msgctxt "@title:menu menubar:toplevel"
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+msgctxt "@title:menu menubar:settings"
msgid "&Printer"
msgstr "Im&primante"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:195
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:207
msgctxt "@title:menu"
msgid "&Material"
msgstr "&Matériau"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:204
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:216
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Définir comme extrudeur actif"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:210
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:222
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:188
msgctxt "@action:inmenu"
msgid "Enable Extruder"
msgstr "Activer l'extrudeuse"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:217
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:229
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:194
msgctxt "@action:inmenu"
msgid "Disable Extruder"
msgstr "Désactiver l'extrudeuse"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:230
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:241
msgctxt "@title:menu"
+msgid "&Build plate"
+msgstr "Plateau"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:242
+msgctxt "@title:settings"
msgid "&Profile"
msgstr "&Profil"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:252
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "E&xtensions"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:274
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:286
msgctxt "@title:menu menubar:toplevel"
msgid "&Toolbox"
msgstr "&Boîte à outils"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:281
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:294
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "P&références"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:289
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:302
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "&Aide"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:348
msgctxt "@label"
msgid "This package will be installed after restarting."
msgstr "Ce paquet sera installé après le redémarrage."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:364
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:377
msgctxt "@action:button"
msgid "Open File"
msgstr "Ouvrir un fichier"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:547
msgctxt "@title:tab"
msgid "Settings"
msgstr "Paramètres"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:579
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:593
msgctxt "@title:window"
msgid "New project"
msgstr "Nouveau projet"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:594
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "Êtes-vous sûr(e) de souhaiter lancer un nouveau projet ? Cela supprimera les objets du plateau ainsi que tous paramètres non enregistrés."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:814
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:722
+msgctxt "@title:window"
+msgid "Closing Cura"
+msgstr "Fermeture de Cura"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:723
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:735
+msgctxt "@label"
+msgid "Are you sure you want to exit Cura?"
+msgstr "Êtes-vous sûr de vouloir quitter Cura ?"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:868
msgctxt "@window:title"
msgid "Install Package"
msgstr "Installer le paquet"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:821
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:875
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Ouvrir le(s) fichier(s)"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:824
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:878
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "Nous avons trouvé au moins un fichier G-Code parmi les fichiers que vous avez sélectionné. Vous ne pouvez ouvrir qu'un seul fichier G-Code à la fois. Si vous souhaitez ouvrir un fichier G-Code, veuillez ne sélectionner qu'un seul fichier de ce type."
@@ -4135,112 +4305,107 @@ msgctxt "@title:window"
msgid "Save Project"
msgstr "Enregistrer le projet"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:116
-msgctxt "@action:label"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:137
msgctxt "@action:label"
msgid "Build plate"
msgstr "Plateau"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:169
msgctxt "@action:label"
msgid "Extruder %1"
msgstr "Extrudeuse %1"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:179
msgctxt "@action:label"
msgid "%1 & material"
msgstr "%1 & matériau"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:268
msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "Ne pas afficher à nouveau le résumé du projet lors de l'enregistrement"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:283
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:287
msgctxt "@action:button"
msgid "Save"
msgstr "Enregistrer"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:192
msgctxt "@label"
msgid "Layer Height"
msgstr "Hauteur de la couche"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:277
msgctxt "@tooltip"
msgid "This quality profile is not available for you current material and nozzle configuration. Please change these to enable this quality profile"
-msgstr "Ce profil de qualité n'est pas disponible pour votre matériau et configuration des buses actuels. Veuillez modifier ces derniers pour activer ce profil de qualité."
+msgstr "Ce profil de qualité n'est pas disponible pour votre matériau et configuration des buses actuels. Veuillez modifier ces derniers pour activer ce profil de qualité"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:450
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
msgstr "Un profil personnalisé est actuellement actif. Pour activer le curseur de qualité, choisissez un profil de qualité par défaut dans l'onglet Personnaliser"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:467
msgctxt "@label"
msgid "Print Speed"
msgstr "Vitesse d’impression"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:479
msgctxt "@label"
msgid "Slower"
msgstr "Ralentir"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:490
msgctxt "@label"
msgid "Faster"
msgstr "Accélérer"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:518
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr "Vous avez modifié certains paramètres du profil. Si vous souhaitez les modifier, allez dans le mode Personnaliser."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:506
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:541
msgctxt "@label"
msgid "Infill"
msgstr "Remplissage"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:740
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:777
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Un remplissage graduel augmentera la quantité de remplissage vers le haut."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:752
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:791
msgctxt "@label"
msgid "Enable gradual"
msgstr "Permettre le remplissage graduel"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:819
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:858
msgctxt "@label"
msgid "Generate Support"
msgstr "Générer les supports"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:853
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:892
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "Générer des structures pour soutenir les parties du modèle qui possèdent des porte-à-faux. Sans ces structures, ces parties s'effondreront durant l'impression."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:925
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:964
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Sélectionnez l'extrudeur à utiliser comme support. Cela créera des structures de support sous le modèle afin de l'empêcher de s'affaisser ou de s'imprimer dans les airs."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:948
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:987
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Adhérence au plateau"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1003
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1042
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
msgstr "Activez l'impression d'une bordure ou plaquette (Brim/Raft). Cela ajoutera une zone plate autour de ou sous votre objet qui est facile à découper par la suite."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1043
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1082
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "Besoin d'aide pour améliorer vos impressions ? Lisez les Guides de dépannage Ultimaker"
@@ -4287,23 +4452,22 @@ msgctxt "@label"
msgid "Printer type"
msgstr "Type d'imprimante"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:376
msgctxt "@label"
msgid "Material"
msgstr "Matériau"
-# Added after the string freeze.
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:538
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:543
msgctxt "@label"
-msgid "Use adhesion sheet or glue with this material combination"
-msgstr "Utilisez feuilles d'adhérence ou de la colle avec cette combinaison des matériaux"
+msgid "Use glue with this material combination"
+msgstr "Utiliser de la colle avec cette combinaison de matériaux"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:570
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:575
msgctxt "@label"
msgid "Check compatibility"
msgstr "Vérifier la compatibilité"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:588
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:593
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Cliquez ici pour vérifier la compatibilité des matériaux sur Ultimaker.com."
@@ -4393,16 +4557,6 @@ msgctxt "name"
msgid "God Mode"
msgstr "Mode God"
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "description"
-msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
-msgstr "Accepte les G-Code et les envoie par Wi-Fi à une box WiFi Doodle3D."
-
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "name"
-msgid "Doodle3D WiFi-Box"
-msgstr "Box WiFi Doodle3D"
-
#: ChangeLogPlugin/plugin.json
msgctxt "description"
msgid "Shows changes since latest checked version."
@@ -4413,6 +4567,16 @@ msgctxt "name"
msgid "Changelog"
msgstr "Récapitulatif des changements"
+#: FirmwareUpdater/plugin.json
+msgctxt "description"
+msgid "Provides a machine actions for updating firmware."
+msgstr "Fournit à une machine des actions permettant la mise à jour du firmware."
+
+#: FirmwareUpdater/plugin.json
+msgctxt "name"
+msgid "Firmware Updater"
+msgstr "Programme de mise à jour du firmware"
+
#: ProfileFlattener/plugin.json
msgctxt "description"
msgid "Create a flattend quality changes profile."
@@ -4436,7 +4600,7 @@ msgstr "Impression par USB"
#: UserAgreement/plugin.json
msgctxt "description"
msgid "Ask the user once if he/she agrees with our license."
-msgstr "Demander à l'utilisateur une fois s'il appose son accord à notre licence"
+msgstr "Demander à l'utilisateur une fois s'il appose son accord à notre licence."
#: UserAgreement/plugin.json
msgctxt "name"
@@ -4483,16 +4647,6 @@ msgctxt "name"
msgid "Prepare Stage"
msgstr "Étape de préparation"
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "description"
-msgid "Provides an edit window for direct script editing."
-msgstr "Fournit une fenêtre d'édition pour l'édition directe de script."
-
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "name"
-msgid "Live scripting tool"
-msgstr "Outil de scripting en direct"
-
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4506,7 +4660,7 @@ msgstr "Plugin de périphérique de sortie sur disque amovible"
#: UM3NetworkPrinting/plugin.json
msgctxt "description"
msgid "Manages network connections to Ultimaker 3 printers."
-msgstr "Gère les connexions réseau vers les imprimantes Ultimaker 3"
+msgstr "Gère les connexions réseau vers les imprimantes Ultimaker 3."
#: UM3NetworkPrinting/plugin.json
msgctxt "name"
@@ -4603,16 +4757,6 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Lecteur de profil Cura antérieur"
-#: CuraBlenderPlugin/plugin.json
-msgctxt "description"
-msgid "Helps to open Blender files directly in Cura."
-msgstr "Aide à ouvrir les fichiers Blender directement dans Cura."
-
-#: CuraBlenderPlugin/plugin.json
-msgctxt "name"
-msgid "Blender Integration (experimental)"
-msgstr "Intégration Blender (expérimental)"
-
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4663,6 +4807,16 @@ msgctxt "name"
msgid "Version Upgrade 2.7 to 3.0"
msgstr "Mise à niveau de version, de 2.7 vers 3.0"
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "description"
+msgid "Upgrades configurations from Cura 3.4 to Cura 3.5."
+msgstr "Configurations des mises à niveau de Cura 3.4 vers Cura 3.5."
+
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "name"
+msgid "Version Upgrade 3.4 to 3.5"
+msgstr "Mise à niveau de 3.4 vers 3.5"
+
#: VersionUpgrade/VersionUpgrade30to31/plugin.json
msgctxt "description"
msgid "Upgrades configurations from Cura 3.0 to Cura 3.1."
@@ -4776,7 +4930,7 @@ msgstr "Générateur de profil Cura"
#: 3MFWriter/plugin.json
msgctxt "description"
msgid "Provides support for writing 3MF files."
-msgstr "Permet l'écriture de fichiers 3MF"
+msgstr "Permet l'écriture de fichiers 3MF."
#: 3MFWriter/plugin.json
msgctxt "name"
@@ -4803,6 +4957,299 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Lecteur de profil Cura"
+#~ msgctxt "@warning:status"
+#~ msgid "Please generate G-code before saving."
+#~ msgstr "Veuillez générer le G-Code avant d'enregistrer."
+
+#~ msgctxt "@item:inmenu"
+#~ msgid "Profile Assistant"
+#~ msgstr "Assistant de profil"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Profile Assistant"
+#~ msgstr "Assistant de profil"
+
+#~ msgctxt "@action"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Mise à niveau du firmware"
+
+#~ msgctxt "@label unknown material"
+#~ msgid "Unknown"
+#~ msgstr "Inconnu"
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "No custom profile to import in file {0}"
+#~ msgstr "Aucun profil personnalisé à importer dans le fichier {0}"
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "This profile {0} contains incorrect data, could not import it."
+#~ msgstr "Le profil {0} contient des données incorrectes ; échec de l'importation."
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it."
+#~ msgstr "La machine définie dans le profil {0} ({1}) ne correspond pas à votre machine actuelle ({2}) ; échec de l'importation."
+
+#~ msgctxt "@title:window"
+#~ msgid "Confirm uninstall "
+#~ msgstr "Confirmer la désinstallation "
+
+#~ msgctxt "@label:status"
+#~ msgid "Paused"
+#~ msgstr "En pause"
+
+#~ msgctxt "@action:button"
+#~ msgid "Previous"
+#~ msgstr "Précédent"
+
+#~ msgctxt "@action:button"
+#~ msgid "Next"
+#~ msgstr "Suivant"
+
+#~ msgctxt "@label"
+#~ msgid "Tip"
+#~ msgstr "Astuce"
+
+#~ msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
+#~ msgid "%1m / ~ %2g / ~ %4 %3"
+#~ msgstr "%1m / ~ %2g / ~ %4 %3"
+
+#~ msgctxt "@label Print estimates: m for meters, g for grams"
+#~ msgid "%1m / ~ %2g"
+#~ msgstr "%1m / ~ %2g"
+
+#~ msgctxt "@label"
+#~ msgid "Print experiment"
+#~ msgstr "Test d'impression"
+
+#~ msgctxt "@label"
+#~ msgid "Checklist"
+#~ msgstr "Liste de contrôle"
+
+#~ msgctxt "@title"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Mise à niveau du firmware"
+
+#~ msgctxt "description"
+#~ msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+#~ msgstr "Permet aux fabricants de matériaux de créer de nouveaux matériaux et profils de qualité à l'aide d'une interface utilisateur ad hoc."
+
+#~ msgctxt "name"
+#~ msgid "Print Profile Assistant"
+#~ msgstr "Assistant de profil d'impression"
+
+#~ msgctxt "@action:button"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Imprimer avec Doodle3D WiFi-Box"
+
+#~ msgctxt "@properties:tooltip"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Imprimer avec Doodle3D WiFi-Box"
+
+#~ msgctxt "@info:status"
+#~ msgid "Connecting to Doodle3D Connect"
+#~ msgstr "Connexion avec Doodle3D Connecter..."
+
+#~ msgctxt "@info:status"
+#~ msgid "Sending data to Doodle3D Connect"
+#~ msgstr "Envoi de données vers Doodle3D Connecter..."
+
+#~ msgctxt "@info:status"
+#~ msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
+#~ msgstr "Impossible d'envoyer les données à Doodle3D Connect. Une autre tâche est-elle toujours active ?"
+
+#~ msgctxt "@info:status"
+#~ msgid "Storing data on Doodle3D Connect"
+#~ msgstr "Enregistrement de données dans Doodle3D Connecter..."
+
+#~ msgctxt "@info:status"
+#~ msgid "File sent to Doodle3D Connect"
+#~ msgstr "Fichier envoyé vers Doodle3D Connecter"
+
+#~ msgctxt "@action:button"
+#~ msgid "Open Connect..."
+#~ msgstr "Ouvrir Connect..."
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Open the Doodle3D Connect web interface"
+#~ msgstr "Ouvrir l'interface web Doodle3D Connecter"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Blender file"
+#~ msgstr "Fichier Blender"
+
+#~ msgctxt "@info:status"
+#~ msgid ""
+#~ "Could not export using \"{}\" quality!\n"
+#~ "Felt back to \"{}\"."
+#~ msgstr ""
+#~ "Impossible d'exporter avec la qualité \"{}\" !\n"
+#~ "Qualité redéfinie sur \"{}\"."
+
+#~ msgctxt "@label"
+#~ msgid "Contact"
+#~ msgstr "Contact"
+
+#~ msgctxt "@label"
+#~ msgid "This printer is not set up to host a group of Ultimaker 3 printers."
+#~ msgstr "L'imprimante n'est pas configurée pour héberger un groupe d'imprimantes Ultimaker 3."
+
+#~ msgctxt "@label"
+#~ msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
+#~ msgstr "L'imprimante est le patron pour un groupe de %1 imprimantes Ultimaker 3."
+
+#~ msgctxt "@label: arg 1 is group name"
+#~ msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
+#~ msgstr "%1 n'est pas configurée pour héberger un groupe d'imprimantes connectées Ultimaker 3."
+
+#~ msgctxt "@label link to connect manager"
+#~ msgid "Add/Remove printers"
+#~ msgstr "Ajouter / supprimer une imprimante"
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Opens the print jobs page with your default web browser."
+#~ msgstr "Ouvre la page des tâches d'impression avec votre navigateur web."
+
+#~ msgctxt "@action:button"
+#~ msgid "View print jobs"
+#~ msgstr "Afficher les tâches d'impression"
+
+#~ msgctxt "@label:status"
+#~ msgid "Preparing to print"
+#~ msgstr "Préparation..."
+
+#~ msgctxt "@label:status"
+#~ msgid "Printing"
+#~ msgstr "Impression..."
+
+#~ msgctxt "@label:status"
+#~ msgid "Available"
+#~ msgstr "Disponible"
+
+#~ msgctxt "@label:status"
+#~ msgid "Lost connection with the printer"
+#~ msgstr "Connexion avec l'imprimante perdue"
+
+#~ msgctxt "@label:status"
+#~ msgid "Unavailable"
+#~ msgstr "Indisponible"
+
+#~ msgctxt "@label:status"
+#~ msgid "Unknown"
+#~ msgstr "Inconnu"
+
+#~ msgctxt "@label:status"
+#~ msgid "Disabled"
+#~ msgstr "Désactivé"
+
+#~ msgctxt "@label:status"
+#~ msgid "Reserved"
+#~ msgstr "Réservée"
+
+#~ msgctxt "@label"
+#~ msgid "Preparing to print"
+#~ msgstr "Préparation de l'impression..."
+
+#~ msgctxt "@label:status"
+#~ msgid "Print aborted"
+#~ msgstr "Abandon de l'impression"
+
+#~ msgctxt "@label"
+#~ msgid "Not accepting print jobs"
+#~ msgstr "Non acceptation des tâches d'impression"
+
+#~ msgctxt "@label"
+#~ msgid "Finishes at: "
+#~ msgstr "Complète a: "
+
+#~ msgctxt "@label"
+#~ msgid "Clear build plate"
+#~ msgstr "Enlever les objets du plateau"
+
+#~ msgctxt "@label"
+#~ msgid "Waiting for configuration change"
+#~ msgstr "En attente de modification de configuration"
+
+#~ msgctxt "@title"
+#~ msgid "Print jobs"
+#~ msgstr "Imprimer les tâches"
+
+#~ msgctxt "@label:title"
+#~ msgid "Printers"
+#~ msgstr "Imprimantes"
+
+#~ msgctxt "@action:button"
+#~ msgid "View printers"
+#~ msgstr "Afficher les imprimantes"
+
+#~ msgctxt "@label:"
+#~ msgid "Pause"
+#~ msgstr "Pause"
+
+#~ msgctxt "@label:"
+#~ msgid "Resume"
+#~ msgstr "Reprendre"
+
+#~ msgctxt "@label:"
+#~ msgid "Abort Print"
+#~ msgstr "Abandonner l'impression"
+
+#~ msgctxt "@option:openProject"
+#~ msgid "Always ask"
+#~ msgstr "Toujours demander"
+
+#~ msgctxt "@label"
+#~ msgid "Override Profile"
+#~ msgstr "Écraser le profil"
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+#~ msgstr "Les modèles nouvellement chargés doivent-ils être disposés sur le plateau ? Utilisé en conjonction avec le multi-plateau (EXPÉRIMENTAL)"
+
+#~ msgctxt "@option:check"
+#~ msgid "Do not arrange objects on load"
+#~ msgstr "Ne pas réorganiser les objets lors du chargement"
+
+#~ msgctxt "@action:inmenu menubar:file"
+#~ msgid "&Save Selection to File"
+#~ msgstr "Enregi&strer la sélection dans un fichier"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save &As..."
+#~ msgstr "Enregistrer &sous..."
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save &Project..."
+#~ msgstr "Enregistrer le &projet..."
+
+# Added after the string freeze.
+#~ msgctxt "@label"
+#~ msgid "Use adhesion sheet or glue with this material combination"
+#~ msgstr "Utilisez feuilles d'adhérence ou de la colle avec cette combinaison des matériaux"
+
+#~ msgctxt "description"
+#~ msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
+#~ msgstr "Accepte les G-Code et les envoie par Wi-Fi à une box WiFi Doodle3D."
+
+#~ msgctxt "name"
+#~ msgid "Doodle3D WiFi-Box"
+#~ msgstr "Box WiFi Doodle3D"
+
+#~ msgctxt "description"
+#~ msgid "Provides an edit window for direct script editing."
+#~ msgstr "Fournit une fenêtre d'édition pour l'édition directe de script."
+
+#~ msgctxt "name"
+#~ msgid "Live scripting tool"
+#~ msgstr "Outil de scripting en direct"
+
+#~ msgctxt "description"
+#~ msgid "Helps to open Blender files directly in Cura."
+#~ msgstr "Aide à ouvrir les fichiers Blender directement dans Cura."
+
+#~ msgctxt "name"
+#~ msgid "Blender Integration (experimental)"
+#~ msgstr "Intégration Blender (expérimental)"
+
#~ msgctxt "@info:title"
#~ msgid "Model Checker Warning"
#~ msgstr "Avertissement contrôleur de modèle"
@@ -5070,10 +5517,6 @@ msgstr "Lecteur de profil Cura"
#~ msgid "Browse plugins..."
#~ msgstr "Parcourir les plug-ins..."
-#~ msgctxt "@title:menu"
-#~ msgid "&Build plate"
-#~ msgstr "&Plateau"
-
#~ msgctxt "@title:menu menubar:toplevel"
#~ msgid "P&lugins"
#~ msgstr "&Plug-ins"
@@ -5299,14 +5742,6 @@ msgstr "Lecteur de profil Cura"
#~ "\n"
#~ "Désolé !"
-#~ msgctxt "@item:inmenu"
-#~ msgid "Profile Assistant"
-#~ msgstr "Assistant de profil"
-
-#~ msgctxt "@item:inlistbox"
-#~ msgid "Profile Assistant"
-#~ msgstr "Assistant de profil"
-
#~ msgctxt "@item:material"
#~ msgid "No material loaded"
#~ msgstr "Pas de matériau chargé"
@@ -5437,14 +5872,6 @@ msgstr "Lecteur de profil Cura"
#~ msgid "Configure setting visiblity..."
#~ msgstr "Configurer la visibilité des paramètres..."
-#~ msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
-#~ msgid "%1m / ~ %2g / ~ %4 %3"
-#~ msgstr "%1m / ~ %2g / ~ %4 %3"
-
-#~ msgctxt "@label Print estimates: m for meters, g for grams"
-#~ msgid "%1m / ~ %2g"
-#~ msgstr "%1m / ~ %2g"
-
#~ msgctxt "@title:menuitem %1 is the automatically selected material"
#~ msgid "Automatic: %1"
#~ msgstr "Automatique : %1"
@@ -5481,14 +5908,6 @@ msgstr "Lecteur de profil Cura"
#~ msgid "GCode Profile Reader"
#~ msgstr "Lecteur de profil GCode"
-#~ msgctxt "description"
-#~ msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
-#~ msgstr "Permet aux fabricants de matériaux de créer de nouveaux matériaux et profils de qualité à l'aide d'une interface utilisateur ad hoc."
-
-#~ msgctxt "name"
-#~ msgid "Print Profile Assistant"
-#~ msgstr "Assistant de profil d'impression"
-
#~ msgctxt "@info:status"
#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
#~ msgstr "Des erreurs sont apparues lors de l'ouverture de votre fichier SolidWorks ! Veuillez vérifier s'il est possible d'ouvrir votre fichier dans SolidWorks sans que cela ne cause de problèmes."
@@ -5685,10 +6104,6 @@ msgstr "Lecteur de profil Cura"
#~ msgid "This printer is the host for a group of %1 connected Ultimaker 3 printers"
#~ msgstr "L'imprimante est configurée pour héberger un groupe de %1 imprimantes connectées Ultimaker 3."
-#~ msgctxt "@label:status"
-#~ msgid "Preparing"
-#~ msgstr "Préparation..."
-
#~ msgctxt "@label"
#~ msgid "Completed on: "
#~ msgstr "Finalisé sur : "
diff --git a/resources/i18n/fr_FR/fdmextruder.def.json.po b/resources/i18n/fr_FR/fdmextruder.def.json.po
index d4d827870e..52969f511f 100644
--- a/resources/i18n/fr_FR/fdmextruder.def.json.po
+++ b/resources/i18n/fr_FR/fdmextruder.def.json.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0000\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
+"PO-Revision-Date: 2018-09-28 14:25+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -166,6 +166,16 @@ msgctxt "extruder_prime_pos_z description"
msgid "The Z coordinate of the position where the nozzle primes at the start of printing."
msgstr "Les coordonnées Z de la position à laquelle la buse s'amorce au début de l'impression."
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number label"
+msgid "Extruder Print Cooling Fan"
+msgstr "Ventilateur de refroidissement d'impression de l'extrudeuse"
+
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number description"
+msgid "The number of the print cooling fan associated with this extruder. Only change this from the default value of 0 when you have a different print cooling fan for each extruder."
+msgstr "Numéro du ventilateur de refroidissement d'impression associé à cette extrudeuse. Ne modifiez cette valeur par rapport à la valeur par défaut 0 que si vous utilisez un ventilateur de refroidissement d'impression différent pour chaque extrudeuse."
+
#: fdmextruder.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
diff --git a/resources/i18n/fr_FR/fdmprinter.def.json.po b/resources/i18n/fr_FR/fdmprinter.def.json.po
index 68f9060093..87aa9d5a27 100644
--- a/resources/i18n/fr_FR/fdmprinter.def.json.po
+++ b/resources/i18n/fr_FR/fdmprinter.def.json.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-03-29 08:36+0200\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
+"PO-Revision-Date: 2018-09-28 15:00+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -81,6 +81,16 @@ msgctxt "material_guid description"
msgid "GUID of the material. This is set automatically. "
msgstr "GUID du matériau. Cela est configuré automatiquement. "
+#: fdmprinter.def.json
+msgctxt "material_diameter label"
+msgid "Diameter"
+msgstr "Diamètre"
+
+#: fdmprinter.def.json
+msgctxt "material_diameter description"
+msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
+msgstr "Ajuste le diamètre du filament utilisé. Faites correspondre cette valeur au diamètre du filament utilisé."
+
#: fdmprinter.def.json
msgctxt "material_bed_temp_wait label"
msgid "Wait for Build Plate Heatup"
@@ -534,7 +544,7 @@ msgstr "Accélération maximale X"
#: fdmprinter.def.json
msgctxt "machine_max_acceleration_x description"
msgid "Maximum acceleration for the motor of the X-direction"
-msgstr "Accélération maximale pour le moteur du sens X."
+msgstr "Accélération maximale pour le moteur du sens X"
#: fdmprinter.def.json
msgctxt "machine_max_acceleration_y label"
@@ -1034,7 +1044,7 @@ msgstr "Zig Zag"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 label"
msgid "Bottom Pattern Initial Layer"
-msgstr "Couche initiale du motif du dessous."
+msgstr "Couche initiale du motif du dessous"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 description"
@@ -1056,6 +1066,16 @@ msgctxt "top_bottom_pattern_0 option zigzag"
msgid "Zig Zag"
msgstr "Zig Zag"
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons label"
+msgid "Connect Top/Bottom Polygons"
+msgstr "Relier les polygones supérieurs / inférieurs"
+
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons description"
+msgid "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happen midway over infill this feature can reduce the top surface quality."
+msgstr "Relier les voies de couche extérieure supérieures / inférieures lorsqu'elles sont côte à côte. Pour le motif concentrique, ce paramètre réduit considérablement le temps de parcours, mais comme les liens peuvent se trouver à mi-chemin sur le remplissage, cette fonctionnalité peut réduire la qualité de la surface supérieure."
+
#: fdmprinter.def.json
msgctxt "skin_angles label"
msgid "Top/Bottom Line Directions"
@@ -1136,6 +1156,26 @@ msgctxt "travel_compensate_overlapping_walls_x_enabled description"
msgid "Compensate the flow for parts of an inner wall being printed where there is already a wall in place."
msgstr "Compenser le débit pour les parties d'une paroi intérieure imprimées aux endroits où une paroi est déjà en place."
+#: fdmprinter.def.json
+msgctxt "wall_min_flow label"
+msgid "Minimum Wall Flow"
+msgstr "Débit minimal de la paroi"
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow description"
+msgid "Minimum allowed percentage flow for a wall line. The wall overlap compensation reduces a wall's flow when it lies close to an existing wall. Walls whose flow is less than this value will be replaced with a travel move. When using this setting, you must enable the wall overlap compensation and print the outer wall before inner walls."
+msgstr "Pourcentage de débit minimum autorisé pour une ligne de paroi. La compensation de chevauchement de paroi réduit le débit d'une paroi lorsqu'elle se trouve à proximité d'une paroi existante. Les parois dont le débit est inférieur à cette valeur seront remplacées par un déplacement. Lors de l'utilisation de ce paramètre, vous devez activer la compensation de chevauchement de paroi et imprimer la paroi externe avant les parois internes."
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract label"
+msgid "Prefer Retract"
+msgstr "Préférer la rétractation"
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract description"
+msgid "If enabled, retraction is used rather than combing for travel moves that replace walls whose flow is below the minimum flow threshold."
+msgstr "Si cette option est activée, la rétraction est utilisée à la place des détours pour les déplacements qui remplacent les parois dont le débit est inférieur au seuil de débit minimal."
+
#: fdmprinter.def.json
msgctxt "fill_perimeter_gaps label"
msgid "Fill Gaps Between Walls"
@@ -1434,7 +1474,7 @@ msgstr "Densité du remplissage"
#: fdmprinter.def.json
msgctxt "infill_sparse_density description"
msgid "Adjusts the density of infill of the print."
-msgstr "Adapte la densité de remplissage de l'impression"
+msgstr "Adapte la densité de remplissage de l'impression."
#: fdmprinter.def.json
msgctxt "infill_line_distance label"
@@ -1453,8 +1493,8 @@ msgstr "Motif de remplissage"
#: fdmprinter.def.json
msgctxt "infill_pattern description"
-msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
-msgstr "Motif du matériau de remplissage de l'impression. La ligne et le remplissage en zigzag changent de sens à chaque alternance de couche, réduisant ainsi les coûts matériels. Les motifs en grille, en triangle, trihexagonaux, cubiques, octaédriques, quart cubiques et concentriques sont entièrement imprimés sur chaque couche. Les remplissages cubique, quart cubique et octaédrique changent à chaque couche afin d'offrir une répartition plus égale de la solidité dans chaque direction."
+msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Gyroid, cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
+msgstr "Motif du matériau de remplissage de l'impression. La ligne et le remplissage en zigzag changent de sens à chaque alternance de couche, réduisant ainsi les coûts matériels. Les motifs en grille, en triangle, trihexagonaux, cubiques, octaédriques, quart cubiques et concentriques sont entièrement imprimés sur chaque couche. Les remplissages gyroïde, cubique, quart cubique et octaédrique changent à chaque couche afin d'offrir une répartition plus égale de la solidité dans chaque direction."
#: fdmprinter.def.json
msgctxt "infill_pattern option grid"
@@ -1501,11 +1541,6 @@ msgctxt "infill_pattern option concentric"
msgid "Concentric"
msgstr "Concentrique"
-#: fdmprinter.def.json
-msgctxt "infill_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concentrique 3D"
-
#: fdmprinter.def.json
msgctxt "infill_pattern option zigzag"
msgid "Zig Zag"
@@ -1521,6 +1556,11 @@ msgctxt "infill_pattern option cross_3d"
msgid "Cross 3D"
msgstr "Entrecroisé 3D"
+#: fdmprinter.def.json
+msgctxt "infill_pattern option gyroid"
+msgid "Gyroid"
+msgstr "Gyroïde"
+
#: fdmprinter.def.json
msgctxt "zig_zaggify_infill label"
msgid "Connect Infill Lines"
@@ -1531,6 +1571,16 @@ msgctxt "zig_zaggify_infill description"
msgid "Connect the ends where the infill pattern meets the inner wall using a line which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduce the effects of infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
msgstr "Relie les extrémités où le motif de remplissage touche la paroi interne, à l'aide d'une ligne épousant la forme de la paroi interne. Activer ce paramètre peut faire mieux coller le remplissage aux parois, et réduit les effets du remplissage sur la qualité des surfaces verticales. Désactiver ce paramètre diminue la quantité de matière utilisée."
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons label"
+msgid "Connect Infill Polygons"
+msgstr "Relier les polygones de remplissage"
+
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons description"
+msgid "Connect infill paths where they run next to each other. For infill patterns which consist of several closed polygons, enabling this setting greatly reduces the travel time."
+msgstr "Relier les voies de remplissage lorsqu'elles sont côte à côte. Pour les motifs de remplissage composés de plusieurs polygones fermés, ce paramètre permet de réduire considérablement le temps de parcours."
+
#: fdmprinter.def.json
msgctxt "infill_angles label"
msgid "Infill Line Directions"
@@ -1561,6 +1611,28 @@ msgctxt "infill_offset_y description"
msgid "The infill pattern is moved this distance along the Y axis."
msgstr "Le motif de remplissage est décalé de cette distance sur l'axe Y."
+#: fdmprinter.def.json
+msgctxt "infill_multiplier label"
+msgid "Infill Line Multiplier"
+msgstr "Multiplicateur de ligne de remplissage"
+
+#: fdmprinter.def.json
+msgctxt "infill_multiplier description"
+msgid "Convert each infill line to this many lines. The extra lines do not cross over each other, but avoid each other. This makes the infill stiffer, but increases print time and material usage."
+msgstr "Convertir chaque ligne de remplissage en ce nombre de lignes. Les lignes supplémentaires ne se croisent pas entre elles, mais s'évitent mutuellement. Cela rend le remplissage plus rigide, mais augmente le temps d'impression et la quantité de matériau utilisé."
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count label"
+msgid "Extra Infill Wall Count"
+msgstr "Nombre de parois de remplissage supplémentaire"
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count description"
+msgid ""
+"Add extra walls around the infill area. Such walls can make top/bottom skin lines sag down less which means you need less top/bottom skin layers for the same quality at the cost of some extra material.\n"
+"This feature can combine with the Connect Infill Polygons to connect all the infill into a single extrusion path without the need for travels or retractions if configured right."
+msgstr "Ajoutez des parois supplémentaires autour de la zone de remplissage. De telles parois peuvent réduire l'affaissement des lignes de couche extérieure supérieure / inférieure, réduisant le nombre de couches extérieures supérieures / inférieures nécessaires pour obtenir la même qualité, au prix d'un peu de matériau supplémentaire.\nConfigurée correctement, cette fonctionnalité peut être combinée avec « Relier les polygones de remplissage » pour relier tous les remplissages en un seul mouvement d'extrusion sans avoir besoin de déplacements ou de rétractions."
+
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
msgid "Cubic Subdivision Shell"
@@ -1789,7 +1861,7 @@ msgstr "Température d’impression par défaut"
#: fdmprinter.def.json
msgctxt "default_material_print_temperature description"
msgid "The default temperature used for printing. This should be the \"base\" temperature of a material. All other print temperatures should use offsets based on this value"
-msgstr "La température par défaut utilisée pour l'impression. Il doit s'agir de la température de « base » d'un matériau. Toutes les autres températures d'impression doivent utiliser des décalages basés sur cette valeur."
+msgstr "La température par défaut utilisée pour l'impression. Il doit s'agir de la température de « base » d'un matériau. Toutes les autres températures d'impression doivent utiliser des décalages basés sur cette valeur"
#: fdmprinter.def.json
msgctxt "material_print_temperature label"
@@ -1849,7 +1921,7 @@ msgstr "Température du plateau par défaut"
#: fdmprinter.def.json
msgctxt "default_material_bed_temperature description"
msgid "The default temperature used for the heated build plate. This should be the \"base\" temperature of a build plate. All other print temperatures should use offsets based on this value"
-msgstr "Température par défaut utilisée pour le plateau chauffant. Il doit s'agir de la température de « base » d'un plateau. Toutes les autres températures d'impression sont définies en fonction de cette valeur."
+msgstr "Température par défaut utilisée pour le plateau chauffant. Il doit s'agir de la température de « base » d'un plateau. Toutes les autres températures d'impression sont définies en fonction de cette valeur"
#: fdmprinter.def.json
msgctxt "material_bed_temperature label"
@@ -1871,16 +1943,6 @@ msgctxt "material_bed_temperature_layer_0 description"
msgid "The temperature used for the heated build plate at the first layer."
msgstr "Température utilisée pour le plateau chauffant à la première couche."
-#: fdmprinter.def.json
-msgctxt "material_diameter label"
-msgid "Diameter"
-msgstr "Diamètre"
-
-#: fdmprinter.def.json
-msgctxt "material_diameter description"
-msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
-msgstr "Ajuste le diamètre du filament utilisé. Faites correspondre cette valeur au diamètre du filament utilisé."
-
#: fdmprinter.def.json
msgctxt "material_adhesion_tendency label"
msgid "Adhesion Tendency"
@@ -2718,8 +2780,8 @@ msgstr "Mode de détours"
#: fdmprinter.def.json
msgctxt "retraction_combing description"
-msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
-msgstr "Les détours (le 'combing') maintiennent le bec dans les zones déjà imprimées lors des déplacements. Cela résulte en des déplacements légèrement plus longs mais réduit le recours aux rétractions. Si les détours sont désactivés, le matériau se rétractera et le bec se déplacera en ligne droite jusqu'au point suivant. Il est également possible d'éviter les détours sur les zones de la couche du dessus / dessous en effectuant les détours uniquement dans le remplissage."
+msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas and also to only comb within the infill. Note that the 'Within Infill' option behaves exactly like the 'Not in Skin' option in earlier Cura releases."
+msgstr "Les détours maintiennent la buse dans les zones déjà imprimées lors des déplacements. Cela résulte en des déplacements légèrement plus longs mais réduit le recours aux rétractions. Si les détours sont désactivés, le matériau se rétractera et la buse se déplacera en ligne droite jusqu'au point suivant. Il est également possible d'éviter les détours sur les zones de la couche extérieure supérieure / inférieure et aussi de n'effectuer les détours que dans le remplissage. Notez que l'option « À l'intérieur du remplissage » se comporte exactement comme l'option « Pas dans la couche extérieure » dans les versions précédentes de Cura."
#: fdmprinter.def.json
msgctxt "retraction_combing option off"
@@ -2736,6 +2798,11 @@ msgctxt "retraction_combing option noskin"
msgid "Not in Skin"
msgstr "Pas dans la couche extérieure"
+#: fdmprinter.def.json
+msgctxt "retraction_combing option infill"
+msgid "Within Infill"
+msgstr "À l'intérieur du remplissage"
+
#: fdmprinter.def.json
msgctxt "retraction_combing_max_distance label"
msgid "Max Comb Distance With No Retract"
@@ -3116,11 +3183,6 @@ msgctxt "support_pattern option concentric"
msgid "Concentric"
msgstr "Concentrique"
-#: fdmprinter.def.json
-msgctxt "support_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concentrique 3D"
-
#: fdmprinter.def.json
msgctxt "support_pattern option zigzag"
msgid "Zig Zag"
@@ -3181,6 +3243,56 @@ msgctxt "support_line_distance description"
msgid "Distance between the printed support structure lines. This setting is calculated by the support density."
msgstr "Distance entre les lignes de support imprimées. Ce paramètre est calculé par la densité du support."
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance label"
+msgid "Initial Layer Support Line Distance"
+msgstr "Distance d'écartement de ligne du support de la couche initiale"
+
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance description"
+msgid "Distance between the printed initial layer support structure lines. This setting is calculated by the support density."
+msgstr "Distance entre les lignes de la structure de support de la couche initiale imprimée. Ce paramètre est calculé en fonction de la densité du support."
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle label"
+msgid "Support Infill Line Direction"
+msgstr "Direction de ligne de remplissage du support"
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle description"
+msgid "Orientation of the infill pattern for supports. The support infill pattern is rotated in the horizontal plane."
+msgstr "Orientation du motif de remplissage pour les supports. Le motif de remplissage du support pivote dans le plan horizontal."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable label"
+msgid "Enable Support Brim"
+msgstr "Activer la bordure du support"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable description"
+msgid "Generate a brim within the support infill regions of the first layer. This brim is printed underneath the support, not around it. Enabling this setting increases the adhesion of support to the build plate."
+msgstr "Générer un bord à l'intérieur des zones de remplissage du support de la première couche. Cette bordure est imprimée sous le support et non autour de celui-ci, ce qui augmente l'adhérence du support au plateau."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width label"
+msgid "Support Brim Width"
+msgstr "Largeur de la bordure du support"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width description"
+msgid "The width of the brim to print underneath the support. A larger brim enhances adhesion to the build plate, at the cost of some extra material."
+msgstr "Largeur de la bordure à imprimer sous le support. Une plus grande bordure améliore l'adhérence au plateau, mais demande un peu de matériau supplémentaire."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count label"
+msgid "Support Brim Line Count"
+msgstr "Nombre de lignes de la bordure du support"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count description"
+msgid "The number of lines used for the support brim. More brim lines enhance adhesion to the build plate, at the cost of some extra material."
+msgstr "Nombre de lignes utilisées pour la bordure du support. L'augmentation du nombre de lignes de bordure améliore l'adhérence au plateau, mais demande un peu de matériau supplémentaire."
+
#: fdmprinter.def.json
msgctxt "support_z_distance label"
msgid "Support Z Distance"
@@ -3471,11 +3583,6 @@ msgctxt "support_interface_pattern option concentric"
msgid "Concentric"
msgstr "Concentrique"
-#: fdmprinter.def.json
-msgctxt "support_interface_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concentrique 3D"
-
#: fdmprinter.def.json
msgctxt "support_interface_pattern option zigzag"
msgid "Zig Zag"
@@ -3511,11 +3618,6 @@ msgctxt "support_roof_pattern option concentric"
msgid "Concentric"
msgstr "Concentrique"
-#: fdmprinter.def.json
-msgctxt "support_roof_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concentrique 3D"
-
#: fdmprinter.def.json
msgctxt "support_roof_pattern option zigzag"
msgid "Zig Zag"
@@ -3551,16 +3653,31 @@ msgctxt "support_bottom_pattern option concentric"
msgid "Concentric"
msgstr "Concentrique"
-#: fdmprinter.def.json
-msgctxt "support_bottom_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "Concentrique 3D"
-
#: fdmprinter.def.json
msgctxt "support_bottom_pattern option zigzag"
msgid "Zig Zag"
msgstr "Zig Zag"
+#: fdmprinter.def.json
+msgctxt "support_fan_enable label"
+msgid "Fan Speed Override"
+msgstr "Annulation de la vitesse du ventilateur"
+
+#: fdmprinter.def.json
+msgctxt "support_fan_enable description"
+msgid "When enabled, the print cooling fan speed is altered for the skin regions immediately above the support."
+msgstr "Lorsque cette fonction est activée, la vitesse du ventilateur de refroidissement de l'impression est modifiée pour les régions de la couche extérieure situées immédiatement au-dessus du support."
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed label"
+msgid "Supported Skin Fan Speed"
+msgstr "Vitesse du ventilateur de couche extérieure supportée"
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed description"
+msgid "Percentage fan speed to use when printing the skin regions immediately above the support. Using a high fan speed can make the support easier to remove."
+msgstr "Pourcentage de la vitesse du ventilateur à utiliser lors de l'impression des zones de couche extérieure situées immédiatement au-dessus du support. Une vitesse de ventilateur élevée facilite le retrait du support."
+
#: fdmprinter.def.json
msgctxt "support_use_towers label"
msgid "Use Towers"
@@ -3743,6 +3860,16 @@ msgctxt "brim_line_count description"
msgid "The number of lines used for a brim. More brim lines enhance adhesion to the build plate, but also reduces the effective print area."
msgstr "Le nombre de lignes utilisées pour une bordure. Un plus grand nombre de lignes de bordure renforce l'adhérence au plateau mais réduit également la zone d'impression réelle."
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support label"
+msgid "Brim Replaces Support"
+msgstr "La bordure remplace le support"
+
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support description"
+msgid "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions of the first layer of support by brim regions."
+msgstr "Appliquer la bordure à imprimer autour du modèle même si cet espace aurait autrement dû être occupé par le support, en remplaçant certaines régions de la première couche de support par des régions de la bordure."
+
#: fdmprinter.def.json
msgctxt "brim_outside_only label"
msgid "Brim Only on Outside"
@@ -3885,8 +4012,8 @@ msgstr "Largeur des lignes de la couche de base du radeau. Elles doivent être
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing label"
-msgid "Raft Line Spacing"
-msgstr "Interligne du radeau"
+msgid "Raft Base Line Spacing"
+msgstr "Espacement des lignes de base du radeau"
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing description"
@@ -4103,16 +4230,6 @@ msgctxt "prime_tower_min_volume description"
msgid "The minimum volume for each layer of the prime tower in order to purge enough material."
msgstr "Le volume minimum pour chaque touche de la tour primaire afin de purger suffisamment de matériau."
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness label"
-msgid "Prime Tower Thickness"
-msgstr "Épaisseur de la tour primaire"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness description"
-msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
-msgstr "L'épaisseur de la tour primaire creuse. Une épaisseur supérieure à la moitié du volume minimum de la tour primaire résultera en une tour primaire dense."
-
#: fdmprinter.def.json
msgctxt "prime_tower_position_x label"
msgid "Prime Tower X Position"
@@ -4153,26 +4270,6 @@ msgctxt "prime_tower_wipe_enabled description"
msgid "After printing the prime tower with one nozzle, wipe the oozed material from the other nozzle off on the prime tower."
msgstr "Après l'impression de la tour primaire à l'aide d'une buse, nettoyer le matériau qui suinte de l'autre buse sur la tour primaire."
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe label"
-msgid "Wipe Nozzle After Switch"
-msgstr "Essuyer la buse après chaque changement"
-
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe description"
-msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
-msgstr "Après un changement d'extrudeuse, essuie le matériau qui suinte de la buse sur la première chose imprimée. Cela exécute un mouvement de nettoyage lent et sûr à l'endroit auquel le matériau qui suinte cause le moins de dommages à la qualité de la surface de votre impression."
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume label"
-msgid "Prime Tower Purge Volume"
-msgstr "Volume de purge de la tour primaire"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume description"
-msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
-msgstr "Quantité de filament à purger lors de l'essuyage de la tour primaire. La purge est utile pour compenser le filament perdu par la suinte pendant l'inactivité de la buse."
-
#: fdmprinter.def.json
msgctxt "ooze_shield_enabled label"
msgid "Enable Ooze Shield"
@@ -4658,6 +4755,16 @@ msgctxt "material_flow_temp_graph description"
msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
msgstr "Données reliant le flux de matériau (en mm3 par seconde) à la température (degrés Celsius)."
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference label"
+msgid "Minimum Polygon Circumference"
+msgstr "Circonférence minimale du polygone"
+
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference description"
+msgid "Polygons in sliced layers that have a circumference smaller than this amount will be filtered out. Lower values lead to higher resolution mesh at the cost of slicing time. It is meant mostly for high resolution SLA printers and very tiny 3D models with a lot of details."
+msgstr "Les polygones en couches tranchées dont la circonférence est inférieure à cette valeur seront filtrés. Des valeurs élevées permettent d'obtenir un maillage de meilleure résolution mais augmentent le temps de découpe. Cette option est principalement destinée aux imprimantes SLA haute résolution et aux modèles 3D de très petite taille avec beaucoup de détails."
+
#: fdmprinter.def.json
msgctxt "meshfix_maximum_resolution label"
msgid "Maximum Resolution"
@@ -5315,6 +5422,26 @@ msgctxt "adaptive_layer_height_threshold description"
msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
msgstr "Limite indiquant d'utiliser ou non une couche plus petite. Ce nombre est comparé à la tangente de la pente la plus raide d'une couche."
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle label"
+msgid "Overhanging Wall Angle"
+msgstr "Angle de parois en porte-à-faux"
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle description"
+msgid "Walls that overhang more than this angle will be printed using overhanging wall settings. When the value is 90, no walls will be treated as overhanging."
+msgstr "Les parois ayant un angle supérieur à cette valeur seront imprimées en utilisant les paramètres de parois en porte-à-faux. Si la valeur est 90, aucune paroi ne sera considérée comme étant en porte-à-faux."
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor label"
+msgid "Overhanging Wall Speed"
+msgstr "Vitesse de paroi en porte-à-faux"
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor description"
+msgid "Overhanging walls will be printed at this percentage of their normal print speed."
+msgstr "Les parois en porte-à-faux seront imprimées à ce pourcentage de leur vitesse d'impression normale."
+
#: fdmprinter.def.json
msgctxt "bridge_settings_enabled label"
msgid "Enable Bridge Settings"
@@ -5345,16 +5472,6 @@ msgctxt "bridge_skin_support_threshold description"
msgid "If a skin region is supported for less than this percentage of its area, print it using the bridge settings. Otherwise it is printed using the normal skin settings."
msgstr "Si une région de couche extérieure est supportée pour une valeur inférieure à ce pourcentage de sa surface, elle sera imprimée selon les paramètres du pont. Sinon, elle sera imprimée selon les paramètres normaux de la couche extérieure."
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang label"
-msgid "Bridge Wall Max Overhang"
-msgstr "Porte-à-faux max. de la paroi du pont"
-
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang description"
-msgid "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings."
-msgstr "Largeur maximale autorisée de la zone d'air sous une ligne de paroi avant que la paroi ne soit imprimée selon les paramètres du pont. Exprimée en pourcentage de la largeur de la ligne de paroi. Si la zone d'air est plus large, la ligne de paroi sera imprimée selon les paramètres du pont. Sinon, la ligne de paroi sera imprimée selon les paramètres normaux. Plus la valeur est faible, plus il est probable que les lignes de paroi en surplomb seront imprimées selon les paramètres du pont."
-
#: fdmprinter.def.json
msgctxt "bridge_wall_coast label"
msgid "Bridge Wall Coasting"
@@ -5575,6 +5692,74 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Matrice de transformation à appliquer au modèle lors de son chargement depuis le fichier."
+#~ msgctxt "connect_skin_polygons description"
+#~ msgid "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happend midway over infill this feature can reduce the top surface quality."
+#~ msgstr "Relier les voies de couche extérieure supérieures / inférieures lorsqu'elles sont côte à côte. Pour le motif concentrique, ce paramètre réduit considérablement le temps de parcours, mais comme les liens peuvent se trouver à mi-chemin sur le remplissage, cette fonctionnalité peut réduire la qualité de la surface supérieure."
+
+#~ msgctxt "infill_pattern description"
+#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
+#~ msgstr "Motif du matériau de remplissage de l'impression. La ligne et le remplissage en zigzag changent de sens à chaque alternance de couche, réduisant ainsi les coûts matériels. Les motifs en grille, en triangle, trihexagonaux, cubiques, octaédriques, quart cubiques et concentriques sont entièrement imprimés sur chaque couche. Les remplissages cubique, quart cubique et octaédrique changent à chaque couche afin d'offrir une répartition plus égale de la solidité dans chaque direction."
+
+#~ msgctxt "infill_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concentrique 3D"
+
+#~ msgctxt "retraction_combing description"
+#~ msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
+#~ msgstr "Les détours (le 'combing') maintiennent le bec dans les zones déjà imprimées lors des déplacements. Cela résulte en des déplacements légèrement plus longs mais réduit le recours aux rétractions. Si les détours sont désactivés, le matériau se rétractera et le bec se déplacera en ligne droite jusqu'au point suivant. Il est également possible d'éviter les détours sur les zones de la couche du dessus / dessous en effectuant les détours uniquement dans le remplissage."
+
+#~ msgctxt "support_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concentrique 3D"
+
+#~ msgctxt "support_interface_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concentrique 3D"
+
+#~ msgctxt "support_roof_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concentrique 3D"
+
+#~ msgctxt "support_bottom_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "Concentrique 3D"
+
+#~ msgctxt "raft_base_line_spacing label"
+#~ msgid "Raft Line Spacing"
+#~ msgstr "Interligne du radeau"
+
+#~ msgctxt "prime_tower_wall_thickness label"
+#~ msgid "Prime Tower Thickness"
+#~ msgstr "Épaisseur de la tour primaire"
+
+#~ msgctxt "prime_tower_wall_thickness description"
+#~ msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
+#~ msgstr "L'épaisseur de la tour primaire creuse. Une épaisseur supérieure à la moitié du volume minimum de la tour primaire résultera en une tour primaire dense."
+
+#~ msgctxt "dual_pre_wipe label"
+#~ msgid "Wipe Nozzle After Switch"
+#~ msgstr "Essuyer la buse après chaque changement"
+
+#~ msgctxt "dual_pre_wipe description"
+#~ msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
+#~ msgstr "Après un changement d'extrudeuse, essuie le matériau qui suinte de la buse sur la première chose imprimée. Cela exécute un mouvement de nettoyage lent et sûr à l'endroit auquel le matériau qui suinte cause le moins de dommages à la qualité de la surface de votre impression."
+
+#~ msgctxt "prime_tower_purge_volume label"
+#~ msgid "Prime Tower Purge Volume"
+#~ msgstr "Volume de purge de la tour primaire"
+
+#~ msgctxt "prime_tower_purge_volume description"
+#~ msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
+#~ msgstr "Quantité de filament à purger lors de l'essuyage de la tour primaire. La purge est utile pour compenser le filament perdu par la suinte pendant l'inactivité de la buse."
+
+#~ msgctxt "bridge_wall_max_overhang label"
+#~ msgid "Bridge Wall Max Overhang"
+#~ msgstr "Porte-à-faux max. de la paroi du pont"
+
+#~ msgctxt "bridge_wall_max_overhang description"
+#~ msgid "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings."
+#~ msgstr "Largeur maximale autorisée de la zone d'air sous une ligne de paroi avant que la paroi ne soit imprimée selon les paramètres du pont. Exprimée en pourcentage de la largeur de la ligne de paroi. Si la zone d'air est plus large, la ligne de paroi sera imprimée selon les paramètres du pont. Sinon, la ligne de paroi sera imprimée selon les paramètres normaux. Plus la valeur est faible, plus il est probable que les lignes de paroi en surplomb seront imprimées selon les paramètres du pont."
+
#~ msgctxt "optimize_wall_printing_order description"
#~ msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization."
#~ msgstr "Optimiser l'ordre dans lequel des parois sont imprimées de manière à réduire le nombre de retraits et les distances parcourues. La plupart des pièces bénéficieront de cette possibilité, mais certaines peuvent en fait prendre plus de temps à l'impression ; veuillez dès lors comparer les estimations de durée d'impression avec et sans optimisation."
diff --git a/resources/i18n/it_IT/cura.po b/resources/i18n/it_IT/cura.po
index 2565825b95..d285cbfdc3 100644
--- a/resources/i18n/it_IT/cura.po
+++ b/resources/i18n/it_IT/cura.po
@@ -5,16 +5,18 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0200\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0100\n"
+"PO-Revision-Date: 2018-09-28 15:01+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 2.0.6\n"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:22
msgctxt "@action"
@@ -38,6 +40,17 @@ msgctxt "@item:inlistbox"
msgid "G-code File"
msgstr "File G-Code"
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:67
+msgctxt "@error:not supported"
+msgid "GCodeWriter does not support non-text mode."
+msgstr "GCodeWriter non supporta la modalità non di testo."
+
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:73
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:89
+msgctxt "@warning:status"
+msgid "Please prepare G-code before exporting."
+msgstr "Preparare il codice G prima dell’esportazione."
+
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
msgctxt "@info:title"
msgid "3D Model Assistant"
@@ -53,102 +66,51 @@ msgid ""
"
"
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
-msgctxt "@action:button"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Stampa con Doodle3D WiFi-Box"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:66
-msgctxt "@properties:tooltip"
-msgid "Print with Doodle3D WiFi-Box"
-msgstr "Stampa con Doodle3D WiFi-Box"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:86
-msgctxt "@info:status"
-msgid "Connecting to Doodle3D Connect"
-msgstr "Collegamento a Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:258
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:204
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:398
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:88
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
-#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
-#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:275
-msgctxt "@action:button"
-msgid "Cancel"
-msgstr "Annulla"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:154
-msgctxt "@info:status"
-msgid "Sending data to Doodle3D Connect"
-msgstr "Invio dati a Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:161
-msgctxt "@info:status"
-msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
-msgstr "Impossibile inviare dati a Doodle3D Connect. C'è un altro processo in corso?"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:175
-msgctxt "@info:status"
-msgid "Storing data on Doodle3D Connect"
-msgstr "Memorizzazione dati su Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:213
-msgctxt "@info:status"
-msgid "File sent to Doodle3D Connect"
-msgstr "File inviato a Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@action:button"
-msgid "Open Connect..."
-msgstr "Apri Connect..."
-
-#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:214
-msgctxt "@info:tooltip"
-msgid "Open the Doodle3D Connect web interface"
-msgstr "Apri interfaccia web Doodle3D Connect"
-
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:32
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Visualizza registro modifiche"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:20
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py:25
+msgctxt "@action"
+msgid "Update Firmware"
+msgstr "Aggiornamento firmware"
+
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:23
msgctxt "@item:inmenu"
msgid "Flatten active settings"
msgstr "Impostazioni attive profilo appiattito"
-#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:32
+#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:35
msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr "Il profilo è stato appiattito e attivato."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:40
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:32
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "Stampa USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:41
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:33
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "Stampa tramite USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:42
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:34
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "Stampa tramite USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:83
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:69
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Connesso tramite USB"
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:92
+msgctxt "@label"
+msgid "A USB print is in progress, closing Cura will stop this print. Are you sure?"
+msgstr "Stampa tramite USB in corso, la chiusura di Cura interrompe la stampa. Confermare?"
+
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/install/X3GWriter/__init__.py:15
#: /home/ruben/Projects/Cura/plugins/X3GWriter/__init__.py:15
msgctxt "X3G Writer File Description"
@@ -171,7 +133,12 @@ msgctxt "@item:inlistbox"
msgid "Compressed G-code File"
msgstr "File G-Code compresso"
-#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/GCodeGzWriter.py:38
+msgctxt "@error:not supported"
+msgid "GCodeGzWriter does not support text mode."
+msgstr "GCodeGzWriter non supporta la modalità di testo."
+
+#: /home/ruben/Projects/Cura/plugins/UFPWriter/__init__.py:28
msgctxt "@item:inlistbox"
msgid "Ultimaker Format Package"
msgstr "Pacchetto formato Ultimaker"
@@ -193,7 +160,7 @@ msgid "Save to Removable Drive {0}"
msgstr "Salva su unità rimovibile {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:64
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:133
msgctxt "@info:status"
msgid "There are no file formats available to write with!"
msgstr "Non ci sono formati di file disponibili per la scrittura!"
@@ -232,7 +199,7 @@ msgstr "Impossibile salvare su unità rimovibile {0}: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:137
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:133
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:140
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1592
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1607
msgctxt "@info:title"
msgid "Error"
msgstr "Errore"
@@ -261,8 +228,8 @@ msgstr "Rimuovi il dispositivo rimovibile {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:151
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:163
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1582
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1681
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1597
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1695
msgctxt "@info:title"
msgid "Warning"
msgstr "Avvertenza"
@@ -289,259 +256,269 @@ msgctxt "@item:intext"
msgid "Removable Drive"
msgstr "Unità rimovibile"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:70
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:78
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:88
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "Stampa sulla rete"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:71
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:79
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:74
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:89
msgctxt "@properties:tooltip"
msgid "Print over network"
msgstr "Stampa sulla rete"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:84
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:87
msgctxt "@info:status"
msgid "Connected over the network."
msgstr "Collegato alla rete."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:87
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:90
msgctxt "@info:status"
msgid "Connected over the network. Please approve the access request on the printer."
msgstr "Collegato alla rete. Si prega di approvare la richiesta di accesso sulla stampante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:89
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:92
msgctxt "@info:status"
msgid "Connected over the network. No access to control the printer."
msgstr "Collegato alla rete. Nessun accesso per controllare la stampante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:94
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:97
msgctxt "@info:status"
msgid "Access to the printer requested. Please approve the request on the printer"
msgstr "Richiesto accesso alla stampante. Approvare la richiesta sulla stampante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:97
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:100
msgctxt "@info:title"
msgid "Authentication status"
msgstr "Stato di autenticazione"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:99
-msgctxt "@info:status"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:100
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:106
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:110
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:108
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:112
msgctxt "@info:title"
msgid "Authentication Status"
msgstr "Stato di autenticazione"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:101
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:103
msgctxt "@action:button"
msgid "Retry"
msgstr "Riprova"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:102
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:104
msgctxt "@info:tooltip"
msgid "Re-send the access request"
msgstr "Invia nuovamente la richiesta di accesso"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:105
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:107
msgctxt "@info:status"
msgid "Access to the printer accepted"
msgstr "Accesso alla stampante accettato"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:109
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:111
msgctxt "@info:status"
msgid "No access to print with this printer. Unable to send print job."
msgstr "Nessun accesso per stampare con questa stampante. Impossibile inviare il processo di stampa."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:111
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:29
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:73
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:113
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:33
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:70
msgctxt "@action:button"
msgid "Request Access"
msgstr "Richiesta di accesso"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:113
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:28
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:72
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:115
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:34
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:71
msgctxt "@info:tooltip"
msgid "Send access request to the printer"
msgstr "Invia la richiesta di accesso alla stampante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:198
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:200
msgctxt "@label"
msgid "Unable to start a new print job."
msgstr "Impossibile avviare un nuovo processo di stampa."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:200
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:202
msgctxt "@label"
msgid "There is an issue with the configuration of your Ultimaker, which makes it impossible to start the print. Please resolve this issues before continuing."
msgstr "È presente un problema di configurazione della stampante che rende impossibile l’avvio della stampa. Risolvere il problema prima di continuare."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:206
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:228
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:208
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:230
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "Mancata corrispondenza della configurazione"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:220
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:222
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "Sei sicuro di voler stampare con la configurazione selezionata?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:222
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:224
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Le configurazioni o la calibrazione della stampante e di Cura non corrispondono. Per ottenere i migliori risultati, sezionare sempre per i PrintCore e i materiali inseriti nella stampante utilizzata."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:249
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:166
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:251
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:199
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "Invio nuovi processi (temporaneamente) bloccato, invio in corso precedente processo di stampa."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:256
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:185
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:202
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:258
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:234
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "Invio dati alla stampante in corso"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:257
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:186
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:203
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:259
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:219
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:235
msgctxt "@info:title"
msgid "Sending Data"
msgstr "Invio dati"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:321
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:260
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:236
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml:18
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:381
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:20
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:143
+#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:87
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:279
+msgctxt "@action:button"
+msgid "Cancel"
+msgstr "Annulla"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:323
#, python-brace-format
msgctxt "@info:status"
msgid "No Printcore loaded in slot {slot_number}"
msgstr "Nessun PrintCore caricato nello slot {slot_number}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:327
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:329
#, python-brace-format
msgctxt "@info:status"
msgid "No material loaded in slot {slot_number}"
msgstr "Nessun materiale caricato nello slot {slot_number}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:350
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:352
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {cura_printcore_name}, Printer: {remote_printcore_name}) selected for extruder {extruder_id}"
msgstr "PrintCore diverso (Cura: {cura_printcore_name}, Stampante: {remote_printcore_name}) selezionata per estrusore {extruder_id}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:359
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:361
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "Materiale diverso (Cura: {0}, Stampante: {1}) selezionato per l’estrusore {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:545
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:547
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "Sincronizzazione con la stampante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:547
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:549
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "Desideri utilizzare la configurazione corrente della tua stampante in Cura?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:549
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/LegacyUM3OutputDevice.py:551
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "I PrintCore e/o i materiali sulla stampante differiscono da quelli contenuti nel tuo attuale progetto. Per ottenere i risultati migliori, sezionare sempre per i PrintCore e i materiali inseriti nella stampante utilizzata."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:81
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:91
msgctxt "@info:status"
msgid "Connected over the network"
-msgstr "Collegato alla rete."
+msgstr "Collegato alla rete"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:303
msgctxt "@info:status"
msgid "Print job was successfully sent to the printer."
msgstr "Processo di stampa inviato con successo alla stampante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:264
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:305
msgctxt "@info:title"
msgid "Data Sent"
msgstr "Dati inviati"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:265
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:306
msgctxt "@action:button"
msgid "View in Monitor"
msgstr "Visualizzazione in Controlla"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:353
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:422
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "La stampante '{printer_name}' ha finito di stampare '{job_name}'."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:355
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:424
#, python-brace-format
msgctxt "@info:status"
msgid "The print job '{job_name}' was finished."
msgstr "Il processo di stampa '{job_name}' è terminato."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:356
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py:425
msgctxt "@info:status"
msgid "Print finished"
msgstr "Stampa finita"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:20
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py:26
msgctxt "@action"
msgid "Connect via Network"
msgstr "Collega tramite rete"
-#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:13
msgctxt "@item:inmenu"
msgid "Monitor"
msgstr "Controlla"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:69
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:119
+msgctxt "@info"
+msgid "Could not access update information."
+msgstr "Non è possibile accedere alle informazioni di aggiornamento."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:17
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr "Sono disponibili nuove funzioni per la {machine_name}! Si consiglia di aggiornare il firmware sulla stampante."
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:73
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:21
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "Nuovo firmware %s disponibile"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:76
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py:27
msgctxt "@action:button"
msgid "How to update"
msgstr "Modalità di aggiornamento"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:92
-msgctxt "@info"
-msgid "Could not access update information."
-msgstr "Non è possibile accedere alle informazioni di aggiornamento."
-
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Layer view"
msgstr "Visualizzazione strato"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:113
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "Cura non visualizza in modo accurato gli strati se la funzione Wire Printing è abilitata"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:114
msgctxt "@info:title"
msgid "Simulation View"
msgstr "Vista simulazione"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:27
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:35
msgid "Modify G-Code"
msgstr "Modifica G-code"
@@ -555,32 +532,32 @@ msgctxt "@info:tooltip"
msgid "Create a volume in which supports are not printed."
msgstr "Crea un volume in cui i supporti non vengono stampati."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:44
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
msgctxt "@info"
msgid "Cura collects anonymized usage statistics."
msgstr "Cura raccoglie statistiche di utilizzo in forma anonima."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:47
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:55
msgctxt "@info:title"
msgid "Collecting Data"
msgstr "Acquisizione dati"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:57
msgctxt "@action:button"
msgid "More info"
msgstr "Per saperne di più"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:58
msgctxt "@action:tooltip"
msgid "See more information on what data Cura sends."
msgstr "Vedere ulteriori informazioni sui dati inviati da Cura."
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:60
msgctxt "@action:button"
msgid "Allow"
msgstr "Consenti"
-#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:53
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:61
msgctxt "@action:tooltip"
msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
msgstr "Consente a Cura di inviare in forma anonima statistiche d’uso, riguardanti alcune delle preferenze e impostazioni, la versione cura e una serie di modelli in sezionamento, per aiutare a dare priorità a miglioramenti futuri in Cura."
@@ -590,18 +567,6 @@ msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Profili Cura 15.04"
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
-msgctxt "@item:inlistbox"
-msgid "Blender file"
-msgstr "File Blender"
-
-#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
-msgctxt "@info:status"
-msgid ""
-"Could not export using \"{}\" quality!\n"
-"Felt back to \"{}\"."
-msgstr "Impossibile esportare utilizzando qualità \"{}\" quality!\nTornato a \"{}\"."
-
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "JPG Image"
@@ -627,49 +592,56 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "Immagine GIF"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "Impossibile eseguire il sezionamento con il materiale corrente in quanto incompatibile con la macchina o la configurazione selezionata."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:315
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:344
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:367
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:376
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:332
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:363
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:387
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:396
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:405
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:414
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "Sezionamento impossibile"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:343
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:362
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "Impossibile eseguire il sezionamento con le impostazioni attuali. Le seguenti impostazioni presentano errori: {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:386
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr "Impossibile eseguire il sezionamento a causa di alcune impostazioni per modello. Le seguenti impostazioni presentano errori su uno o più modelli: {error_labels}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:375
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:395
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "Impossibile eseguire il sezionamento perché la torre di innesco o la posizione di innesco non sono valide."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:385
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:404
+#, python-format
+msgctxt "@info:status"
+msgid "Unable to slice because there are objects associated with disabled Extruder %s."
+msgstr "Impossibile effettuare il sezionamento in quanto vi sono oggetti associati a Extruder %s disabilitato."
+
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:413
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "Nulla da sezionare in quanto nessuno dei modelli corrisponde al volume di stampa. Ridimensionare o ruotare i modelli secondo necessità."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "Elaborazione dei livelli"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:243
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:255
msgctxt "@info:title"
msgid "Information"
msgstr "Informazioni"
@@ -685,29 +657,40 @@ msgid "Configure Per Model Settings"
msgstr "Configura impostazioni per modello"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:175
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:575
msgctxt "@title:tab"
msgid "Recommended"
msgstr "Consigliata"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:177
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:580
msgctxt "@title:tab"
msgid "Custom"
msgstr "Personalizzata"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:32
-#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:28
+#: /home/ruben/Projects/Cura/plugins/3MFReader/__init__.py:34
msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "File 3MF"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:199
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:695
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:714
msgctxt "@label"
msgid "Nozzle"
msgstr "Ugello"
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:468
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Project file {0} contains an unknown machine type {1}. Cannot import the machine. Models will be imported instead."
+msgstr "Il file di progetto {0} contiene un tipo di macchina sconosciuto {1}. Impossibile importare la macchina. Verranno invece importati i modelli."
+
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:471
+msgctxt "@info:title"
+msgid "Open Project File"
+msgstr "Apri file progetto"
+
#: /home/ruben/Projects/Cura/plugins/SolidView/__init__.py:12
msgctxt "@item:inmenu"
msgid "Solid view"
@@ -718,18 +701,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "File G"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:322
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "Parsing codice G"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:324
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:470
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:326
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:474
msgctxt "@info:title"
msgid "G-code Details"
msgstr "Dettagli codice G"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:468
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:472
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "Verifica che il codice G sia idoneo alla tua stampante e alla sua configurazione prima di trasmettere il file. La rappresentazione del codice G potrebbe non essere accurata."
@@ -740,27 +723,27 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Profilo Cura"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:26
msgctxt "@item:inlistbox"
msgid "3MF file"
msgstr "File 3MF"
-#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:38
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:34
msgctxt "@item:inlistbox"
msgid "Cura Project 3MF file"
msgstr "File 3MF Progetto Cura"
+#: /home/ruben/Projects/Cura/plugins/3MFWriter/ThreeMFWriter.py:179
+msgctxt "@error:zip"
+msgid "Error writing 3mf file."
+msgstr "Errore scrittura file 3MF."
+
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelection.py:17
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelection.py:18
msgctxt "@action"
msgid "Select upgrades"
msgstr "Seleziona aggiornamenti"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.py:12
-msgctxt "@action"
-msgid "Upgrade Firmware"
-msgstr "Aggiorna firmware"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py:14
msgctxt "@action"
msgid "Checkup"
@@ -771,79 +754,79 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "Livella piano di stampa"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:82
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "Parete esterna"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:83
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "Pareti interne"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:84
msgctxt "@tooltip"
msgid "Skin"
msgstr "Rivestimento esterno"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:85
msgctxt "@tooltip"
msgid "Infill"
msgstr "Riempimento"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:86
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "Riempimento del supporto"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:87
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "Interfaccia supporto"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:88
msgctxt "@tooltip"
msgid "Support"
msgstr "Supporto"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
msgctxt "@tooltip"
msgid "Skirt"
msgstr "Skirt"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
msgctxt "@tooltip"
msgid "Travel"
msgstr "Spostamenti"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
msgctxt "@tooltip"
msgid "Retractions"
msgstr "Retrazioni"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
msgctxt "@tooltip"
msgid "Other"
msgstr "Altro"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:229
-msgctxt "@label unknown material"
-msgid "Unknown"
-msgstr "Sconosciuto"
-
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:313
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:310
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "File pre-sezionato {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:235
+#: /home/ruben/Projects/Cura/cura/API/Account.py:71
+msgctxt "@info:title"
+msgid "Login failed"
+msgstr "Login non riuscito"
+
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:201
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "Il file esiste già"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:236
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:202
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:122
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
@@ -855,23 +838,23 @@ msgctxt "@menuitem"
msgid "Not overridden"
msgstr "Non sottoposto a override"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:119
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "Il materiale selezionato è incompatibile con la macchina o la configurazione selezionata."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:120
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
msgctxt "@info:title"
msgid "Incompatible Material"
msgstr "Materiale incompatibile"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:842
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:866
#, python-format
msgctxt "@info:generic"
msgid "Settings have been changed to match the current availability of extruders: [%s]"
msgstr "Le impostazioni sono state modificate in base all’attuale disponibilità di estrusori: [%s]"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:844
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:868
msgctxt "@info:title"
msgid "Settings updated"
msgstr "Impostazioni aggiornate"
@@ -900,8 +883,6 @@ msgid "Export succeeded"
msgstr "Esportazione riuscita"
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:170
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
@@ -909,58 +890,70 @@ msgstr "Impossibile importare il profilo da {0}: {
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:190
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "No custom profile to import in file {0}"
msgstr "Nessun profilo personalizzato da importare nel file {0}"
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:194
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags !"
+msgid "Failed to import profile from {0}:"
+msgstr "Impossibile importare il profilo da {0}:"
+
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:218
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:228
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "This profile {0} contains incorrect data, could not import it."
msgstr "Questo profilo {0} contiene dati errati, impossibile importarlo."
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:241
#, python-brace-format
-msgctxt "@info:status Don't translate the XML tags or !"
+msgctxt "@info:status Don't translate the XML tags !"
msgid "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it."
-msgstr "La macchina definita nel profilo {0} ({1}) non corrisponde alla macchina corrente ({2}), impossibile importarlo."
+msgstr "La macchina definita nel profilo {0} ({1}) non corrisponde alla macchina corrente ({2}), impossibile importarla."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:316
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:312
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Failed to import profile from {0}:"
+msgstr "Impossibile importare il profilo da {0}:"
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:315
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "Profilo importato correttamente {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:319
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:318
#, python-brace-format
msgctxt "@info:status"
msgid "File {0} does not contain any valid profile."
msgstr "Il file {0} non contiene nessun profilo valido."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:322
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "Il profilo {0} ha un tipo di file sconosciuto o corrotto."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:340
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:339
msgctxt "@label"
msgid "Custom profile"
msgstr "Profilo personalizzato"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:356
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:355
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "Il profilo è privo del tipo di qualità."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:368
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:369
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "Impossibile trovare un tipo qualità {0} per la configurazione corrente."
-#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:60
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:63
#, python-brace-format
msgctxt "@label"
msgid "Group #{group_nr}"
@@ -987,42 +980,42 @@ msgctxt "@item:inlistbox"
msgid "All Files (*)"
msgstr "Tutti i file (*)"
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:544
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:636
msgctxt "@label"
msgid "Custom Material"
msgstr "Materiale personalizzato"
-#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:545
+#: /home/ruben/Projects/Cura/cura/Machines/MaterialManager.py:637
msgctxt "@label"
msgid "Custom"
msgstr "Personalizzata"
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:80
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:81
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
msgstr "L’altezza del volume di stampa è stata ridotta a causa del valore dell’impostazione \"Sequenza di stampa” per impedire la collisione del gantry con i modelli stampati."
-#: /home/ruben/Projects/Cura/cura/BuildVolume.py:82
+#: /home/ruben/Projects/Cura/cura/BuildVolume.py:83
msgctxt "@info:title"
msgid "Build Volume"
msgstr "Volume di stampa"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:98
msgctxt "@info:backup_failed"
msgid "Could not create archive from user data directory: {}"
msgstr "Impossibile creare un archivio dalla directory dei dati utente: {}"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:103
msgctxt "@info:title"
msgid "Backup"
msgstr "Backup"
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:113
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup without having proper data or meta data."
msgstr "Tentativo di ripristinare un backup di Cura senza dati o metadati appropriati."
-#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
+#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:123
msgctxt "@info:backup_failed"
msgid "Tried to restore a Cura backup that does not match your current version."
msgstr "Tentativo di ripristinare un backup di Cura non corrispondente alla versione corrente."
@@ -1033,32 +1026,32 @@ msgid "Multiplying and placing objects"
msgstr "Moltiplicazione e collocazione degli oggetti"
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:28
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
msgctxt "@info:title"
msgid "Placing Object"
msgstr "Sistemazione oggetto"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:99
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:100
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:96
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:149
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
msgctxt "@info:status"
msgid "Unable to find a location within the build volume for all objects"
msgstr "Impossibile individuare una posizione nel volume di stampa per tutti gli oggetti"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:30
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:66
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:67
msgctxt "@info:status"
msgid "Finding new location for objects"
msgstr "Ricerca nuova posizione per gli oggetti"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:34
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:70
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:71
msgctxt "@info:title"
msgid "Finding Location"
msgstr "Ricerca posizione"
#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:97
-#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:150
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:151
msgctxt "@info:title"
msgid "Can't Find Location"
msgstr "Impossibile individuare posizione"
@@ -1189,223 +1182,233 @@ msgctxt "@action:button"
msgid "Send report"
msgstr "Invia report"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:328
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:473
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Caricamento macchine in corso..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:756
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:775
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "Impostazione scena in corso..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:789
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:811
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Caricamento interfaccia in corso..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1023
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1037
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1581
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1596
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "È possibile caricare un solo file codice G per volta. Importazione saltata {0}"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1591
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1606
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "Impossibile aprire altri file durante il caricamento del codice G. Importazione saltata {0}"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1680
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1694
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr "Il modello selezionato è troppo piccolo per il caricamento."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:59
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:61
msgctxt "@title"
msgid "Machine Settings"
msgstr "Impostazioni macchina"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:78
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:80
msgctxt "@title:tab"
msgid "Printer"
msgstr "Stampante"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:97
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:99
msgctxt "@label"
msgid "Printer Settings"
msgstr "Impostazioni della stampante"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:108
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:110
msgctxt "@label"
msgid "X (Width)"
msgstr "X (Larghezza)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:109
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:384
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:418
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:430
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:855
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:111
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:121
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:131
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:237
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:386
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:402
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:428
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:440
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:896
msgctxt "@label"
msgid "mm"
msgstr "mm"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:118
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:120
msgctxt "@label"
msgid "Y (Depth)"
msgstr "Y (Profondità)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:128
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:130
msgctxt "@label"
msgid "Z (Height)"
msgstr "Z (Altezza)"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:140
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:142
msgctxt "@label"
msgid "Build plate shape"
msgstr "Forma del piano di stampa"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:149
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:151
msgctxt "@option:check"
msgid "Origin at center"
msgstr "Origine al centro"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:157
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:159
msgctxt "@option:check"
msgid "Heated bed"
msgstr "Piano riscaldato"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:168
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:170
msgctxt "@label"
msgid "G-code flavor"
msgstr "Versione codice G"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:181
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:183
msgctxt "@label"
msgid "Printhead Settings"
msgstr "Impostazioni della testina di stampa"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:191
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:193
msgctxt "@label"
msgid "X min"
msgstr "X min"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:192
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:194
msgctxt "@tooltip"
msgid "Distance from the left of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distanza tra il lato sinistro della testina di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testina di stampa durante la stampa \"Uno alla volta\"."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:201
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:203
msgctxt "@label"
msgid "Y min"
msgstr "Y min"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:202
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:204
msgctxt "@tooltip"
msgid "Distance from the front of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distanza tra il lato anteriore della testina di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testina di stampa durante la stampa \"Uno alla volta\"."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:211
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:213
msgctxt "@label"
msgid "X max"
msgstr "X max"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:212
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:214
msgctxt "@tooltip"
msgid "Distance from the right of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distanza tra il lato destro della testina di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testina di stampa durante la stampa \"Uno alla volta\"."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:221
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:223
msgctxt "@label"
msgid "Y max"
msgstr "Y max"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:222
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:224
msgctxt "@tooltip"
msgid "Distance from the rear of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
msgstr "Distanza tra il lato posteriore della testina di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testina di stampa durante la stampa \"Uno alla volta\"."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:234
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
msgctxt "@label"
msgid "Gantry height"
msgstr "Altezza gantry"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:236
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:238
msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "La differenza di altezza tra la punta dell’ugello e il sistema gantry (assi X e Y). Utilizzata per evitare collisioni tra le stampe precedenti e il gantry durante la stampa \"Uno alla volta\"."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:257
msgctxt "@label"
msgid "Number of Extruders"
msgstr "Numero di estrusori"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:311
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:313
msgctxt "@label"
msgid "Start G-code"
msgstr "Codice G avvio"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:321
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:323
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very start."
msgstr "Comandi codice G da eseguire all’avvio."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:330
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:332
msgctxt "@label"
msgid "End G-code"
msgstr "Codice G fine"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:340
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:342
msgctxt "@tooltip"
msgid "G-code commands to be executed at the very end."
msgstr "Comandi codice G da eseguire alla fine."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:371
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:373
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Impostazioni ugello"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:385
msgctxt "@label"
msgid "Nozzle size"
msgstr "Dimensione ugello"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:399
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
msgctxt "@label"
msgid "Compatible material diameter"
msgstr "Diametro del materiale compatibile"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:403
msgctxt "@tooltip"
msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
msgstr "Diametro nominale del filamento supportato dalla stampante. Il diametro esatto verrà sovrapposto dal materiale e/o dal profilo."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:417
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:427
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "Scostamento X ugello"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:429
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:439
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Scostamento Y ugello"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:450
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+msgctxt "@label"
+msgid "Cooling Fan Number"
+msgstr "Numero ventola di raffreddamento"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:452
+msgctxt "@label"
+msgid ""
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:472
msgctxt "@label"
msgid "Extruder Start G-code"
msgstr "Codice G avvio estrusore"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:468
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:490
msgctxt "@label"
msgid "Extruder End G-code"
msgstr "Codice G fine estrusore"
@@ -1425,12 +1428,20 @@ msgctxt "@info"
msgid "Could not connect to the Cura Package database. Please check your connection."
msgstr "Impossibile connettersi al database pacchetto Cura. Verificare la connessione."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:38
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:28
msgctxt "@title:tab"
msgid "Plugins"
msgstr "Plugin"
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:75
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:66
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:551
+msgctxt "@title:tab"
+msgid "Materials"
+msgstr "Materiali"
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
msgctxt "@label"
msgid "Version"
@@ -1446,8 +1457,14 @@ msgctxt "@label"
msgid "Author"
msgstr "Autore"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:97
+msgctxt "@label"
+msgid "Downloads"
+msgstr "Download"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:116
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:158
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:258
msgctxt "@label"
msgid "Unknown"
msgstr "Sconosciuto"
@@ -1480,17 +1497,57 @@ msgctxt "@action:button"
msgid "Back"
msgstr "Indietro"
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:20
+msgctxt "@title:window"
+msgid "Confirm uninstall"
+msgstr "Conferma disinstalla"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:50
+msgctxt "@text:window"
+msgid "You are uninstalling materials and/or profiles that are still in use. Confirming will reset the following materials/profiles to their defaults."
+msgstr "Si stanno installando materiali e/o profili ancora in uso. La conferma ripristina i seguenti materiali/profili ai valori predefiniti."
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:51
+msgctxt "@text:window"
+msgid "Materials"
+msgstr "Materiali"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:52
+msgctxt "@text:window"
+msgid "Profiles"
+msgstr "Profili"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml:89
+msgctxt "@action:button"
+msgid "Confirm"
+msgstr "Conferma"
+
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
msgctxt "@info"
msgid "You will need to restart Cura before changes in packages have effect."
msgstr "Riavviare Cura per rendere effettive le modifiche apportate ai pacchetti."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:34
msgctxt "@info:button"
msgid "Quit Cura"
msgstr "Esci da Cura"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Contributions"
+msgstr "Contributi della comunità"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:34
+msgctxt "@label"
+msgid "Community Plugins"
+msgstr "Plugin della comunità"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml:43
+msgctxt "@label"
+msgid "Generic Materials"
+msgstr "Materiali generici"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:56
msgctxt "@title:tab"
msgid "Installed"
msgstr "Installa"
@@ -1533,12 +1590,12 @@ msgctxt "@action:button"
msgid "Decline"
msgstr "Non accetto"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:23
msgctxt "@label"
msgid "Featured"
msgstr "In primo piano"
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:31
msgctxt "@label"
msgid "Compatibility"
msgstr "Compatibilità"
@@ -1548,10 +1605,15 @@ msgctxt "@info"
msgid "Fetching packages..."
msgstr "Recupero dei pacchetti..."
-#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:88
msgctxt "@label"
-msgid "Contact"
-msgstr "Contatto"
+msgid "Website"
+msgstr "Sito web"
+
+#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:94
+msgctxt "@label"
+msgid "Email"
+msgstr "E-mail"
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
msgctxt "@info:tooltip"
@@ -1564,48 +1626,88 @@ msgid "Changelog"
msgstr "Registro modifiche"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:84
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:56
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:464
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:509
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:185
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:53
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:467
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:514
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:121
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:166
#: /home/ruben/Projects/Cura/resources/qml/EngineLog.qml:38
msgctxt "@action:button"
msgid "Close"
msgstr "Chiudi"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:22
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:31
+msgctxt "@title"
+msgid "Update Firmware"
+msgstr "Aggiornamento firmware"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:39
+msgctxt "@label"
+msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
+msgstr "Il firmware è la parte di software eseguita direttamente sulla stampante 3D. Questo firmware controlla i motori passo-passo, regola la temperatura e, in ultima analisi, consente il funzionamento della stampante."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:46
+msgctxt "@label"
+msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
+msgstr "Il firmware inviato a corredo delle nuove stampanti funziona, tuttavia le nuove versioni tendono ad avere più funzioni ed ottimizzazioni."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:58
+msgctxt "@action:button"
+msgid "Automatically upgrade Firmware"
+msgstr "Aggiorna automaticamente il firmware"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:69
+msgctxt "@action:button"
+msgid "Upload custom Firmware"
+msgstr "Carica il firmware personalizzato"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:83
+msgctxt "@label"
+msgid "Firmware can not be updated because there is no connection with the printer."
+msgstr "Impossibile aggiornare il firmware: nessun collegamento con la stampante."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:91
+msgctxt "@label"
+msgid "Firmware can not be updated because the connection with the printer does not support upgrading firmware."
+msgstr "Impossibile aggiornare il firmware: il collegamento con la stampante non supporta l’aggiornamento del firmware."
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:98
+msgctxt "@title:window"
+msgid "Select custom firmware"
+msgstr "Seleziona il firmware personalizzato"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:119
msgctxt "@title:window"
msgid "Firmware Update"
msgstr "Aggiornamento del firmware"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:42
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:143
msgctxt "@label"
msgid "Updating firmware."
msgstr "Aggiornamento firmware."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:44
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:145
msgctxt "@label"
msgid "Firmware update completed."
msgstr "Aggiornamento del firmware completato."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:46
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:147
msgctxt "@label"
msgid "Firmware update failed due to an unknown error."
msgstr "Aggiornamento firmware non riuscito a causa di un errore sconosciuto."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:48
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:149
msgctxt "@label"
msgid "Firmware update failed due to an communication error."
msgstr "Aggiornamento firmware non riuscito a causa di un errore di comunicazione."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:50
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:151
msgctxt "@label"
msgid "Firmware update failed due to an input/output error."
msgstr "Aggiornamento firmware non riuscito a causa di un errore di input/output."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:52
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml:153
msgctxt "@label"
msgid "Firmware update failed due to missing firmware."
msgstr "Aggiornamento firmware non riuscito per firmware mancante."
@@ -1615,22 +1717,22 @@ msgctxt "@title:window"
msgid "User Agreement"
msgstr "Contratto di licenza"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:57
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:46
msgctxt "@window:title"
msgid "Existing Connection"
msgstr "Collegamento esistente"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:59
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:48
msgctxt "@message:text"
msgid "This printer/group is already added to Cura. Please select another printer/group."
msgstr "Stampante/gruppo già aggiunto a Cura. Selezionare un’altra stampante o un altro gruppo."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:76
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:65
msgctxt "@title:window"
msgid "Connect to Networked Printer"
msgstr "Collega alla stampante in rete"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:86
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:75
msgctxt "@label"
msgid ""
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
@@ -1638,333 +1740,395 @@ msgid ""
"Select your printer from the list below:"
msgstr "Per stampare direttamente sulla stampante in rete, verificare che la stampante desiderata sia collegata alla rete mediante un cavo di rete o mediante collegamento alla rete WIFI. Se si collega Cura alla stampante, è comunque possibile utilizzare una chiavetta USB per trasferire i file codice G alla stampante.\n\nSelezionare la stampante dall’elenco seguente:"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:85
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
msgctxt "@action:button"
msgid "Add"
msgstr "Aggiungi"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:95
msgctxt "@action:button"
msgid "Edit"
msgstr "Modifica"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:128
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:48
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:117
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:132
msgctxt "@action:button"
msgid "Remove"
msgstr "Rimuovi"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:125
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:114
msgctxt "@action:button"
msgid "Refresh"
msgstr "Aggiorna"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:218
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:207
msgctxt "@label"
msgid "If your printer is not listed, read the network printing troubleshooting guide"
msgstr "Se la stampante non è nell’elenco, leggere la guida alla risoluzione dei problemi per la stampa in rete"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:245
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:234
msgctxt "@label"
msgid "Type"
msgstr "Tipo"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:282
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:271
msgctxt "@label"
msgid "Firmware version"
msgstr "Versione firmware"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:294
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:283
msgctxt "@label"
msgid "Address"
msgstr "Indirizzo"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:316
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:305
msgctxt "@label"
-msgid "This printer is not set up to host a group of Ultimaker 3 printers."
-msgstr "Questa stampante non è predisposta per comandare un gruppo di stampanti Ultimaker 3."
+msgid "This printer is not set up to host a group of printers."
+msgstr "Questa stampante non è predisposta per comandare un gruppo di stampanti."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:320
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:309
msgctxt "@label"
-msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
-msgstr "Questa stampante comanda un gruppo di %1 stampanti Ultimaker 3."
+msgid "This printer is the host for a group of %1 printers."
+msgstr "Questa stampante comanda un gruppo di %1 stampanti."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:330
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:319
msgctxt "@label"
msgid "The printer at this address has not yet responded."
msgstr "La stampante a questo indirizzo non ha ancora risposto."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:335
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:39
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:324
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:42
msgctxt "@action:button"
msgid "Connect"
msgstr "Collega"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:349
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:338
msgctxt "@title:window"
msgid "Printer Address"
msgstr "Indirizzo stampante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:377
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:361
msgctxt "@alabel"
msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Inserire l’indirizzo IP o l’hostname della stampante sulla rete."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:407
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml:390
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:132
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
msgstr "OK"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:30
-msgctxt "@title:window"
-msgid "Print over network"
-msgstr "Stampa sulla rete"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:61
-msgctxt "@label"
-msgid "Printer selection"
-msgstr "Selezione stampante"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:100
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:44
msgctxt "@action:button"
msgid "Print"
msgstr "Stampa"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:36
-msgctxt "@label: arg 1 is group name"
-msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
-msgstr "%1 non è configurata per supportare la connessione di un gruppo di stampanti Ultimaker 3"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:47
+msgctxt "@title:window"
+msgid "Print over network"
+msgstr "Stampa sulla rete"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
-msgctxt "@label link to connect manager"
-msgid "Add/Remove printers"
-msgstr "Aggiungi/Rimuovi stampanti"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml:79
+msgctxt "@label"
+msgid "Printer selection"
+msgstr "Selezione stampante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
-msgctxt "@info:tooltip"
-msgid "Opens the print jobs page with your default web browser."
-msgstr "Apre la pagina processi di stampa con il browser web predefinito."
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:173
+msgctxt "@label"
+msgid "Not available"
+msgstr "Non disponibile"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:15
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:130
-msgctxt "@action:button"
-msgid "View print jobs"
-msgstr "Visualizza processi di stampa"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:175
+msgctxt "@label"
+msgid "Unreachable"
+msgstr "Non raggiungibile"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:37
-msgctxt "@label:status"
-msgid "Preparing to print"
-msgstr "Preparazione della stampa"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:39
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:263
-msgctxt "@label:status"
-msgid "Printing"
-msgstr "Stampa in corso"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:41
-msgctxt "@label:status"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCard.qml:180
+msgctxt "@label"
msgid "Available"
msgstr "Disponibile"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:37
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:44
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:46
msgctxt "@label:status"
-msgid "Lost connection with the printer"
-msgstr "Persa connessione con la stampante"
+msgid "Aborted"
+msgstr "Interrotto"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:45
-msgctxt "@label:status"
-msgid "Unavailable"
-msgstr "Non disponibile"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label:status"
-msgid "Unknown"
-msgstr "Sconosciuto"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:249
-msgctxt "@label:status"
-msgid "Disabled"
-msgstr "Disabilitato"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:265
-msgctxt "@label:status"
-msgid "Reserved"
-msgstr "Riservato"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:268
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:39
msgctxt "@label:status"
msgid "Finished"
msgstr "Terminato"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:271
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:392
-msgctxt "@label"
-msgid "Preparing to print"
-msgstr "Preparazione della stampa"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:273
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:42
msgctxt "@label:status"
-msgid "Action required"
-msgstr "Richiede un'azione"
+msgid "Preparing"
+msgstr "Preparazione in corso"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:276
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:48
msgctxt "@label:status"
-msgid "Paused"
-msgstr "In pausa"
+msgid "Pausing"
+msgstr "Messa in pausa"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:52
msgctxt "@label:status"
msgid "Resuming"
msgstr "Ripresa"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:280
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrinterCardProgressBar.qml:54
msgctxt "@label:status"
-msgid "Print aborted"
-msgstr "Stampa interrotta"
+msgid "Action required"
+msgstr "Richiede un'azione"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:373
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:394
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:213
msgctxt "@label"
-msgid "Not accepting print jobs"
-msgstr "Mancata accettazione processi di stampa"
+msgid "Waiting for: Unavailable printer"
+msgstr "In attesa: stampante non disponibile"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:387
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:215
msgctxt "@label"
-msgid "Finishes at: "
-msgstr "Finisce alle: "
+msgid "Waiting for: First available"
+msgstr "In attesa della prima disponibile"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:389
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:217
msgctxt "@label"
-msgid "Clear build plate"
-msgstr "Cancellare piano di stampa"
+msgid "Waiting for: "
+msgstr "In attesa: "
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:396
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:299
msgctxt "@label"
-msgid "Waiting for configuration change"
-msgstr "In attesa di modifica configurazione"
+msgid "Configuration change"
+msgstr "Modifica configurazione"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:63
-msgctxt "@title"
-msgid "Print jobs"
-msgstr "Processi di stampa"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:93
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:365
msgctxt "@label"
-msgid "Printing"
-msgstr "Stampa in corso"
+msgid "The assigned printer, %1, requires the following configuration change(s):"
+msgstr "La stampante assegnata, %1, richiede le seguenti modifiche di configurazione:"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:111
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:367
+msgctxt "@label"
+msgid "The printer %1 is assigned, but the job contains an unknown material configuration."
+msgstr "La stampante %1 è assegnata, ma il processo contiene una configurazione materiale sconosciuta."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:375
+msgctxt "@label"
+msgid "Change material %1 from %2 to %3."
+msgstr "Cambia materiale %1 da %2 a %3."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:378
+msgctxt "@label"
+msgid "Load %3 as material %1 (This cannot be overridden)."
+msgstr "Caricare %3 come materiale %1 (Operazione non annullabile)."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:381
+msgctxt "@label"
+msgid "Change print core %1 from %2 to %3."
+msgstr "Cambia print core %1 da %2 a %3."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:384
+msgctxt "@label"
+msgid "Change build plate to %1 (This cannot be overridden)."
+msgstr "Cambia piano di stampa a %1 (Operazione non annullabile)."
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:404
+msgctxt "@label"
+msgid "Override"
+msgstr "Override"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:432
+msgctxt "@label"
+msgid "Starting a print job with an incompatible configuration could damage your 3D printer. Are you sure you want to override the configuration and print %1?"
+msgstr "L’avvio di un processo di stampa con una configurazione non compatibile potrebbe danneggiare la stampante 3D. Sei sicuro di voler annullare la configurazione e stampare %1?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:435
+msgctxt "@window:title"
+msgid "Override configuration configuration and start print"
+msgstr "Annullare la configurazione e avviare la stampa"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:466
+msgctxt "@label"
+msgid "Glass"
+msgstr "Vetro"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml:469
+msgctxt "@label"
+msgid "Aluminum"
+msgstr "Alluminio"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:39
+msgctxt "@label link to connect manager"
+msgid "Manage queue"
+msgstr "Gestione coda di stampa"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml:60
msgctxt "@label"
msgid "Queued"
msgstr "Coda di stampa"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:170
-msgctxt "@label:title"
-msgid "Printers"
-msgstr "Stampanti"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:36
+msgctxt "@label"
+msgid "Printing"
+msgstr "Stampa in corso"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterControlItem.qml:224
-msgctxt "@action:button"
-msgid "View printers"
-msgstr "Visualizza stampanti"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml:49
+msgctxt "@label link to connect manager"
+msgid "Manage printers"
+msgstr "Gestione stampanti"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:38
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:115
+msgctxt "@label"
+msgid "Move to top"
+msgstr "Sposta in alto"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:124
+msgctxt "@label"
+msgid "Delete"
+msgstr "Cancella"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
+msgctxt "@label"
+msgid "Resume"
+msgstr "Riprendi"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
+msgctxt "@label"
+msgid "Pause"
+msgstr "Pausa"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:146
+msgctxt "@label"
+msgid "Abort"
+msgstr "Interrompi"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:178
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to move %1 to the top of the queue?"
+msgstr "Sei sicuro di voler spostare 1% all’inizio della coda?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:179
+msgctxt "@window:title"
+msgid "Move print job to top"
+msgstr "Sposta il processo di stampa in alto"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:188
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to delete %1?"
+msgstr "Sei sicuro di voler cancellare %1?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:189
+msgctxt "@window:title"
+msgid "Delete print job"
+msgstr "Cancella processo di stampa"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:198
+msgctxt "@label %1 is the name of a print job."
+msgid "Are you sure you want to abort %1?"
+msgstr "Sei sicuro di voler interrompere %1?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
+msgctxt "@window:title"
+msgid "Abort print"
+msgstr "Interrompi la stampa"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:43
msgctxt "@info:tooltip"
msgid "Connect to a printer"
msgstr "Collega a una stampante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:117
-msgctxt "@info:tooltip"
-msgid "Load the configuration of the printer into Cura"
-msgstr "Carica la configurazione della stampante in Cura"
-
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:118
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:121
msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Attiva la configurazione"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/resources/qml/UM3InfoComponents.qml:122
+msgctxt "@info:tooltip"
+msgid "Load the configuration of the printer into Cura"
+msgstr "Carica la configurazione della stampante in Cura"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:130
msgctxt "@label"
msgid "Color scheme"
msgstr "Schema colori"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:145
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Colore materiale"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:149
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Tipo di linea"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:153
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr "Velocità"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:157
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr "Spessore strato"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:198
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Modalità di compatibilità"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:284
msgctxt "@label"
msgid "Show Travels"
msgstr "Mostra spostamenti"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:290
msgctxt "@label"
msgid "Show Helpers"
msgstr "Mostra helper"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:296
msgctxt "@label"
msgid "Show Shell"
msgstr "Mostra guscio"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:302
msgctxt "@label"
msgid "Show Infill"
msgstr "Mostra riempimento"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:355
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Mostra solo strati superiori"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:366
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "Mostra 5 strati superiori in dettaglio"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:379
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Superiore / Inferiore"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:383
msgctxt "@label"
msgid "Inner Wall"
msgstr "Parete interna"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:448
msgctxt "@label"
msgid "min"
msgstr "min."
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:500
msgctxt "@label"
msgid "max"
msgstr "max."
@@ -1979,17 +2143,17 @@ msgctxt "@label"
msgid "Post Processing Scripts"
msgstr "Script di post-elaborazione"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:225
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:227
msgctxt "@action"
msgid "Add a script"
msgstr "Aggiungi uno script"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:271
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:273
msgctxt "@label"
msgid "Settings"
msgstr "Impostazioni"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:474
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:477
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "Modifica script di post-elaborazione attivi"
@@ -2084,53 +2248,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Smoothing"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:37
msgctxt "@label"
msgid "Mesh Type"
msgstr "Tipo di maglia"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:68
msgctxt "@label"
msgid "Normal model"
msgstr "Modello normale"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:75
msgctxt "@label"
msgid "Print as support"
msgstr "Stampa come supporto"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:83
msgctxt "@label"
msgid "Don't support overlap with other models"
msgstr "Non supporta sovrapposizione con altri modelli"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:91
msgctxt "@label"
msgid "Modify settings for overlap with other models"
msgstr "Modifica impostazioni per sovrapposizione con altri modelli"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:99
msgctxt "@label"
msgid "Modify settings for infill of other models"
msgstr "Modifica impostazioni per riempimento di altri modelli"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:342
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:347
msgctxt "@action:button"
msgid "Select settings"
msgstr "Seleziona impostazioni"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:384
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:389
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Seleziona impostazioni di personalizzazione per questo modello"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:432
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:437
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:98
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Filtro..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:446
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:451
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Mostra tutto"
@@ -2152,13 +2316,13 @@ msgid "Create new"
msgstr "Crea nuovo"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:70
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:68
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:72
msgctxt "@action:title"
msgid "Summary - Cura Project"
msgstr "Riepilogo - Progetto Cura"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:92
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:96
msgctxt "@action:label"
msgid "Printer settings"
msgstr "Impostazioni della stampante"
@@ -2175,18 +2339,19 @@ msgid "Update"
msgstr "Aggiorna"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:143
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:105
msgctxt "@action:label"
msgid "Type"
msgstr "Tipo"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:159
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
msgctxt "@action:label"
msgid "Printer Group"
msgstr "Gruppo stampanti"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:180
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:192
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:196
msgctxt "@action:label"
msgid "Profile settings"
msgstr "Impostazioni profilo"
@@ -2198,19 +2363,20 @@ msgstr "Come può essere risolto il conflitto nel profilo?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:216
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:308
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:216
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:220
msgctxt "@action:label"
msgid "Name"
msgstr "Nome"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:231
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:200
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:204
msgctxt "@action:label"
msgid "Not in profile"
msgstr "Non nel profilo"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:236
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:209
msgctxt "@action:label"
msgid "%1 override"
msgid_plural "%1 overrides"
@@ -2240,7 +2406,7 @@ msgid "How should the conflict in the material be resolved?"
msgstr "Come può essere risolto il conflitto nel materiale?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:327
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:233
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:237
msgctxt "@action:label"
msgid "Setting visibility"
msgstr "Impostazione visibilità"
@@ -2251,13 +2417,13 @@ msgid "Mode"
msgstr "Modalità"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:352
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:246
msgctxt "@action:label"
msgid "Visible settings:"
msgstr "Impostazioni visibili:"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:357
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:247
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:251
msgctxt "@action:label"
msgid "%1 out of %2"
msgstr "%1 su %2"
@@ -2313,36 +2479,6 @@ msgctxt "@action:button"
msgid "Move to Next Position"
msgstr "Spostamento alla posizione successiva"
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:30
-msgctxt "@title"
-msgid "Upgrade Firmware"
-msgstr "Aggiorna firmware"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:41
-msgctxt "@label"
-msgid "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work."
-msgstr "Il firmware è la parte di software eseguita direttamente sulla stampante 3D. Questo firmware controlla i motori passo-passo, regola la temperatura e, in ultima analisi, consente il funzionamento della stampante."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:51
-msgctxt "@label"
-msgid "The firmware shipping with new printers works, but new versions tend to have more features and improvements."
-msgstr "Il firmware inviato a corredo delle nuove stampanti funziona, tuttavia le nuove versioni tendono ad avere più funzioni ed ottimizzazioni."
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:65
-msgctxt "@action:button"
-msgid "Automatically upgrade Firmware"
-msgstr "Aggiorna automaticamente il firmware"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:75
-msgctxt "@action:button"
-msgid "Upload custom Firmware"
-msgstr "Carica il firmware personalizzato"
-
-#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml:87
-msgctxt "@title:window"
-msgid "Select custom firmware"
-msgstr "Seleziona il firmware personalizzato"
-
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml:37
msgctxt "@label"
msgid "Please select any upgrades made to this Ultimaker Original"
@@ -2490,27 +2626,11 @@ msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr "Rimuovere la stampa"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:293
-msgctxt "@label:"
-msgid "Pause"
-msgstr "Pausa"
-
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:289
-msgctxt "@label:"
-msgid "Resume"
-msgstr "Riprendi"
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:325
-msgctxt "@label:"
+msgctxt "@label"
msgid "Abort Print"
msgstr "Interrompi la stampa"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:335
-msgctxt "@window:title"
-msgid "Abort print"
-msgstr "Interrompi la stampa"
-
#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:337
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
@@ -2544,19 +2664,17 @@ msgid "Customized"
msgstr "Valore personalizzato"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:634
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:637
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Chiedi sempre"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:635
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Elimina e non chiedere nuovamente"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Mantieni e non chiedere nuovamente"
@@ -2576,101 +2694,179 @@ msgctxt "@action:button"
msgid "Create New Profile"
msgstr "Crea nuovo profilo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:65
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:71
msgctxt "@title"
msgid "Information"
msgstr "Informazioni"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:94
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:100
msgctxt "@title:window"
msgid "Confirm Diameter Change"
msgstr "Conferma modifica diametro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:101
msgctxt "@label (%1 is a number)"
msgid "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?"
msgstr "Il nuovo diametro del filamento impostato a %1 mm non è compatibile con l'attuale estrusore. Continuare?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:133
msgctxt "@label"
msgid "Display Name"
msgstr "Visualizza nome"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:143
msgctxt "@label"
msgid "Brand"
msgstr "Marchio"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:153
msgctxt "@label"
msgid "Material Type"
msgstr "Tipo di materiale"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:162
msgctxt "@label"
msgid "Color"
msgstr "Colore"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:212
msgctxt "@label"
msgid "Properties"
msgstr "Proprietà"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:214
msgctxt "@label"
msgid "Density"
msgstr "Densità"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:229
msgctxt "@label"
msgid "Diameter"
msgstr "Diametro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:253
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:263
msgctxt "@label"
msgid "Filament Cost"
msgstr "Costo del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:269
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:280
msgctxt "@label"
msgid "Filament weight"
msgstr "Peso del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:298
msgctxt "@label"
msgid "Filament length"
msgstr "Lunghezza del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:295
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:307
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Costo al metro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:309
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:321
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "Questo materiale è collegato a %1 e condivide alcune delle sue proprietà."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:316
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:328
msgctxt "@label"
msgid "Unlink Material"
msgstr "Scollega materiale"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:327
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:339
msgctxt "@label"
msgid "Description"
msgstr "Descrizione"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:352
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Informazioni sull’aderenza"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:366
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsView.qml:378
msgctxt "@label"
msgid "Print settings"
msgstr "Impostazioni di stampa"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:84
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
+msgctxt "@action:button"
+msgid "Activate"
+msgstr "Attiva"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:101
+msgctxt "@action:button"
+msgid "Create"
+msgstr "Crea"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:114
+msgctxt "@action:button"
+msgid "Duplicate"
+msgstr "Duplica"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
+msgctxt "@action:button"
+msgid "Import"
+msgstr "Importa"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
+msgctxt "@action:button"
+msgid "Export"
+msgstr "Esporta"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:203
+msgctxt "@action:label"
+msgid "Printer"
+msgstr "Stampante"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:262
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
+msgctxt "@title:window"
+msgid "Confirm Remove"
+msgstr "Conferma rimozione"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:263
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
+msgctxt "@label (%1 is object name)"
+msgid "Are you sure you wish to remove %1? This cannot be undone!"
+msgstr "Sei sicuro di voler rimuovere %1? Questa operazione non può essere annullata!"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:285
+msgctxt "@title:window"
+msgid "Import Material"
+msgstr "Importa materiale"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:286
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "Could not import material %1: %2"
+msgstr "Impossibile importare materiale {1}: %2"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:290
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully imported material %1"
+msgstr "Materiale importato correttamente %1"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:308
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:316
+msgctxt "@title:window"
+msgid "Export Material"
+msgstr "Esporta materiale"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:320
+msgctxt "@info:status Don't translate the XML tags and !"
+msgid "Failed to export material to %1: %2"
+msgstr "Impossibile esportare il materiale su %1: %2"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/Materials/MaterialsPage.qml:326
+msgctxt "@info:status Don't translate the XML tag !"
+msgid "Successfully exported material to %1"
+msgstr "Materiale esportato correttamente su %1"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:14
msgctxt "@title:tab"
msgid "Setting Visibility"
@@ -2707,289 +2903,287 @@ msgid "Unit"
msgstr "Unità"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:544
msgctxt "@title:tab"
msgid "General"
msgstr "Generale"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:142
msgctxt "@label"
msgid "Interface"
msgstr "Interfaccia"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:153
msgctxt "@label"
msgid "Language:"
msgstr "Lingua:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:220
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Currency:"
msgstr "Valuta:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:235
msgctxt "@label"
msgid "Theme:"
msgstr "Tema:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:294
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:292
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Riavviare l'applicazione per rendere effettive le modifiche."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:311
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:309
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Seziona automaticamente alla modifica delle impostazioni."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:317
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Seziona automaticamente"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:333
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:331
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Comportamento del riquadro di visualizzazione"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:339
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Evidenzia in rosso le zone non supportate del modello. In assenza di supporto, queste aree non saranno stampate in modo corretto."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:348
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Visualizza sbalzo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:357
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:355
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
msgstr "Sposta la fotocamera in modo che il modello si trovi al centro della visualizzazione quando è selezionato"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:360
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "Centratura fotocamera alla selezione dell'elemento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:371
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:369
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "Il comportamento dello zoom predefinito di Cura dovrebbe essere invertito?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:376
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:374
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "Inverti la direzione dello zoom della fotocamera."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "Lo zoom si muove nella direzione del mouse?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "Zoom verso la direzione del mouse"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:401
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "I modelli sull’area di stampa devono essere spostati per evitare intersezioni?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:406
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "Assicurarsi che i modelli siano mantenuti separati"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:413
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "I modelli sull’area di stampa devono essere portati a contatto del piano di stampa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:420
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:418
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Rilascia automaticamente i modelli sul piano di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
msgctxt "@info:tooltip"
msgid "Show caution message in g-code reader."
msgstr "Visualizza il messaggio di avvertimento sul lettore codice G."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:439
msgctxt "@option:check"
msgid "Caution message in g-code reader"
-msgstr "Messaggio di avvertimento sul lettore codice G."
+msgstr "Messaggio di avvertimento sul lettore codice G"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:449
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:447
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "Lo strato deve essere forzato in modalità di compatibilità?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:452
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "Forzare la modalità di compatibilità visualizzazione strato (riavvio necessario)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:470
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:468
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Apertura e salvataggio file"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:477
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:475
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "I modelli devono essere ridimensionati al volume di stampa, se troppo grandi?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:482
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:480
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Ridimensiona i modelli troppo grandi"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Un modello può apparire eccessivamente piccolo se la sua unità di misura è espressa in metri anziché in millimetri. Questi modelli devono essere aumentati?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:497
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:495
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Ridimensiona i modelli eccessivamente piccoli"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@info:tooltip"
msgid "Should models be selected after they are loaded?"
msgstr "I modelli devono essere selezionati dopo essere stati caricati?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:510
msgctxt "@option:check"
msgid "Select models when loaded"
msgstr "Selezionare i modelli dopo il caricamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "Al nome del processo di stampa deve essere aggiunto automaticamente un prefisso basato sul nome della stampante?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:527
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:525
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "Aggiungi al nome del processo un prefisso macchina"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "Quando si salva un file di progetto deve essere visualizzato un riepilogo?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:541
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:539
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Visualizza una finestra di riepilogo quando si salva un progetto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:551
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:549
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Comportamento predefinito all'apertura di un file progetto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:559
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:557
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Comportamento predefinito all'apertura di un file progetto: "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
msgctxt "@option:openProject"
-msgid "Always ask"
+msgid "Always ask me this"
msgstr "Chiedi sempre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:574
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:572
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Apri sempre come progetto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:575
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Importa sempre i modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:611
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:609
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Dopo aver modificato un profilo ed essere passati a un altro, si apre una finestra di dialogo che chiede se mantenere o eliminare le modifiche oppure se scegliere un comportamento predefinito e non visualizzare più tale finestra di dialogo."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:618
msgctxt "@label"
-msgid "Override Profile"
-msgstr "Override profilo"
+msgid "Profiles"
+msgstr "Profili"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:670
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:623
+msgctxt "@window:text"
+msgid "Default behavior for changed setting values when switching to a different profile: "
+msgstr "Comportamento predefinito per i valori di impostazione modificati al passaggio a un profilo diverso: "
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
+msgctxt "@option:discardOrKeep"
+msgid "Always discard changed settings"
+msgstr "Elimina sempre le impostazioni modificate"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:639
+msgctxt "@option:discardOrKeep"
+msgid "Always transfer changed settings to new profile"
+msgstr "Trasferisci sempre le impostazioni modificate a un nuovo profilo"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:673
msgctxt "@label"
msgid "Privacy"
msgstr "Privacy"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:678
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:681
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "Cura deve verificare la presenza di eventuali aggiornamenti all’avvio del programma?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:683
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:686
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Controlla aggiornamenti all’avvio"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:697
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "I dati anonimi sulla stampa devono essere inviati a Ultimaker? Nota, non sono trasmessi o memorizzati modelli, indirizzi IP o altre informazioni personali."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:702
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "Invia informazioni di stampa (anonime)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:711
msgctxt "@action:button"
msgid "More information"
msgstr "Ulteriori informazioni"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:729
msgctxt "@label"
msgid "Experimental"
msgstr "Sperimentale"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:733
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:736
msgctxt "@info:tooltip"
msgid "Use multi build plate functionality"
msgstr "Utilizzare la funzionalità piano di stampa multiplo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:738
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:741
msgctxt "@option:check"
msgid "Use multi build plate functionality (restart required)"
msgstr "Utilizzare la funzionalità piano di stampa multiplo (necessario riavvio)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:748
-msgctxt "@info:tooltip"
-msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
-msgstr "I modelli appena caricati devono essere sistemati sul piano di stampa? Utilizzato in abbinamento al piano di stampa multiplo (SPERIMENTALE)"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:753
-msgctxt "@option:check"
-msgid "Do not arrange objects on load"
-msgstr "Non posizionare oggetti sul carico"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:16
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:536
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:549
msgctxt "@title:tab"
msgid "Printers"
msgstr "Stampanti"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:35
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:72
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:90
-msgctxt "@action:button"
-msgid "Activate"
-msgstr "Attiva"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:55
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:129
msgctxt "@action:button"
@@ -3007,7 +3201,7 @@ msgid "Connection:"
msgstr "Collegamento:"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:162
-#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:47
+#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/OutputDeviceHeader.qml:55
msgctxt "@info:status"
msgid "The printer is not connected."
msgstr "La stampante non è collegata."
@@ -3033,7 +3227,7 @@ msgid "Aborting print..."
msgstr "Interruzione stampa in corso..."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:540
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:553
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Profili"
@@ -3048,18 +3242,6 @@ msgctxt "@label"
msgid "Duplicate"
msgstr "Duplica"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:142
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:145
-msgctxt "@action:button"
-msgid "Import"
-msgstr "Importa"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:152
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:158
-msgctxt "@action:button"
-msgid "Export"
-msgstr "Esporta"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:174
msgctxt "@title:window"
msgid "Create Profile"
@@ -3070,18 +3252,6 @@ msgctxt "@title:window"
msgid "Duplicate Profile"
msgstr "Duplica profilo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:239
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:221
-msgctxt "@title:window"
-msgid "Confirm Remove"
-msgstr "Conferma rimozione"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:240
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:222
-msgctxt "@label (%1 is object name)"
-msgid "Are you sure you wish to remove %1? This cannot be undone!"
-msgstr "Sei sicuro di voler rimuovere %1? Questa operazione non può essere annullata!"
-
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:256
msgctxt "@title:window"
msgid "Rename Profile"
@@ -3102,228 +3272,200 @@ msgctxt "@label %1 is printer name"
msgid "Printer: %1"
msgstr "Stampante: %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Protected profiles"
msgstr "Profili protetti"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:403
msgctxt "@label"
msgid "Custom profiles"
msgstr "Profili personalizzati"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:468
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:480
msgctxt "@action:button"
msgid "Update profile with current settings/overrides"
msgstr "Aggiorna il profilo con le impostazioni/esclusioni correnti"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:475
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:487
msgctxt "@action:button"
msgid "Discard current changes"
msgstr "Elimina le modifiche correnti"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:492
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:504
msgctxt "@action:label"
msgid "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below."
msgstr "Questo profilo utilizza le impostazioni predefinite dalla stampante, perciò non ci sono impostazioni/esclusioni nell’elenco riportato di seguito."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:511
msgctxt "@action:label"
msgid "Your current settings match the selected profile."
msgstr "Le impostazioni correnti corrispondono al profilo selezionato."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:518
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:530
msgctxt "@title:tab"
msgid "Global Settings"
msgstr "Impostazioni globali"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:40
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:538
-msgctxt "@title:tab"
-msgid "Materials"
-msgstr "Materiali"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:105
-msgctxt "@action:button"
-msgid "Create"
-msgstr "Crea"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:118
-msgctxt "@action:button"
-msgid "Duplicate"
-msgstr "Duplica"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:235
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:243
-msgctxt "@title:window"
-msgid "Import Material"
-msgstr "Importa materiale"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:244
-msgctxt "@info:status Don't translate the XML tags or !"
-msgid "Could not import material %1: %2"
-msgstr "Impossibile importare materiale {1}: %2"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:248
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully imported material %1"
-msgstr "Materiale importato correttamente %1"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:266
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:274
-msgctxt "@title:window"
-msgid "Export Material"
-msgstr "Esporta materiale"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:278
-msgctxt "@info:status Don't translate the XML tags and !"
-msgid "Failed to export material to %1: %2"
-msgstr "Impossibile esportare il materiale su %1: %2"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:284
-msgctxt "@info:status Don't translate the XML tag !"
-msgid "Successfully exported material to %1"
-msgstr "Materiale esportato correttamente su %1"
-
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:337
-msgctxt "@action:label"
-msgid "Printer"
-msgstr "Stampante"
-
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:896
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:953
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Aggiungi stampante"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:195
msgctxt "@label"
msgid "Printer Name:"
msgstr "Nome stampante:"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:219
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Aggiungi stampante"
+#: /home/ruben/Projects/Cura/resources/qml/JobSpecs.qml:84
+msgctxt "@text Print job name"
+msgid "Untitled"
+msgstr "Senza titolo"
+
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:15
msgctxt "@title:window"
msgid "About Cura"
msgstr "Informazioni su Cura"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:55
msgctxt "@label"
msgid "version: %1"
msgstr "versione: %1"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:56
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
msgctxt "@label"
msgid "End-to-end solution for fused filament 3D printing."
msgstr "Soluzione end-to-end per la stampa 3D con filamento fuso."
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:82
msgctxt "@info:credit"
msgid ""
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
"Cura proudly uses the following open source projects:"
msgstr "Cura è stato sviluppato da Ultimaker B.V. in cooperazione con la comunità.\nCura è orgogliosa di utilizzare i seguenti progetti open source:"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
msgctxt "@label"
msgid "Graphical user interface"
msgstr "Interfaccia grafica utente"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
msgctxt "@label"
msgid "Application framework"
msgstr "Struttura applicazione"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
msgctxt "@label"
msgid "G-code generator"
msgstr "Generatore codice G"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
msgctxt "@label"
msgid "Interprocess communication library"
msgstr "Libreria di comunicazione intra-processo"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
msgctxt "@label"
msgid "Programming language"
msgstr "Lingua di programmazione"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:124
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
msgctxt "@label"
msgid "GUI framework"
msgstr "Struttura GUI"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:125
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
msgctxt "@label"
msgid "GUI framework bindings"
msgstr "Vincoli struttura GUI"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:126
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:140
msgctxt "@label"
msgid "C/C++ Binding library"
msgstr "Libreria vincoli C/C++"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:141
msgctxt "@label"
msgid "Data interchange format"
msgstr "Formato scambio dati"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:128
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:142
msgctxt "@label"
msgid "Support library for scientific computing"
msgstr "Libreria di supporto per calcolo scientifico"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:129
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:143
msgctxt "@label"
msgid "Support library for faster math"
msgstr "Libreria di supporto per calcolo rapido"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:130
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:144
msgctxt "@label"
msgid "Support library for handling STL files"
msgstr "Libreria di supporto per gestione file STL"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:145
+msgctxt "@label"
+msgid "Support library for handling planar objects"
+msgstr "Libreria di supporto per gestione oggetti planari"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:146
+msgctxt "@label"
+msgid "Support library for handling triangular meshes"
+msgstr "Libreria di supporto per gestione maglie triangolari"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
+msgctxt "@label"
+msgid "Support library for analysis of complex networks"
+msgstr "Libreria di supporto per l’analisi di reti complesse"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:148
msgctxt "@label"
msgid "Support library for handling 3MF files"
msgstr "Libreria di supporto per gestione file 3MF"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:149
+msgctxt "@label"
+msgid "Support library for file metadata and streaming"
+msgstr "Libreria di supporto per metadati file e streaming"
+
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:150
msgctxt "@label"
msgid "Serial communication library"
msgstr "Libreria di comunicazione seriale"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:151
msgctxt "@label"
msgid "ZeroConf discovery library"
msgstr "Libreria scoperta ZeroConf"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:152
msgctxt "@label"
msgid "Polygon clipping library"
msgstr "Libreria ritaglio poligono"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:135
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:153
msgctxt "@Label"
msgid "Python HTTP library"
msgstr "Libreria Python HTTP"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:155
msgctxt "@label"
msgid "Font"
msgstr "Font"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:156
msgctxt "@label"
msgid "SVG icons"
msgstr "Icone SVG"
-#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:157
msgctxt "@label"
msgid "Linux cross-distribution application deployment"
msgstr "Apertura applicazione distribuzione incrociata Linux"
@@ -3333,7 +3475,7 @@ msgctxt "@label"
msgid "Profile:"
msgstr "Profilo:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
@@ -3341,53 +3483,53 @@ msgid ""
"Click to open the profile manager."
msgstr "Alcuni valori di impostazione/esclusione sono diversi dai valori memorizzati nel profilo.\n\nFare clic per aprire la gestione profili."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:200
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Ricerca..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:544
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:545
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Copia valore su tutti gli estrusori"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:553
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:554
msgctxt "@action:menu"
msgid "Copy all changed values to all extruders"
msgstr "Copia tutti i valori modificati su tutti gli estrusori"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:568
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:591
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Nascondi questa impostazione"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:609
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "Nascondi questa impostazione"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:590
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:613
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Mantieni visibile questa impostazione"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:614
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:426
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:637
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Configura visibilità delle impostazioni..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:644
msgctxt "@action:inmenu"
msgid "Collapse All"
msgstr "Comprimi tutto"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:649
msgctxt "@action:inmenu"
msgid "Expand All"
msgstr "Espandi tutto"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:253
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
@@ -3405,17 +3547,17 @@ msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Influenzato da"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:154
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:155
msgctxt "@label"
msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
msgstr "Questa impostazione è sempre condivisa tra tutti gli estrusori. La sua modifica varierà il valore per tutti gli estrusori."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:158
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "Questo valore è risolto da valori per estrusore "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:189
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
@@ -3423,7 +3565,7 @@ msgid ""
"Click to restore the value of the profile."
msgstr "Questa impostazione ha un valore diverso dal profilo.\n\nFare clic per ripristinare il valore del profilo."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:281
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
@@ -3467,7 +3609,7 @@ msgid "Send a custom G-code command to the connected printer. Press 'enter' to s
msgstr "Invia un comando codice G personalizzato alla stampante connessa. Premere ‘invio’ per inviare il comando."
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ExtruderBox.qml:36
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:268
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:272
msgctxt "@label"
msgid "Extruder"
msgstr "Estrusore"
@@ -3520,7 +3662,7 @@ msgid "The nozzle inserted in this extruder."
msgstr "L’ugello inserito in questo estrusore."
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/HeatedBedBox.qml:25
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:489
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:493
msgctxt "@label"
msgid "Build plate"
msgstr "Piano di stampa"
@@ -3545,6 +3687,21 @@ msgctxt "@tooltip of pre-heat"
msgid "Heat the bed in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the bed to heat up when you're ready to print."
msgstr "Riscalda il piano prima della stampa. È possibile continuare a regolare la stampa durante il riscaldamento e non è necessario attendere il riscaldamento del piano quando si è pronti per la stampa."
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:13
+msgctxt "@label:category menu label"
+msgid "Material"
+msgstr "Materiale"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:37
+msgctxt "@label:category menu label"
+msgid "Favorites"
+msgstr "Preferiti"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:61
+msgctxt "@label:category menu label"
+msgid "Generic"
+msgstr "Generale"
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/PrinterMenu.qml:25
msgctxt "@label:category menu label"
msgid "Network enabled printers"
@@ -3560,27 +3717,27 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Visualizza"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:39
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:42
msgctxt "@action:inmenu menubar:view"
msgid "&Camera position"
msgstr "&Posizione fotocamera"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:58
msgctxt "@action:inmenu menubar:view"
msgid "&Build plate"
-msgstr "&Piano di stampa"
+msgstr "P&iano di stampa"
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:13
msgctxt "@action:inmenu"
msgid "Visible Settings"
msgstr "Impostazioni visibili"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:42
msgctxt "@action:inmenu"
msgid "Show All Settings"
msgstr "Mostra tutte le impostazioni"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:54
+#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:53
msgctxt "@action:inmenu"
msgid "Manage Setting Visibility..."
msgstr "Gestisci Impostazione visibilità..."
@@ -3641,347 +3798,346 @@ msgid ""
"G-code files cannot be modified"
msgstr "Impostazione di stampa disabilitata\nI file codice G non possono essere modificati"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:340
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr "00h 00min"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:359
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:358
msgctxt "@tooltip"
msgid "Time specification"
msgstr "Indicazioni di tempo"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:441
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:440
msgctxt "@label"
msgid "Cost specification"
msgstr "Indicazione di costo"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
msgctxt "@label m for meter"
msgid "%1m"
msgstr "%1m"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:447
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:456
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:455
msgctxt "@label g for grams"
msgid "%1g"
msgstr "%1g"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:454
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:453
msgctxt "@label"
msgid "Total:"
msgstr "Totale:"
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:577
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:576
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Impostazione di stampa consigliata
Stampa con le impostazioni consigliate per la stampante, il materiale e la qualità selezionati."
-#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:582
+#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:581
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "Impostazione di stampa personalizzata
Stampa con il controllo grana fine su ogni sezione finale del processo di sezionamento."
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:106
msgctxt "@label"
msgid "Active print"
msgstr "Stampa attiva"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:115
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:114
msgctxt "@label"
msgid "Job Name"
msgstr "Nome del processo"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:122
msgctxt "@label"
msgid "Printing Time"
msgstr "Tempo di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:131
+#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:130
msgctxt "@label"
msgid "Estimated time left"
msgstr "Tempo residuo stimato"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:78
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
-msgid "Toggle Fu&ll Screen"
-msgstr "Att&iva/disattiva schermo intero"
+msgid "Toggle Full Screen"
+msgstr "Attiva/disattiva schermo intero"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:85
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "&Annulla"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:95
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "Ri&peti"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
-msgstr "E&sci"
+msgstr "&Esci"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:113
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
msgctxt "@action:inmenu menubar:view"
-msgid "&3D View"
-msgstr "&Visualizzazione 3D"
+msgid "3D View"
+msgstr "Visualizzazione 3D"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
msgctxt "@action:inmenu menubar:view"
-msgid "&Front View"
-msgstr "&Visualizzazione frontale"
+msgid "Front View"
+msgstr "Visualizzazione frontale"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
msgctxt "@action:inmenu menubar:view"
-msgid "&Top View"
-msgstr "&Visualizzazione superiore"
+msgid "Top View"
+msgstr "Visualizzazione superiore"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
msgctxt "@action:inmenu menubar:view"
-msgid "&Left Side View"
-msgstr "&Visualizzazione lato sinistro"
+msgid "Left Side View"
+msgstr "Visualizzazione lato sinistro"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
msgctxt "@action:inmenu menubar:view"
-msgid "&Right Side View"
-msgstr "&Visualizzazione lato destro"
+msgid "Right Side View"
+msgstr "Visualizzazione lato destro"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:148
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Configura Cura..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:155
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
-msgstr "A&ggiungi stampante..."
+msgstr "&Aggiungi stampante..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:161
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
-msgstr "&Gestione stampanti..."
+msgstr "Gestione stampanti..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Gestione materiali..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:176
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Aggiorna il profilo con le impostazioni/esclusioni correnti"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Elimina le modifiche correnti"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "&Crea profilo dalle impostazioni/esclusioni correnti..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:202
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Gestione profili..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:209
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Mostra documentazione &online"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "Se&gnala un errore"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:225
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
-msgid "&About..."
-msgstr "I&nformazioni..."
+msgid "About..."
+msgstr "Informazioni..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:232
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
-msgid "Delete &Selected Model"
-msgid_plural "Delete &Selected Models"
-msgstr[0] "Cancella &modello selezionato"
-msgstr[1] "Cancella modelli &selezionati"
+msgid "Delete Selected Model"
+msgid_plural "Delete Selected Models"
+msgstr[0] "Cancella modello selezionato"
+msgstr[1] "Cancella modelli selezionati"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Centra modello selezionato"
msgstr[1] "Centra modelli selezionati"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Moltiplica modello selezionato"
msgstr[1] "Moltiplica modelli selezionati"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Elimina modello"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:278
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "C&entra modello su piattaforma"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "&Raggruppa modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Separa modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "&Unisci modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
-msgstr "Mo<iplica modello"
+msgstr "Mo<iplica modello..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:331
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
-msgid "&Select All Models"
-msgstr "Sel&eziona tutti i modelli"
+msgid "Select All Models"
+msgstr "Seleziona tutti i modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
-msgid "&Clear Build Plate"
-msgstr "&Cancellare piano di stampa"
+msgid "Clear Build Plate"
+msgstr "Cancellare piano di stampa"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
+msgctxt "@action:inmenu menubar:file"
+msgid "Reload All Models"
+msgstr "Ricarica tutti i modelli"
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
-msgctxt "@action:inmenu menubar:file"
-msgid "Re&load All Models"
-msgstr "R&icarica tutti i modelli"
-
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:360
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models To All Build Plates"
msgstr "Sistema tutti i modelli su tutti i piani di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:367
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Sistema tutti i modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Sistema selezione"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:382
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Reimposta tutte le posizioni dei modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
-msgid "Reset All Model &Transformations"
-msgstr "Reimposta tutte le &trasformazioni dei modelli"
+msgid "Reset All Model Transformations"
+msgstr "Reimposta tutte le trasformazioni dei modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:396
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Apri file..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:404
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Nuovo Progetto..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:411
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
-msgstr "M&ostra log motore..."
+msgstr "Mostra &log motore..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:419
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Mostra cartella di configurazione"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:433
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse packages..."
msgstr "Sfoglia i pacchetti..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:440
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:inmenu menubar:view"
msgid "Expand/Collapse Sidebar"
msgstr "Espandi/Riduci barra laterale"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:27
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Caricare un modello 3D"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:37
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Pronto per il sezionamento"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:39
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "Sezionamento in corso..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:41
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Pronto a %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:43
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
msgstr "Sezionamento impossibile"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:45
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "Sezionamento non disponibile"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Slice current printjob"
msgstr "Seziona processo di stampa corrente"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:172
msgctxt "@info:tooltip"
msgid "Cancel slicing process"
msgstr "Annulla processo di sezionamento"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Prepara"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:184
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Annulla"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:320
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "Seleziona l'unità di uscita attiva"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:713
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:767
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Apri file"
@@ -4001,244 +4157,255 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr "Ultimaker Cura"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:103
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "&File"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:121
+msgctxt "@title:menu menubar:file"
+msgid "&Save..."
+msgstr "&Salva..."
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:142
+msgctxt "@title:menu menubar:file"
+msgid "&Export..."
+msgstr "&Esporta..."
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:153
msgctxt "@action:inmenu menubar:file"
-msgid "&Save Selection to File"
-msgstr "&Salva selezione su file"
+msgid "Export Selection..."
+msgstr "Esporta selezione..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
-msgctxt "@title:menu menubar:file"
-msgid "Save &As..."
-msgstr "Salva &come..."
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
-msgctxt "@title:menu menubar:file"
-msgid "Save &Project..."
-msgstr "Salva &progetto..."
-
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:174
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Modifica"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:191
msgctxt "@title:menu"
msgid "&View"
msgstr "&Visualizza"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
msgctxt "@title:menu"
msgid "&Settings"
msgstr "&Impostazioni"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
-msgctxt "@title:menu menubar:toplevel"
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+msgctxt "@title:menu menubar:settings"
msgid "&Printer"
msgstr "S&tampante"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:195
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:207
msgctxt "@title:menu"
msgid "&Material"
msgstr "Ma&teriale"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:204
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:216
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Imposta come estrusore attivo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:210
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:184
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:222
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:188
msgctxt "@action:inmenu"
msgid "Enable Extruder"
msgstr "Abilita estrusore"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:217
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:229
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:194
msgctxt "@action:inmenu"
msgid "Disable Extruder"
msgstr "Disabilita estrusore"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:230
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:241
msgctxt "@title:menu"
+msgid "&Build plate"
+msgstr "&Piano di stampa"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:242
+msgctxt "@title:settings"
msgid "&Profile"
msgstr "&Profilo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:252
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "Es&tensioni"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:274
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:286
msgctxt "@title:menu menubar:toplevel"
msgid "&Toolbox"
msgstr "&Casella degli strumenti"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:281
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:294
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "P&referenze"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:289
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:302
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "&Help"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:348
msgctxt "@label"
msgid "This package will be installed after restarting."
msgstr "Questo pacchetto sarà installato dopo il riavvio."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:364
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:377
msgctxt "@action:button"
msgid "Open File"
msgstr "Apri file"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:547
msgctxt "@title:tab"
msgid "Settings"
msgstr "Impostazioni"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:579
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:593
msgctxt "@title:window"
msgid "New project"
msgstr "Nuovo progetto"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:594
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "Sei sicuro di voler aprire un nuovo progetto? Questo cancellerà il piano di stampa e tutte le impostazioni non salvate."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:814
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:722
+msgctxt "@title:window"
+msgid "Closing Cura"
+msgstr "Chiusura di Cura"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:723
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:735
+msgctxt "@label"
+msgid "Are you sure you want to exit Cura?"
+msgstr "Sei sicuro di voler uscire da Cura?"
+
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:868
msgctxt "@window:title"
msgid "Install Package"
msgstr "Installa il pacchetto"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:821
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:875
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Apri file"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:824
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:878
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
-msgstr "Rilevata la presenza di uno o più file codice G tra i file selezionati. È possibile aprire solo un file codice G alla volta. Se desideri aprire un file codice G, selezionane uno solo. "
+msgstr "Rilevata la presenza di uno o più file codice G tra i file selezionati. È possibile aprire solo un file codice G alla volta. Se desideri aprire un file codice G, selezionane uno solo."
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:14
msgctxt "@title:window"
msgid "Save Project"
msgstr "Salva progetto"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:116
-msgctxt "@action:label"
-msgid ""
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:133
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:137
msgctxt "@action:label"
msgid "Build plate"
msgstr "Piano di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:169
msgctxt "@action:label"
msgid "Extruder %1"
msgstr "Estrusore %1"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:179
msgctxt "@action:label"
msgid "%1 & material"
msgstr "%1 & materiale"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:268
msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "Non mostrare il riepilogo di progetto alla ripetizione di salva"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:283
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:287
msgctxt "@action:button"
msgid "Save"
msgstr "Salva"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:192
msgctxt "@label"
msgid "Layer Height"
msgstr "Altezza dello strato"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:252
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:277
msgctxt "@tooltip"
msgid "This quality profile is not available for you current material and nozzle configuration. Please change these to enable this quality profile"
-msgstr "Questo profilo di qualità non è disponibile per il materiale e la configurazione ugello corrente. Modificarli per abilitare questo profilo di qualità."
+msgstr "Questo profilo di qualità non è disponibile per il materiale e la configurazione ugello corrente. Modificarli per abilitare questo profilo di qualità"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:415
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:450
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
msgstr "Un profilo personalizzato è attualmente attivo. Per attivare il cursore qualità, selezionare un profilo di qualità predefinito nella scheda Personalizza"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:432
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:467
msgctxt "@label"
msgid "Print Speed"
msgstr "Velocità di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:479
msgctxt "@label"
msgid "Slower"
msgstr "Più lenta"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:455
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:490
msgctxt "@label"
msgid "Faster"
msgstr "Più veloce"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:518
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr "Sono state modificate alcune impostazioni del profilo. Per modificarle, andare alla modalità personalizzata."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:506
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:541
msgctxt "@label"
msgid "Infill"
msgstr "Riempimento"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:740
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:777
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Un riempimento graduale aumenterà gradualmente la quantità di riempimento verso l'alto."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:752
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:791
msgctxt "@label"
msgid "Enable gradual"
msgstr "Consenti variazione graduale"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:819
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:858
msgctxt "@label"
msgid "Generate Support"
msgstr "Generazione supporto"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:853
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:892
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "Genera strutture per supportare le parti del modello a sbalzo. Senza queste strutture, queste parti collasserebbero durante la stampa."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:925
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:964
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Seleziona l’estrusore da utilizzare per la stampa di strutture di supporto. Ciò consentirà di costruire strutture di supporto sotto il modello per evitare cedimenti del modello o di stampare a mezz'aria."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:948
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:987
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Adesione piano di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1003
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1042
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
msgstr "Abilita stampa di brim o raft. Questa funzione aggiunge un’area piana attorno o sotto l’oggetto, facile da tagliare successivamente."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1043
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:1082
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "Serve aiuto per migliorare le tue stampe? Leggi la Guida alla ricerca e riparazione guasti Ultimaker"
@@ -4285,23 +4452,22 @@ msgctxt "@label"
msgid "Printer type"
msgstr "Tipo di stampante"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:376
msgctxt "@label"
msgid "Material"
msgstr "Materiale"
-# Added after the string freeze.
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:538
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:543
msgctxt "@label"
-msgid "Use adhesion sheet or glue with this material combination"
-msgstr "Utilizzare un foglio di adesione o colla con questa combinazione di materiali"
+msgid "Use glue with this material combination"
+msgstr "Utilizzare la colla con questa combinazione di materiali"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:570
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:575
msgctxt "@label"
msgid "Check compatibility"
msgstr "Controlla compatibilità"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:588
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:593
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Fai clic per verificare la compatibilità del materiale su Ultimaker.com."
@@ -4391,16 +4557,6 @@ msgctxt "name"
msgid "God Mode"
msgstr "Modalità God"
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "description"
-msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
-msgstr "Accetta i G-Code e li invia tramite WiFi a un Doodle3D WiFi-Box."
-
-#: Doodle3D-cura-plugin/Doodle3D/plugin.json
-msgctxt "name"
-msgid "Doodle3D WiFi-Box"
-msgstr "Doodle3D WiFi-Box"
-
#: ChangeLogPlugin/plugin.json
msgctxt "description"
msgid "Shows changes since latest checked version."
@@ -4411,6 +4567,16 @@ msgctxt "name"
msgid "Changelog"
msgstr "Registro modifiche"
+#: FirmwareUpdater/plugin.json
+msgctxt "description"
+msgid "Provides a machine actions for updating firmware."
+msgstr "Fornisce azioni macchina per l’aggiornamento del firmware."
+
+#: FirmwareUpdater/plugin.json
+msgctxt "name"
+msgid "Firmware Updater"
+msgstr "Aggiornamento firmware"
+
#: ProfileFlattener/plugin.json
msgctxt "description"
msgid "Create a flattend quality changes profile."
@@ -4434,7 +4600,7 @@ msgstr "Stampa USB"
#: UserAgreement/plugin.json
msgctxt "description"
msgid "Ask the user once if he/she agrees with our license."
-msgstr "Chiedere una volta all'utente se accetta la nostra licenza"
+msgstr "Chiedere una volta all'utente se accetta la nostra licenza."
#: UserAgreement/plugin.json
msgctxt "name"
@@ -4481,16 +4647,6 @@ msgctxt "name"
msgid "Prepare Stage"
msgstr "Fase di preparazione"
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "description"
-msgid "Provides an edit window for direct script editing."
-msgstr "Fornisce una finestra di modifica per la modifica script diretta."
-
-#: CuraLiveScriptingPlugin/plugin.json
-msgctxt "name"
-msgid "Live scripting tool"
-msgstr "Strumento di script diretto"
-
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4504,7 +4660,7 @@ msgstr "Plugin dispositivo di output unità rimovibile"
#: UM3NetworkPrinting/plugin.json
msgctxt "description"
msgid "Manages network connections to Ultimaker 3 printers."
-msgstr "Gestisce le connessioni di rete alle stampanti Ultimaker 3"
+msgstr "Gestisce le connessioni di rete alle stampanti Ultimaker 3."
#: UM3NetworkPrinting/plugin.json
msgctxt "name"
@@ -4601,16 +4757,6 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Lettore legacy profilo Cura"
-#: CuraBlenderPlugin/plugin.json
-msgctxt "description"
-msgid "Helps to open Blender files directly in Cura."
-msgstr "Aiuta ad aprire i file Blender direttamente in Cura."
-
-#: CuraBlenderPlugin/plugin.json
-msgctxt "name"
-msgid "Blender Integration (experimental)"
-msgstr "Integrazione Blender (sperimentale)"
-
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4661,6 +4807,16 @@ msgctxt "name"
msgid "Version Upgrade 2.7 to 3.0"
msgstr "Aggiornamento della versione da 2.7 a 3.0"
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "description"
+msgid "Upgrades configurations from Cura 3.4 to Cura 3.5."
+msgstr "Aggiorna le configurazioni da Cura 3.4 a Cura 3.5."
+
+#: VersionUpgrade/VersionUpgrade34to35/plugin.json
+msgctxt "name"
+msgid "Version Upgrade 3.4 to 3.5"
+msgstr "Aggiornamento della versione da 3.4 a 3.5"
+
#: VersionUpgrade/VersionUpgrade30to31/plugin.json
msgctxt "description"
msgid "Upgrades configurations from Cura 3.0 to Cura 3.1."
@@ -4801,6 +4957,299 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Lettore profilo Cura"
+#~ msgctxt "@warning:status"
+#~ msgid "Please generate G-code before saving."
+#~ msgstr "Generare il codice G prima di salvare."
+
+#~ msgctxt "@item:inmenu"
+#~ msgid "Profile Assistant"
+#~ msgstr "Assistente profilo"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Profile Assistant"
+#~ msgstr "Assistente profilo"
+
+#~ msgctxt "@action"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Aggiorna firmware"
+
+#~ msgctxt "@label unknown material"
+#~ msgid "Unknown"
+#~ msgstr "Sconosciuto"
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "No custom profile to import in file {0}"
+#~ msgstr "Nessun profilo personalizzato da importare nel file {0}"
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "This profile {0} contains incorrect data, could not import it."
+#~ msgstr "Questo profilo {0} contiene dati errati, impossibile importarlo."
+
+#~ msgctxt "@info:status Don't translate the XML tags or !"
+#~ msgid "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it."
+#~ msgstr "La macchina definita nel profilo {0} ({1}) non corrisponde alla macchina corrente ({2}), impossibile importarlo."
+
+#~ msgctxt "@title:window"
+#~ msgid "Confirm uninstall "
+#~ msgstr "Conferma disinstalla "
+
+#~ msgctxt "@label:status"
+#~ msgid "Paused"
+#~ msgstr "In pausa"
+
+#~ msgctxt "@action:button"
+#~ msgid "Previous"
+#~ msgstr "Precedente"
+
+#~ msgctxt "@action:button"
+#~ msgid "Next"
+#~ msgstr "Avanti"
+
+#~ msgctxt "@label"
+#~ msgid "Tip"
+#~ msgstr "Suggerimento"
+
+#~ msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
+#~ msgid "%1m / ~ %2g / ~ %4 %3"
+#~ msgstr "%1m / ~ %2g / ~ %4 %3"
+
+#~ msgctxt "@label Print estimates: m for meters, g for grams"
+#~ msgid "%1m / ~ %2g"
+#~ msgstr "%1m / ~ %2g"
+
+#~ msgctxt "@label"
+#~ msgid "Print experiment"
+#~ msgstr "Prova di stampa"
+
+#~ msgctxt "@label"
+#~ msgid "Checklist"
+#~ msgstr "Lista di controllo"
+
+#~ msgctxt "@title"
+#~ msgid "Upgrade Firmware"
+#~ msgstr "Aggiorna firmware"
+
+#~ msgctxt "description"
+#~ msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+#~ msgstr "Consente ai produttori di materiali di creare nuovi profili materiale e di qualità utilizzando una UI drop-in."
+
+#~ msgctxt "name"
+#~ msgid "Print Profile Assistant"
+#~ msgstr "Assistente profilo di stampa"
+
+#~ msgctxt "@action:button"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Stampa con Doodle3D WiFi-Box"
+
+#~ msgctxt "@properties:tooltip"
+#~ msgid "Print with Doodle3D WiFi-Box"
+#~ msgstr "Stampa con Doodle3D WiFi-Box"
+
+#~ msgctxt "@info:status"
+#~ msgid "Connecting to Doodle3D Connect"
+#~ msgstr "Collegamento a Doodle3D Connect"
+
+#~ msgctxt "@info:status"
+#~ msgid "Sending data to Doodle3D Connect"
+#~ msgstr "Invio dati a Doodle3D Connect"
+
+#~ msgctxt "@info:status"
+#~ msgid "Unable to send data to Doodle3D Connect. Is another job still active?"
+#~ msgstr "Impossibile inviare dati a Doodle3D Connect. C'è un altro processo in corso?"
+
+#~ msgctxt "@info:status"
+#~ msgid "Storing data on Doodle3D Connect"
+#~ msgstr "Memorizzazione dati su Doodle3D Connect"
+
+#~ msgctxt "@info:status"
+#~ msgid "File sent to Doodle3D Connect"
+#~ msgstr "File inviato a Doodle3D Connect"
+
+#~ msgctxt "@action:button"
+#~ msgid "Open Connect..."
+#~ msgstr "Apri Connect..."
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Open the Doodle3D Connect web interface"
+#~ msgstr "Apri interfaccia web Doodle3D Connect"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Blender file"
+#~ msgstr "File Blender"
+
+#~ msgctxt "@info:status"
+#~ msgid ""
+#~ "Could not export using \"{}\" quality!\n"
+#~ "Felt back to \"{}\"."
+#~ msgstr ""
+#~ "Impossibile esportare utilizzando qualità \"{}\" quality!\n"
+#~ "Tornato a \"{}\"."
+
+#~ msgctxt "@label"
+#~ msgid "Contact"
+#~ msgstr "Contatto"
+
+#~ msgctxt "@label"
+#~ msgid "This printer is not set up to host a group of Ultimaker 3 printers."
+#~ msgstr "Questa stampante non è predisposta per comandare un gruppo di stampanti Ultimaker 3."
+
+#~ msgctxt "@label"
+#~ msgid "This printer is the host for a group of %1 Ultimaker 3 printers."
+#~ msgstr "Questa stampante comanda un gruppo di %1 stampanti Ultimaker 3."
+
+#~ msgctxt "@label: arg 1 is group name"
+#~ msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
+#~ msgstr "%1 non è configurata per supportare la connessione di un gruppo di stampanti Ultimaker 3"
+
+#~ msgctxt "@label link to connect manager"
+#~ msgid "Add/Remove printers"
+#~ msgstr "Aggiungi/Rimuovi stampanti"
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Opens the print jobs page with your default web browser."
+#~ msgstr "Apre la pagina processi di stampa con il browser web predefinito."
+
+#~ msgctxt "@action:button"
+#~ msgid "View print jobs"
+#~ msgstr "Visualizza processi di stampa"
+
+#~ msgctxt "@label:status"
+#~ msgid "Preparing to print"
+#~ msgstr "Preparazione della stampa"
+
+#~ msgctxt "@label:status"
+#~ msgid "Printing"
+#~ msgstr "Stampa in corso"
+
+#~ msgctxt "@label:status"
+#~ msgid "Available"
+#~ msgstr "Disponibile"
+
+#~ msgctxt "@label:status"
+#~ msgid "Lost connection with the printer"
+#~ msgstr "Persa connessione con la stampante"
+
+#~ msgctxt "@label:status"
+#~ msgid "Unavailable"
+#~ msgstr "Non disponibile"
+
+#~ msgctxt "@label:status"
+#~ msgid "Unknown"
+#~ msgstr "Sconosciuto"
+
+#~ msgctxt "@label:status"
+#~ msgid "Disabled"
+#~ msgstr "Disabilitato"
+
+#~ msgctxt "@label:status"
+#~ msgid "Reserved"
+#~ msgstr "Riservato"
+
+#~ msgctxt "@label"
+#~ msgid "Preparing to print"
+#~ msgstr "Preparazione della stampa"
+
+#~ msgctxt "@label:status"
+#~ msgid "Print aborted"
+#~ msgstr "Stampa interrotta"
+
+#~ msgctxt "@label"
+#~ msgid "Not accepting print jobs"
+#~ msgstr "Mancata accettazione processi di stampa"
+
+#~ msgctxt "@label"
+#~ msgid "Finishes at: "
+#~ msgstr "Finisce alle: "
+
+#~ msgctxt "@label"
+#~ msgid "Clear build plate"
+#~ msgstr "Cancellare piano di stampa"
+
+#~ msgctxt "@label"
+#~ msgid "Waiting for configuration change"
+#~ msgstr "In attesa di modifica configurazione"
+
+#~ msgctxt "@title"
+#~ msgid "Print jobs"
+#~ msgstr "Processi di stampa"
+
+#~ msgctxt "@label:title"
+#~ msgid "Printers"
+#~ msgstr "Stampanti"
+
+#~ msgctxt "@action:button"
+#~ msgid "View printers"
+#~ msgstr "Visualizza stampanti"
+
+#~ msgctxt "@label:"
+#~ msgid "Pause"
+#~ msgstr "Pausa"
+
+#~ msgctxt "@label:"
+#~ msgid "Resume"
+#~ msgstr "Riprendi"
+
+#~ msgctxt "@label:"
+#~ msgid "Abort Print"
+#~ msgstr "Interrompi la stampa"
+
+#~ msgctxt "@option:openProject"
+#~ msgid "Always ask"
+#~ msgstr "Chiedi sempre"
+
+#~ msgctxt "@label"
+#~ msgid "Override Profile"
+#~ msgstr "Override profilo"
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+#~ msgstr "I modelli appena caricati devono essere sistemati sul piano di stampa? Utilizzato in abbinamento al piano di stampa multiplo (SPERIMENTALE)"
+
+#~ msgctxt "@option:check"
+#~ msgid "Do not arrange objects on load"
+#~ msgstr "Non posizionare oggetti sul carico"
+
+#~ msgctxt "@action:inmenu menubar:file"
+#~ msgid "&Save Selection to File"
+#~ msgstr "&Salva selezione su file"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save &As..."
+#~ msgstr "Salva &come..."
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save &Project..."
+#~ msgstr "Salva &progetto..."
+
+# Added after the string freeze.
+#~ msgctxt "@label"
+#~ msgid "Use adhesion sheet or glue with this material combination"
+#~ msgstr "Utilizzare un foglio di adesione o colla con questa combinazione di materiali"
+
+#~ msgctxt "description"
+#~ msgid "Accepts G-Code and sends them over WiFi to a Doodle3D WiFi-Box."
+#~ msgstr "Accetta i G-Code e li invia tramite WiFi a un Doodle3D WiFi-Box."
+
+#~ msgctxt "name"
+#~ msgid "Doodle3D WiFi-Box"
+#~ msgstr "Doodle3D WiFi-Box"
+
+#~ msgctxt "description"
+#~ msgid "Provides an edit window for direct script editing."
+#~ msgstr "Fornisce una finestra di modifica per la modifica script diretta."
+
+#~ msgctxt "name"
+#~ msgid "Live scripting tool"
+#~ msgstr "Strumento di script diretto"
+
+#~ msgctxt "description"
+#~ msgid "Helps to open Blender files directly in Cura."
+#~ msgstr "Aiuta ad aprire i file Blender direttamente in Cura."
+
+#~ msgctxt "name"
+#~ msgid "Blender Integration (experimental)"
+#~ msgstr "Integrazione Blender (sperimentale)"
+
#~ msgctxt "@info:title"
#~ msgid "Model Checker Warning"
#~ msgstr "Avvertenza controllo modello"
@@ -5068,10 +5517,6 @@ msgstr "Lettore profilo Cura"
#~ msgid "Browse plugins..."
#~ msgstr "Sfoglia plugin..."
-#~ msgctxt "@title:menu"
-#~ msgid "&Build plate"
-#~ msgstr "&Piano di stampa"
-
#~ msgctxt "@title:menu menubar:toplevel"
#~ msgid "P&lugins"
#~ msgstr "&Plugin"
@@ -5297,14 +5742,6 @@ msgstr "Lettore profilo Cura"
#~ "\n"
#~ " Spiacenti."
-#~ msgctxt "@item:inmenu"
-#~ msgid "Profile Assistant"
-#~ msgstr "Assistente profilo"
-
-#~ msgctxt "@item:inlistbox"
-#~ msgid "Profile Assistant"
-#~ msgstr "Assistente profilo"
-
#~ msgctxt "@item:material"
#~ msgid "No material loaded"
#~ msgstr "Nessun materiale caricato"
@@ -5435,14 +5872,6 @@ msgstr "Lettore profilo Cura"
#~ msgid "Configure setting visiblity..."
#~ msgstr "Configurazione visibilità delle impostazioni in corso..."
-#~ msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
-#~ msgid "%1m / ~ %2g / ~ %4 %3"
-#~ msgstr "%1m / ~ %2g / ~ %4 %3"
-
-#~ msgctxt "@label Print estimates: m for meters, g for grams"
-#~ msgid "%1m / ~ %2g"
-#~ msgstr "%1m / ~ %2g"
-
#~ msgctxt "@title:menuitem %1 is the automatically selected material"
#~ msgid "Automatic: %1"
#~ msgstr "Automatico: %1"
@@ -5479,14 +5908,6 @@ msgstr "Lettore profilo Cura"
#~ msgid "GCode Profile Reader"
#~ msgstr "Lettore profilo GCode"
-#~ msgctxt "description"
-#~ msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
-#~ msgstr "Consente ai produttori di materiali di creare nuovi profili materiale e di qualità utilizzando una UI drop-in."
-
-#~ msgctxt "name"
-#~ msgid "Print Profile Assistant"
-#~ msgstr "Assistente profilo di stampa"
-
#~ msgctxt "@info:status"
#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
#~ msgstr "Rilevati errori all'apertura del file SolidWorks! Controllare se è possibile aprire il file in SolidWorks senza che si verifichino problemi!"
@@ -5683,10 +6104,6 @@ msgstr "Lettore profilo Cura"
#~ msgid "This printer is the host for a group of %1 connected Ultimaker 3 printers"
#~ msgstr "Questa stampante fa da host per un gruppo di %1 stampanti Ultimaker 3 connesse"
-#~ msgctxt "@label:status"
-#~ msgid "Preparing"
-#~ msgstr "Preparazione in corso"
-
#~ msgctxt "@label"
#~ msgid "Completed on: "
#~ msgstr "Completato su: "
diff --git a/resources/i18n/it_IT/fdmextruder.def.json.po b/resources/i18n/it_IT/fdmextruder.def.json.po
index 0ed16e504b..aa170f18be 100644
--- a/resources/i18n/it_IT/fdmextruder.def.json.po
+++ b/resources/i18n/it_IT/fdmextruder.def.json.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0000\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
+"PO-Revision-Date: 2018-09-28 14:25+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
@@ -166,6 +166,16 @@ msgctxt "extruder_prime_pos_z description"
msgid "The Z coordinate of the position where the nozzle primes at the start of printing."
msgstr "Indica la coordinata Z della posizione in cui l’ugello si innesca all’avvio della stampa."
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number label"
+msgid "Extruder Print Cooling Fan"
+msgstr "Ventola di raffreddamento stampa estrusore"
+
+#: fdmextruder.def.json
+msgctxt "machine_extruder_cooling_fan_number description"
+msgid "The number of the print cooling fan associated with this extruder. Only change this from the default value of 0 when you have a different print cooling fan for each extruder."
+msgstr "Il numero di ventole di raffreddamento stampa abbinate a questo estrusore. Modificarlo dal valore predefinito 0 solo quando si ha una ventola di raffreddamento diversa per ciascun estrusore."
+
#: fdmextruder.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
diff --git a/resources/i18n/it_IT/fdmprinter.def.json.po b/resources/i18n/it_IT/fdmprinter.def.json.po
index 2d883982e3..e2d013f74c 100644
--- a/resources/i18n/it_IT/fdmprinter.def.json.po
+++ b/resources/i18n/it_IT/fdmprinter.def.json.po
@@ -5,16 +5,17 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-03-29 08:36+0200\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0000\n"
+"PO-Revision-Date: 2018-09-28 15:02+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.6\n"
#: fdmprinter.def.json
msgctxt "machine_settings label"
@@ -80,6 +81,16 @@ msgctxt "material_guid description"
msgid "GUID of the material. This is set automatically. "
msgstr "Il GUID del materiale. È impostato automaticamente. "
+#: fdmprinter.def.json
+msgctxt "material_diameter label"
+msgid "Diameter"
+msgstr "Diametro"
+
+#: fdmprinter.def.json
+msgctxt "material_diameter description"
+msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
+msgstr "Regolare il diametro del filamento utilizzato. Abbinare questo valore al diametro del filamento utilizzato."
+
#: fdmprinter.def.json
msgctxt "material_bed_temp_wait label"
msgid "Wait for Build Plate Heatup"
@@ -533,7 +544,7 @@ msgstr "Accelerazione massima X"
#: fdmprinter.def.json
msgctxt "machine_max_acceleration_x description"
msgid "Maximum acceleration for the motor of the X-direction"
-msgstr "Indica l’accelerazione massima del motore per la direzione X."
+msgstr "Indica l’accelerazione massima del motore per la direzione X"
#: fdmprinter.def.json
msgctxt "machine_max_acceleration_y label"
@@ -853,7 +864,7 @@ msgstr "Larghezza linea strato iniziale"
#: fdmprinter.def.json
msgctxt "initial_layer_line_width_factor description"
msgid "Multiplier of the line width on the first layer. Increasing this could improve bed adhesion."
-msgstr "Moltiplicatore della larghezza della linea del primo strato Il suo aumento potrebbe migliorare l'adesione al piano"
+msgstr "Moltiplicatore della larghezza della linea del primo strato Il suo aumento potrebbe migliorare l'adesione al piano."
#: fdmprinter.def.json
msgctxt "shell label"
@@ -1055,6 +1066,16 @@ msgctxt "top_bottom_pattern_0 option zigzag"
msgid "Zig Zag"
msgstr "Zig Zag"
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons label"
+msgid "Connect Top/Bottom Polygons"
+msgstr "Collega poligoni superiori/inferiori"
+
+#: fdmprinter.def.json
+msgctxt "connect_skin_polygons description"
+msgid "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happen midway over infill this feature can reduce the top surface quality."
+msgstr "Collega i percorsi del rivestimento esterno superiore/inferiore quando corrono uno accanto all’altro. Per le configurazioni concentriche, l’abilitazione di questa impostazione riduce notevolmente il tempo di spostamento, tuttavia poiché i collegamenti possono aver luogo a metà del riempimento, con questa funzione la qualità della superficie superiore potrebbe risultare inferiore."
+
#: fdmprinter.def.json
msgctxt "skin_angles label"
msgid "Top/Bottom Line Directions"
@@ -1135,6 +1156,26 @@ msgctxt "travel_compensate_overlapping_walls_x_enabled description"
msgid "Compensate the flow for parts of an inner wall being printed where there is already a wall in place."
msgstr "Compensa il flusso per le parti di una parete interna che viene stampata dove è già presente una parete."
+#: fdmprinter.def.json
+msgctxt "wall_min_flow label"
+msgid "Minimum Wall Flow"
+msgstr "Flusso minimo della parete"
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow description"
+msgid "Minimum allowed percentage flow for a wall line. The wall overlap compensation reduces a wall's flow when it lies close to an existing wall. Walls whose flow is less than this value will be replaced with a travel move. When using this setting, you must enable the wall overlap compensation and print the outer wall before inner walls."
+msgstr "Flusso percentuale minimo ammesso per una linea perimetrale. La compensazione di sovrapposizione pareti riduce il flusso di una parete quando si trova vicino a una parete esistente. Le pareti con un flusso inferiore a questo valore saranno sostituite da uno spostamento. Quando si utilizza questa impostazione, si deve abilitare la compensazione della sovrapposizione pareti e stampare la parete esterna prima delle pareti interne."
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract label"
+msgid "Prefer Retract"
+msgstr "Preferire retrazione"
+
+#: fdmprinter.def.json
+msgctxt "wall_min_flow_retract description"
+msgid "If enabled, retraction is used rather than combing for travel moves that replace walls whose flow is below the minimum flow threshold."
+msgstr "Se abilitata, la retrazione viene utilizzata al posto del combing per gli spostamenti che sostituiscono le pareti aventi un flusso inferiore alla soglia minima."
+
#: fdmprinter.def.json
msgctxt "fill_perimeter_gaps label"
msgid "Fill Gaps Between Walls"
@@ -1383,7 +1424,7 @@ msgstr "Velocità di stiratura"
#: fdmprinter.def.json
msgctxt "speed_ironing description"
msgid "The speed at which to pass over the top surface."
-msgstr "Velocità alla quale passare sopra la superficie superiore"
+msgstr "Velocità alla quale passare sopra la superficie superiore."
#: fdmprinter.def.json
msgctxt "acceleration_ironing label"
@@ -1452,8 +1493,8 @@ msgstr "Configurazione di riempimento"
#: fdmprinter.def.json
msgctxt "infill_pattern description"
-msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
-msgstr "Configurazione del materiale di riempimento della stampa. Il riempimento a linea e a zig zag cambia direzione su strati alternati, riducendo il costo del materiale. Le configurazioni a griglia, a triangolo, tri-esagonali, cubiche, ottagonali, a quarto di cubo, incrociate e concentriche sono stampate completamente su ogni strato. Le configurazioni cubiche, a quarto di cubo e ottagonali variano per ciascuno strato per garantire una più uniforme distribuzione della forza in ogni direzione."
+msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Gyroid, cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
+msgstr "Configurazione del materiale di riempimento della stampa. Il riempimento a linea e a zig zag cambia direzione su strati alternati, riducendo il costo del materiale. Le configurazioni a griglia, a triangolo, tri-esagonali, cubiche, ottagonali, a quarto di cubo, incrociate e concentriche sono stampate completamente su ogni strato. Le configurazioni gyroid, cubiche, a quarto di cubo e ottagonali variano per ciascuno strato per garantire una più uniforme distribuzione della forza in ogni direzione."
#: fdmprinter.def.json
msgctxt "infill_pattern option grid"
@@ -1500,11 +1541,6 @@ msgctxt "infill_pattern option concentric"
msgid "Concentric"
msgstr "Concentriche"
-#: fdmprinter.def.json
-msgctxt "infill_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "3D concentrica"
-
#: fdmprinter.def.json
msgctxt "infill_pattern option zigzag"
msgid "Zig Zag"
@@ -1520,6 +1556,11 @@ msgctxt "infill_pattern option cross_3d"
msgid "Cross 3D"
msgstr "Incrociata 3D"
+#: fdmprinter.def.json
+msgctxt "infill_pattern option gyroid"
+msgid "Gyroid"
+msgstr "Gyroid"
+
#: fdmprinter.def.json
msgctxt "zig_zaggify_infill label"
msgid "Connect Infill Lines"
@@ -1530,6 +1571,16 @@ msgctxt "zig_zaggify_infill description"
msgid "Connect the ends where the infill pattern meets the inner wall using a line which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduce the effects of infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
msgstr "Collegare le estremità nel punto in cui il riempimento incontra la parete interna utilizzando una linea che segue la forma della parete interna. L'abilitazione di questa impostazione può far meglio aderire il riempimento alle pareti riducendo nel contempo gli effetti del riempimento sulla qualità delle superfici verticali. La disabilitazione di questa impostazione consente di ridurre la quantità di materiale utilizzato."
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons label"
+msgid "Connect Infill Polygons"
+msgstr "Collega poligoni di riempimento"
+
+#: fdmprinter.def.json
+msgctxt "connect_infill_polygons description"
+msgid "Connect infill paths where they run next to each other. For infill patterns which consist of several closed polygons, enabling this setting greatly reduces the travel time."
+msgstr "Collega i percorsi di riempimento quando corrono uno accanto all’altro. Per le configurazioni di riempimento composte da più poligoni chiusi, l’abilitazione di questa impostazione riduce notevolmente il tempo di spostamento."
+
#: fdmprinter.def.json
msgctxt "infill_angles label"
msgid "Infill Line Directions"
@@ -1560,6 +1611,28 @@ msgctxt "infill_offset_y description"
msgid "The infill pattern is moved this distance along the Y axis."
msgstr "Il riempimento si sposta di questa distanza lungo l'asse Y."
+#: fdmprinter.def.json
+msgctxt "infill_multiplier label"
+msgid "Infill Line Multiplier"
+msgstr "Moltiplicatore delle linee di riempimento"
+
+#: fdmprinter.def.json
+msgctxt "infill_multiplier description"
+msgid "Convert each infill line to this many lines. The extra lines do not cross over each other, but avoid each other. This makes the infill stiffer, but increases print time and material usage."
+msgstr "Converte ogni linea di riempimento in questo numero di linee. Le linee supplementari non si incrociano tra loro, ma si evitano. In tal modo il riempimento risulta più rigido, ma il tempo di stampa e la quantità di materiale aumentano."
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count label"
+msgid "Extra Infill Wall Count"
+msgstr "Conteggio pareti di riempimento supplementari"
+
+#: fdmprinter.def.json
+msgctxt "infill_wall_line_count description"
+msgid ""
+"Add extra walls around the infill area. Such walls can make top/bottom skin lines sag down less which means you need less top/bottom skin layers for the same quality at the cost of some extra material.\n"
+"This feature can combine with the Connect Infill Polygons to connect all the infill into a single extrusion path without the need for travels or retractions if configured right."
+msgstr "Aggiunge pareti supplementari intorno alla zona di riempimento. Queste pareti possono ridurre l’abbassamento delle linee del rivestimento esterno superiore/inferiore, pertanto saranno necessari meno strati di rivestimento esterno superiore/inferiore per ottenere la stessa qualità al costo del materiale supplementare.\nQuesta funzione può essere abbinata a Collega poligoni riempimento per collegare tutto il riempimento in un unico percorso di estrusione senza necessità di avanzamenti o arretramenti, se configurata correttamente."
+
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
msgid "Cubic Subdivision Shell"
@@ -1788,7 +1861,7 @@ msgstr "Temperatura di stampa preimpostata"
#: fdmprinter.def.json
msgctxt "default_material_print_temperature description"
msgid "The default temperature used for printing. This should be the \"base\" temperature of a material. All other print temperatures should use offsets based on this value"
-msgstr "La temperatura preimpostata utilizzata per la stampa. Deve essere la temperatura “base” di un materiale. Tutte le altre temperature di stampa devono usare scostamenti basati su questo valore."
+msgstr "La temperatura preimpostata utilizzata per la stampa. Deve essere la temperatura “base” di un materiale. Tutte le altre temperature di stampa devono usare scostamenti basati su questo valore"
#: fdmprinter.def.json
msgctxt "material_print_temperature label"
@@ -1848,7 +1921,7 @@ msgstr "Temperatura piano di stampa preimpostata"
#: fdmprinter.def.json
msgctxt "default_material_bed_temperature description"
msgid "The default temperature used for the heated build plate. This should be the \"base\" temperature of a build plate. All other print temperatures should use offsets based on this value"
-msgstr "La temperatura preimpostata utilizzata per il piano di stampa. Deve essere la temperatura “base” di un piano di stampa. Tutte le altre temperature di stampa devono usare scostamenti basati su questo valore."
+msgstr "La temperatura preimpostata utilizzata per il piano di stampa. Deve essere la temperatura “base” di un piano di stampa. Tutte le altre temperature di stampa devono usare scostamenti basati su questo valore"
#: fdmprinter.def.json
msgctxt "material_bed_temperature label"
@@ -1870,16 +1943,6 @@ msgctxt "material_bed_temperature_layer_0 description"
msgid "The temperature used for the heated build plate at the first layer."
msgstr "Indica la temperatura usata per il piano di stampa riscaldato per il primo strato."
-#: fdmprinter.def.json
-msgctxt "material_diameter label"
-msgid "Diameter"
-msgstr "Diametro"
-
-#: fdmprinter.def.json
-msgctxt "material_diameter description"
-msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
-msgstr "Regolare il diametro del filamento utilizzato. Abbinare questo valore al diametro del filamento utilizzato."
-
#: fdmprinter.def.json
msgctxt "material_adhesion_tendency label"
msgid "Adhesion Tendency"
@@ -1948,7 +2011,7 @@ msgstr "Retrazione al cambio strato"
#: fdmprinter.def.json
msgctxt "retract_at_layer_change description"
msgid "Retract the filament when the nozzle is moving to the next layer."
-msgstr "Ritrae il filamento quando l'ugello si sta muovendo allo strato successivo. "
+msgstr "Ritrae il filamento quando l'ugello si sta muovendo allo strato successivo."
#: fdmprinter.def.json
msgctxt "retraction_amount label"
@@ -2717,8 +2780,8 @@ msgstr "Modalità Combing"
#: fdmprinter.def.json
msgctxt "retraction_combing description"
-msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
-msgstr "La funzione Combing tiene l’ugello all’interno delle aree già stampate durante lo spostamento. In tal modo le corse di spostamento sono leggermente più lunghe ma si riduce l’esigenza di effettuare retrazioni. Se questa funzione viene disabilitata, il materiale viene retratto e l’ugello si sposta in linea retta al punto successivo. È anche possibile evitare il combing sopra le aree del rivestimento esterno superiore/inferiore effettuando il combing solo nel riempimento."
+msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas and also to only comb within the infill. Note that the 'Within Infill' option behaves exactly like the 'Not in Skin' option in earlier Cura releases."
+msgstr "La funzione Combing tiene l’ugello all’interno delle aree già stampate durante lo spostamento. In tal modo le corse di spostamento sono leggermente più lunghe, ma si riduce l’esigenza di effettuare retrazioni. Se questa funzione viene disabilitata, il materiale viene retratto e l’ugello si sposta in linea retta al punto successivo. È anche possibile evitare il combing sopra le aree del rivestimento esterno superiore/inferiore effettuando il combing solo nel riempimento. Si noti che l’opzione ‘Nel riempimento' si comporta esattamente come l’opzione ‘Non nel rivestimento' delle precedenti versioni Cura."
#: fdmprinter.def.json
msgctxt "retraction_combing option off"
@@ -2735,6 +2798,11 @@ msgctxt "retraction_combing option noskin"
msgid "Not in Skin"
msgstr "Non nel rivestimento"
+#: fdmprinter.def.json
+msgctxt "retraction_combing option infill"
+msgid "Within Infill"
+msgstr "Nel riempimento"
+
#: fdmprinter.def.json
msgctxt "retraction_combing_max_distance label"
msgid "Max Comb Distance With No Retract"
@@ -3115,11 +3183,6 @@ msgctxt "support_pattern option concentric"
msgid "Concentric"
msgstr "Concentriche"
-#: fdmprinter.def.json
-msgctxt "support_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "3D concentrica"
-
#: fdmprinter.def.json
msgctxt "support_pattern option zigzag"
msgid "Zig Zag"
@@ -3180,6 +3243,56 @@ msgctxt "support_line_distance description"
msgid "Distance between the printed support structure lines. This setting is calculated by the support density."
msgstr "Indica la distanza tra le linee della struttura di supporto stampata. Questa impostazione viene calcolata mediante la densità del supporto."
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance label"
+msgid "Initial Layer Support Line Distance"
+msgstr "Distanza tra le linee del supporto dello strato iniziale"
+
+#: fdmprinter.def.json
+msgctxt "support_initial_layer_line_distance description"
+msgid "Distance between the printed initial layer support structure lines. This setting is calculated by the support density."
+msgstr "Indica la distanza tra le linee della struttura di supporto dello strato iniziale stampato. Questa impostazione viene calcolata mediante la densità del supporto."
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle label"
+msgid "Support Infill Line Direction"
+msgstr "Direzione delle linee di riempimento supporto"
+
+#: fdmprinter.def.json
+msgctxt "support_infill_angle description"
+msgid "Orientation of the infill pattern for supports. The support infill pattern is rotated in the horizontal plane."
+msgstr "Indica l’orientamento della configurazione del riempimento per i supporti. La configurazione del riempimento del supporto viene ruotata sul piano orizzontale."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable label"
+msgid "Enable Support Brim"
+msgstr "Abilitazione brim del supporto"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_enable description"
+msgid "Generate a brim within the support infill regions of the first layer. This brim is printed underneath the support, not around it. Enabling this setting increases the adhesion of support to the build plate."
+msgstr "Genera un brim entro le zone di riempimento del supporto del primo strato. Questo brim viene stampato al di sotto del supporto, non intorno ad esso. L’abilitazione di questa impostazione aumenta l’adesione del supporto al piano di stampa."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width label"
+msgid "Support Brim Width"
+msgstr "Larghezza del brim del supporto"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_width description"
+msgid "The width of the brim to print underneath the support. A larger brim enhances adhesion to the build plate, at the cost of some extra material."
+msgstr "Corrisponde alla larghezza del brim da stampare al di sotto del supporto. Un brim più largo migliora l’adesione al piano di stampa, ma utilizza una maggiore quantità di materiale."
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count label"
+msgid "Support Brim Line Count"
+msgstr "Numero di linee del brim del supporto"
+
+#: fdmprinter.def.json
+msgctxt "support_brim_line_count description"
+msgid "The number of lines used for the support brim. More brim lines enhance adhesion to the build plate, at the cost of some extra material."
+msgstr "Corrisponde al numero di linee utilizzate per il brim del supporto. Più linee brim migliorano l’adesione al piano di stampa, ma utilizzano una maggiore quantità di materiale."
+
#: fdmprinter.def.json
msgctxt "support_z_distance label"
msgid "Support Z Distance"
@@ -3470,11 +3583,6 @@ msgctxt "support_interface_pattern option concentric"
msgid "Concentric"
msgstr "Concentriche"
-#: fdmprinter.def.json
-msgctxt "support_interface_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "3D concentrica"
-
#: fdmprinter.def.json
msgctxt "support_interface_pattern option zigzag"
msgid "Zig Zag"
@@ -3510,11 +3618,6 @@ msgctxt "support_roof_pattern option concentric"
msgid "Concentric"
msgstr "Concentriche"
-#: fdmprinter.def.json
-msgctxt "support_roof_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "3D concentrica"
-
#: fdmprinter.def.json
msgctxt "support_roof_pattern option zigzag"
msgid "Zig Zag"
@@ -3550,16 +3653,31 @@ msgctxt "support_bottom_pattern option concentric"
msgid "Concentric"
msgstr "Concentriche"
-#: fdmprinter.def.json
-msgctxt "support_bottom_pattern option concentric_3d"
-msgid "Concentric 3D"
-msgstr "3D concentrica"
-
#: fdmprinter.def.json
msgctxt "support_bottom_pattern option zigzag"
msgid "Zig Zag"
msgstr "Zig Zag"
+#: fdmprinter.def.json
+msgctxt "support_fan_enable label"
+msgid "Fan Speed Override"
+msgstr "Override velocità della ventola"
+
+#: fdmprinter.def.json
+msgctxt "support_fan_enable description"
+msgid "When enabled, the print cooling fan speed is altered for the skin regions immediately above the support."
+msgstr "Quando abilitata, la velocità della ventola di raffreddamento stampa viene modificata per le zone del rivestimento esterno subito sopra il supporto."
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed label"
+msgid "Supported Skin Fan Speed"
+msgstr "Velocità della ventola del rivestimento esterno supportato"
+
+#: fdmprinter.def.json
+msgctxt "support_supported_skin_fan_speed description"
+msgid "Percentage fan speed to use when printing the skin regions immediately above the support. Using a high fan speed can make the support easier to remove."
+msgstr "Percentuale della velocità della ventola da usare quando si stampano le zone del rivestimento esterno subito sopra il supporto. L’uso di una velocità ventola elevata può facilitare la rimozione del supporto."
+
#: fdmprinter.def.json
msgctxt "support_use_towers label"
msgid "Use Towers"
@@ -3742,6 +3860,16 @@ msgctxt "brim_line_count description"
msgid "The number of lines used for a brim. More brim lines enhance adhesion to the build plate, but also reduces the effective print area."
msgstr "Corrisponde al numero di linee utilizzate per un brim. Più linee brim migliorano l’adesione al piano di stampa, ma con riduzione dell'area di stampa."
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support label"
+msgid "Brim Replaces Support"
+msgstr "Brim in sostituzione del supporto"
+
+#: fdmprinter.def.json
+msgctxt "brim_replaces_support description"
+msgid "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions of the first layer of support by brim regions."
+msgstr "Abilita la stampa del brim intorno al modello anche se quello spazio dovrebbe essere occupato dal supporto. Sostituisce alcune zone del primo strato del supporto con zone del brim."
+
#: fdmprinter.def.json
msgctxt "brim_outside_only label"
msgid "Brim Only on Outside"
@@ -3884,8 +4012,8 @@ msgstr "Indica la larghezza delle linee dello strato di base del raft. Le linee
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing label"
-msgid "Raft Line Spacing"
-msgstr "Spaziatura delle linee del raft"
+msgid "Raft Base Line Spacing"
+msgstr "Spaziatura delle linee dello strato di base del raft"
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing description"
@@ -4102,16 +4230,6 @@ msgctxt "prime_tower_min_volume description"
msgid "The minimum volume for each layer of the prime tower in order to purge enough material."
msgstr "Il volume minimo per ciascuno strato della torre di innesco per scaricare materiale a sufficienza."
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness label"
-msgid "Prime Tower Thickness"
-msgstr "Spessore torre di innesco"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_wall_thickness description"
-msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
-msgstr "Lo spessore della torre di innesco cava. Uno spessore superiore alla metà del volume minimo della torre di innesco genera una torre di innesco densa."
-
#: fdmprinter.def.json
msgctxt "prime_tower_position_x label"
msgid "Prime Tower X Position"
@@ -4152,26 +4270,6 @@ msgctxt "prime_tower_wipe_enabled description"
msgid "After printing the prime tower with one nozzle, wipe the oozed material from the other nozzle off on the prime tower."
msgstr "Dopo la stampa della torre di innesco con un ugello, pulisce il materiale fuoriuscito dall’altro ugello sulla torre di innesco."
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe label"
-msgid "Wipe Nozzle After Switch"
-msgstr "Ugello pulitura dopo commutazione"
-
-#: fdmprinter.def.json
-msgctxt "dual_pre_wipe description"
-msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
-msgstr "Dopo la commutazione dell’estrusore, pulire il materiale fuoriuscito dall’ugello sul primo oggetto stampato. Questo effettua un movimento di pulitura lento in un punto in cui il materiale fuoriuscito causa il minor danno alla qualità della superficie della stampa."
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume label"
-msgid "Prime Tower Purge Volume"
-msgstr "Volume di scarico torre di innesco"
-
-#: fdmprinter.def.json
-msgctxt "prime_tower_purge_volume description"
-msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
-msgstr "Quantità di filamento da scaricare durante la pulizia della torre di innesco. Lo scarico è utile per compensare il filamento perso per colatura durante l'inattività dell'ugello."
-
#: fdmprinter.def.json
msgctxt "ooze_shield_enabled label"
msgid "Enable Ooze Shield"
@@ -4590,7 +4688,7 @@ msgstr "Larghezza linea rivestimento superficie superiore"
#: fdmprinter.def.json
msgctxt "roofing_line_width description"
msgid "Width of a single line of the areas at the top of the print."
-msgstr "Larghezza di un singola linea delle aree nella parte superiore della stampa"
+msgstr "Larghezza di un singola linea delle aree nella parte superiore della stampa."
#: fdmprinter.def.json
msgctxt "roofing_pattern label"
@@ -4657,6 +4755,16 @@ msgctxt "material_flow_temp_graph description"
msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
msgstr "Collegamento dei dati di flusso del materiale (in mm3 al secondo) alla temperatura (in °C)."
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference label"
+msgid "Minimum Polygon Circumference"
+msgstr "Circonferenza minima dei poligoni"
+
+#: fdmprinter.def.json
+msgctxt "minimum_polygon_circumference description"
+msgid "Polygons in sliced layers that have a circumference smaller than this amount will be filtered out. Lower values lead to higher resolution mesh at the cost of slicing time. It is meant mostly for high resolution SLA printers and very tiny 3D models with a lot of details."
+msgstr "I poligoni in strati sezionati con una circonferenza inferiore a questo valore verranno scartati. I valori inferiori generano una maglia con risoluzione superiore al costo del tempo di sezionamento. È dedicata in particolare alle stampanti SLA ad alta risoluzione e a modelli 3D molto piccoli, ricchi di dettagli."
+
#: fdmprinter.def.json
msgctxt "meshfix_maximum_resolution label"
msgid "Maximum Resolution"
@@ -5314,6 +5422,26 @@ msgctxt "adaptive_layer_height_threshold description"
msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
msgstr "Soglia per l’utilizzo o meno di uno strato di dimensioni minori. Questo numero è confrontato al valore dell’inclinazione più ripida di uno strato."
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle label"
+msgid "Overhanging Wall Angle"
+msgstr "Angolo parete di sbalzo"
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_angle description"
+msgid "Walls that overhang more than this angle will be printed using overhanging wall settings. When the value is 90, no walls will be treated as overhanging."
+msgstr "Le pareti che sbalzano oltre questo angolo verranno stampate utilizzando le impostazioni parete di sbalzo. Quando il valore è 90, nessuna parete sarà trattata come sbalzo."
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor label"
+msgid "Overhanging Wall Speed"
+msgstr "Velocità parete di sbalzo"
+
+#: fdmprinter.def.json
+msgctxt "wall_overhang_speed_factor description"
+msgid "Overhanging walls will be printed at this percentage of their normal print speed."
+msgstr "Le pareti di sbalzo verranno stampate a questa percentuale della loro normale velocità di stampa."
+
#: fdmprinter.def.json
msgctxt "bridge_settings_enabled label"
msgid "Enable Bridge Settings"
@@ -5344,16 +5472,6 @@ msgctxt "bridge_skin_support_threshold description"
msgid "If a skin region is supported for less than this percentage of its area, print it using the bridge settings. Otherwise it is printed using the normal skin settings."
msgstr "Se una zona di rivestimento esterno è supportata per meno di questa percentuale della sua area, effettuare la stampa utilizzando le impostazioni ponte. In caso contrario viene stampata utilizzando le normali impostazioni rivestimento esterno."
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang label"
-msgid "Bridge Wall Max Overhang"
-msgstr "Massimo sbalzo parete ponte"
-
-#: fdmprinter.def.json
-msgctxt "bridge_wall_max_overhang description"
-msgid "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings."
-msgstr "La larghezza massima ammessa per la zona di aria al di sotto di una linea perimetrale prima di stampare la parete utilizzando le impostazioni ponte. Espressa come percentuale della larghezza della linea perimetrale. Quando la distanza è superiore a questo valore, la linea perimetrale viene stampata utilizzando le normali impostazioni. Più è basso il valore, più è probabile che le linee perimetrali a sbalzo siano stampate utilizzando le impostazioni ponte."
-
#: fdmprinter.def.json
msgctxt "bridge_wall_coast label"
msgid "Bridge Wall Coasting"
@@ -5574,6 +5692,74 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Matrice di rotazione da applicare al modello quando caricato dal file."
+#~ msgctxt "connect_skin_polygons description"
+#~ msgid "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happend midway over infill this feature can reduce the top surface quality."
+#~ msgstr "Collega i percorsi del rivestimento esterno superiore/inferiore quando corrono uno accanto all’altro. Per le configurazioni concentriche, l’abilitazione di questa impostazione riduce notevolmente il tempo di spostamento, tuttavia poiché i collegamenti possono aver luogo a metà del riempimento, con questa funzione la qualità della superficie superiore potrebbe risultare inferiore."
+
+#~ msgctxt "infill_pattern description"
+#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
+#~ msgstr "Configurazione del materiale di riempimento della stampa. Il riempimento a linea e a zig zag cambia direzione su strati alternati, riducendo il costo del materiale. Le configurazioni a griglia, a triangolo, tri-esagonali, cubiche, ottagonali, a quarto di cubo, incrociate e concentriche sono stampate completamente su ogni strato. Le configurazioni cubiche, a quarto di cubo e ottagonali variano per ciascuno strato per garantire una più uniforme distribuzione della forza in ogni direzione."
+
+#~ msgctxt "infill_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "3D concentrica"
+
+#~ msgctxt "retraction_combing description"
+#~ msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
+#~ msgstr "La funzione Combing tiene l’ugello all’interno delle aree già stampate durante lo spostamento. In tal modo le corse di spostamento sono leggermente più lunghe ma si riduce l’esigenza di effettuare retrazioni. Se questa funzione viene disabilitata, il materiale viene retratto e l’ugello si sposta in linea retta al punto successivo. È anche possibile evitare il combing sopra le aree del rivestimento esterno superiore/inferiore effettuando il combing solo nel riempimento."
+
+#~ msgctxt "support_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "3D concentrica"
+
+#~ msgctxt "support_interface_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "3D concentrica"
+
+#~ msgctxt "support_roof_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "3D concentrica"
+
+#~ msgctxt "support_bottom_pattern option concentric_3d"
+#~ msgid "Concentric 3D"
+#~ msgstr "3D concentrica"
+
+#~ msgctxt "raft_base_line_spacing label"
+#~ msgid "Raft Line Spacing"
+#~ msgstr "Spaziatura delle linee del raft"
+
+#~ msgctxt "prime_tower_wall_thickness label"
+#~ msgid "Prime Tower Thickness"
+#~ msgstr "Spessore torre di innesco"
+
+#~ msgctxt "prime_tower_wall_thickness description"
+#~ msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
+#~ msgstr "Lo spessore della torre di innesco cava. Uno spessore superiore alla metà del volume minimo della torre di innesco genera una torre di innesco densa."
+
+#~ msgctxt "dual_pre_wipe label"
+#~ msgid "Wipe Nozzle After Switch"
+#~ msgstr "Ugello pulitura dopo commutazione"
+
+#~ msgctxt "dual_pre_wipe description"
+#~ msgid "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print."
+#~ msgstr "Dopo la commutazione dell’estrusore, pulire il materiale fuoriuscito dall’ugello sul primo oggetto stampato. Questo effettua un movimento di pulitura lento in un punto in cui il materiale fuoriuscito causa il minor danno alla qualità della superficie della stampa."
+
+#~ msgctxt "prime_tower_purge_volume label"
+#~ msgid "Prime Tower Purge Volume"
+#~ msgstr "Volume di scarico torre di innesco"
+
+#~ msgctxt "prime_tower_purge_volume description"
+#~ msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
+#~ msgstr "Quantità di filamento da scaricare durante la pulizia della torre di innesco. Lo scarico è utile per compensare il filamento perso per colatura durante l'inattività dell'ugello."
+
+#~ msgctxt "bridge_wall_max_overhang label"
+#~ msgid "Bridge Wall Max Overhang"
+#~ msgstr "Massimo sbalzo parete ponte"
+
+#~ msgctxt "bridge_wall_max_overhang description"
+#~ msgid "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings."
+#~ msgstr "La larghezza massima ammessa per la zona di aria al di sotto di una linea perimetrale prima di stampare la parete utilizzando le impostazioni ponte. Espressa come percentuale della larghezza della linea perimetrale. Quando la distanza è superiore a questo valore, la linea perimetrale viene stampata utilizzando le normali impostazioni. Più è basso il valore, più è probabile che le linee perimetrali a sbalzo siano stampate utilizzando le impostazioni ponte."
+
#~ msgctxt "optimize_wall_printing_order description"
#~ msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization."
#~ msgstr "Ottimizza l'ordine in cui vengono stampate le pareti in modo da ridurre le retrazioni e la distanza percorsa. L'abilitazione di questa funzione porta vantaggi per la maggior parte dei pezzi, ma alcuni potrebbero richiedere un maggior tempo di esecuzione, per cui si consiglia di confrontare i tempi di stampa stimati con e senza ottimizzazione."
diff --git a/resources/i18n/ja_JP/cura.po b/resources/i18n/ja_JP/cura.po
index 6995099db1..13916ef1e2 100644
--- a/resources/i18n/ja_JP/cura.po
+++ b/resources/i18n/ja_JP/cura.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.4\n"
+"Project-Id-Version: Cura 3.6\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2018-06-06 16:13+0200\n"
-"PO-Revision-Date: 2018-04-11 14:40+0100\n"
+"POT-Creation-Date: 2018-10-29 15:01+0100\n"
+"PO-Revision-Date: 2018-11-06 14:58+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: Japanese\n"
"Language: ja_JP\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
-"X-Generator: Poedit 2.0.4\n"
+"X-Generator: Poedit 2.0.6\n"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:22
msgctxt "@action"
@@ -40,6 +40,17 @@ msgctxt "@item:inlistbox"
msgid "G-code File"
msgstr "G-codeファイル"
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:67
+msgctxt "@error:not supported"
+msgid "GCodeWriter does not support non-text mode."
+msgstr "GCodeWriter は非テキストモードはサポートしていません。"
+
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:73
+#: /home/ruben/Projects/Cura/plugins/GCodeWriter/GCodeWriter.py:89
+msgctxt "@warning:status"
+msgid "Please prepare G-code before exporting."
+msgstr "エクスポートする前にG-codeの準備をしてください。"
+
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
msgctxt "@info:title"
msgid "3D Model Assistant"
@@ -53,106 +64,58 @@ msgid ""
"
{model_names}
\n"
"
Find out how to ensure the best possible print quality and reliability.