diff --git a/.gitignore b/.gitignore
index 0a66b6eb33..60b59e6829 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,7 +42,6 @@ plugins/cura-siemensnx-plugin
plugins/CuraBlenderPlugin
plugins/CuraCloudPlugin
plugins/CuraDrivePlugin
-plugins/CuraDrive
plugins/CuraLiveScriptingPlugin
plugins/CuraOpenSCADPlugin
plugins/CuraPrintProfileCreator
diff --git a/Jenkinsfile b/Jenkinsfile
index f9a3a9864a..a345ebbd05 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -38,20 +38,9 @@ parallel_nodes(['linux && cura', 'windows && cura'])
{
if (isUnix())
{
- // For Linux to show everything
- def branch = env.BRANCH_NAME
- if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}"))
- {
- branch = "master"
- }
- def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
-
+ // For Linux
try {
- sh """
- cd ..
- export PYTHONPATH=.:"${uranium_dir}"
- ${env.CURA_ENVIRONMENT_PATH}/${branch}/bin/pytest -x --verbose --full-trace --capture=no ./tests
- """
+ sh 'make CTEST_OUTPUT_ON_FAILURE=TRUE test'
} catch(e)
{
currentBuild.result = "UNSTABLE"
@@ -70,34 +59,6 @@ parallel_nodes(['linux && cura', 'windows && cura'])
}
}
}
-
- stage('Code Style')
- {
- if (isUnix())
- {
- // For Linux to show everything.
- // CMake also runs this test, but if it fails then the test just shows "failed" without details of what exactly failed.
- def branch = env.BRANCH_NAME
- if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}"))
- {
- branch = "master"
- }
- def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
-
- try
- {
- sh """
- cd ..
- export PYTHONPATH=.:"${uranium_dir}"
- ${env.CURA_ENVIRONMENT_PATH}/${branch}/bin/python3 run_mypy.py
- """
- }
- catch(e)
- {
- currentBuild.result = "UNSTABLE"
- }
- }
- }
}
}
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 f2ee92d65b..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})
diff --git a/cura.desktop.in b/cura.desktop.in
index fbe8b30fed..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=model/stl;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 23d38795eb..ed9099d425 100644
--- a/cura.sharedmimeinfo
+++ b/cura.sharedmimeinfo
@@ -19,4 +19,12 @@
+
+
+ Gcode file
+
+
+
+
+
\ No newline at end of file
diff --git a/cura/API/Account.py b/cura/API/Account.py
index 397e220478..a04f97ef1c 100644
--- a/cura/API/Account.py
+++ b/cura/API/Account.py
@@ -6,6 +6,7 @@ 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
@@ -37,15 +38,16 @@ class Account(QObject):
self._logged_in = False
self._callback_port = 32118
- self._oauth_root = "https://account.ultimaker.com"
- self._cloud_api_root = "https://api.ultimaker.com"
+ 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_drive_plugin",
- CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download packages.rating.read packages.rating.write",
+ 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)
@@ -60,6 +62,11 @@ class Account(QObject):
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
diff --git a/cura/API/Backups.py b/cura/API/Backups.py
index 8e5cd7b83a..ef74e74be0 100644
--- a/cura/API/Backups.py
+++ b/cura/API/Backups.py
@@ -1,6 +1,6 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import Tuple, Optional, TYPE_CHECKING
+from typing import Tuple, Optional, TYPE_CHECKING, Dict, Any
from cura.Backups.BackupsManager import BackupsManager
@@ -24,12 +24,12 @@ class Backups:
## 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/ApplicationMetadata.py b/cura/ApplicationMetadata.py
new file mode 100644
index 0000000000..a38466a715
--- /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.
+
+# ---------
+# Genearl constants used in Cura
+# ---------
+DEFAUKT_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/Backups/Backup.py b/cura/Backups/Backup.py
index 897d5fa979..714d6527fe 100644
--- a/cura/Backups/Backup.py
+++ b/cura/Backups/Backup.py
@@ -46,12 +46,13 @@ class Backup:
# 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():
+ 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()
diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py
index 547c3dae71..aa1f170707 100755
--- a/cura/BuildVolume.py
+++ b/cura/BuildVolume.py
@@ -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
@@ -105,6 +112,8 @@ class BuildVolume(SceneNode):
self._setting_change_timer.setSingleShot(True)
self._setting_change_timer.timeout.connect(self._onSettingChangeTimerFinished)
+
+
# Must be after setting _build_volume_message, apparently that is used in getMachineManager.
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
# Therefore this works.
@@ -479,6 +488,8 @@ class BuildVolume(SceneNode):
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1)
)
+ self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds
+
self.updateNodeBoundaryCheck()
def getBoundingBox(self) -> AxisAlignedBox:
@@ -489,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 = (
@@ -522,8 +535,11 @@ 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().getActiveExtruderStacks()
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 bfc2c8bddc..aeb26e9713 100755
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -4,7 +4,7 @@
import os
import sys
import time
-from typing import cast, TYPE_CHECKING, Optional, Callable
+from typing import cast, TYPE_CHECKING, Optional, Callable, List
import numpy
@@ -51,6 +51,7 @@ 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
@@ -113,8 +114,11 @@ from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions
from cura.ObjectsModel import ObjectsModel
+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
@@ -127,22 +131,12 @@ if TYPE_CHECKING:
numpy.seterr(all = "ignore")
-try:
- from cura.CuraVersion import CuraAppName, CuraAppDisplayName, CuraVersion, CuraBuildType, CuraDebugMode, CuraSDKVersion # type: ignore
-except ImportError:
- CuraAppName = "cura"
- CuraAppDisplayName = "Ultimaker Cura"
- CuraVersion = "master" # [CodeStyle: Reflecting imported value]
- CuraBuildType = ""
- CuraDebugMode = False
- CuraSDKVersion = "5.0.0"
-
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
@@ -162,12 +156,12 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes)
def __init__(self, *args, **kwargs):
- super().__init__(name = CuraAppName,
- app_display_name = CuraAppDisplayName,
- version = CuraVersion,
- api_version = CuraSDKVersion,
- 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)
@@ -182,7 +176,6 @@ 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
@@ -207,6 +200,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
@@ -261,6 +256,14 @@ class CuraApplication(QtApplication):
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):
@@ -293,7 +296,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))
@@ -429,7 +435,8 @@ class CuraApplication(QtApplication):
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.:
@@ -440,6 +447,7 @@ class CuraApplication(QtApplication):
"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.
@@ -493,7 +501,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", "{}")
@@ -632,9 +641,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
@@ -660,12 +667,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:
@@ -862,6 +869,19 @@ class CuraApplication(QtApplication):
self._object_manager = ObjectsModel.createObjectsModel()
return self._object_manager
+ @pyqtSlot(result = QObject)
+ 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:
@@ -936,7 +956,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")
@@ -954,6 +974,7 @@ class CuraApplication(QtApplication):
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")
@@ -975,6 +996,8 @@ 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)
@@ -1085,88 +1108,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):
@@ -1246,62 +1187,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
@@ -1315,7 +1269,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)
@@ -1324,20 +1278,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)
@@ -1348,7 +1290,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()
@@ -1398,13 +1340,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)
@@ -1448,7 +1389,7 @@ class CuraApplication(QtApplication):
@pyqtSlot()
- def groupSelected(self):
+ def groupSelected(self) -> None:
# Create a group-node
group_node = CuraSceneNode()
group_decorator = GroupDecorator()
@@ -1464,7 +1405,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
@@ -1476,7 +1418,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"):
@@ -1499,7 +1441,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()
@@ -1665,7 +1607,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)
diff --git a/cura/CuraVersion.py.in b/cura/CuraVersion.py.in
index f4c02ee396..1a500df248 100644
--- a/cura/CuraVersion.py.in
+++ b/cura/CuraVersion.py.in
@@ -9,3 +9,4 @@ 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..289a03d1c4
--- /dev/null
+++ b/cura/GlobalStacksModel.py
@@ -0,0 +1,63 @@
+# 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, pyqtSignal
+
+from UM.Settings.ContainerRegistry import ContainerRegistry
+from UM.Settings.ContainerStack import ContainerStack
+
+from cura.PrinterOutputDevice import ConnectionType
+
+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.ConnectionTypeRole, "connectionType")
+ self.addRoleName(self.MetaDataRole, "metadata")
+ self._container_stacks = []
+
+ # Listen to changes
+ ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
+ ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
+ ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
+ self._filter_dict = {}
+ self._update()
+
+ ## 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._update()
+
+ def _update(self) -> None:
+ items = []
+
+ container_stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
+
+ for container_stack in container_stacks:
+ connection_type = int(container_stack.getMetaDataEntry("connection_type", ConnectionType.NotConnected.value))
+ has_remote_connection = connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]
+ if container_stack.getMetaDataEntry("hidden", False) in ["True", True]:
+ continue
+
+ # TODO: Remove reference to connect group name.
+ items.append({"name": container_stack.getMetaDataEntry("connect_group_name", container_stack.getName()),
+ "id": container_stack.getId(),
+ "hasRemoteConnection": has_remote_connection,
+ "connectionType": connection_type,
+ "metadata": container_stack.getMetaData().copy()})
+ items.sort(key=lambda i: not i["hasRemoteConnection"])
+ self.setItems(items)
diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py
index f33934de0c..1941a558ba 100644
--- a/cura/LayerPolygon.py
+++ b/cura/LayerPolygon.py
@@ -5,6 +5,8 @@ from UM.Application import Application
from typing import Any
import numpy
+from UM.Logger import Logger
+
class LayerPolygon:
NoneType = 0
@@ -18,7 +20,8 @@ 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)
@@ -33,7 +36,8 @@ class LayerPolygon:
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
@@ -236,7 +240,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/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py
index 06f064315b..fb11123af6 100644
--- a/cura/Machines/MachineErrorChecker.py
+++ b/cura/Machines/MachineErrorChecker.py
@@ -64,21 +64,21 @@ class MachineErrorChecker(QObject):
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,6 +93,13 @@ 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) -> None:
if not self._check_in_progress:
diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py
index aee96f3153..160508e7a6 100644
--- a/cura/Machines/MaterialManager.py
+++ b/cura/Machines/MaterialManager.py
@@ -302,6 +302,10 @@ class MaterialManager(QObject):
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.
#
@@ -679,7 +683,11 @@ class MaterialManager(QObject):
@pyqtSlot(str)
def removeFavorite(self, root_material_id: str) -> None:
- self._favorites.remove(root_material_id)
+ 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.
@@ -688,4 +696,4 @@ class MaterialManager(QObject):
@pyqtSlot()
def getFavorites(self):
- return self._favorites
\ No newline at end of file
+ return self._favorites
diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py
index ef2e760330..212e4fcf1e 100644
--- a/cura/Machines/Models/BaseMaterialsModel.py
+++ b/cura/Machines/Models/BaseMaterialsModel.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 Optional, Dict, Set
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
@@ -9,6 +10,9 @@ from UM.Qt.ListModel import ListModel
# 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):
extruderPositionChanged = pyqtSignal()
@@ -54,8 +58,8 @@ class BaseMaterialsModel(ListModel):
self._extruder_position = 0
self._extruder_stack = None
- self._available_materials = None
- self._favorite_ids = None
+ self._available_materials = None # type: Optional[Dict[str, MaterialNode]]
+ self._favorite_ids = set() # type: Set[str]
def _updateExtruderStack(self):
global_stack = self._machine_manager.activeMachine
@@ -102,7 +106,6 @@ class BaseMaterialsModel(ListModel):
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
diff --git a/cura/Machines/Models/FavoriteMaterialsModel.py b/cura/Machines/Models/FavoriteMaterialsModel.py
index 18fe310c44..98a2a01597 100644
--- a/cura/Machines/Models/FavoriteMaterialsModel.py
+++ b/cura/Machines/Models/FavoriteMaterialsModel.py
@@ -1,20 +1,16 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from UM.Logger import Logger
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):
-
- # Perform standard check and reset if the check fails
if not self._canUpdate():
- self.setItems([])
return
# Get updated list of favorites
diff --git a/cura/Machines/Models/GenericMaterialsModel.py b/cura/Machines/Models/GenericMaterialsModel.py
index c276b865bf..8f41dd6a70 100644
--- a/cura/Machines/Models/GenericMaterialsModel.py
+++ b/cura/Machines/Models/GenericMaterialsModel.py
@@ -11,10 +11,7 @@ class GenericMaterialsModel(BaseMaterialsModel):
self._update()
def _update(self):
-
- # Perform standard check and reset if the check fails
if not self._canUpdate():
- self.setItems([])
return
# Get updated list of favorites
diff --git a/cura/Machines/Models/MaterialBrandsModel.py b/cura/Machines/Models/MaterialBrandsModel.py
index 458e4d9b47..ac82cf6670 100644
--- a/cura/Machines/Models/MaterialBrandsModel.py
+++ b/cura/Machines/Models/MaterialBrandsModel.py
@@ -28,12 +28,8 @@ class MaterialBrandsModel(BaseMaterialsModel):
self._update()
def _update(self):
-
- # Perform standard check and reset if the check fails
if not self._canUpdate():
- self.setItems([])
return
-
# Get updated list of favorites
self._favorite_ids = self._material_manager.getFavorites()
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 a01cc1194f..7ccc886bfe 100644
--- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
+++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
@@ -1,7 +1,7 @@
# 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
@@ -21,6 +21,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,20 +33,29 @@ 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_timer = QTimer() # type: QTimer
+ self._update_timer.setInterval(100)
+ self._update_timer.setSingleShot(True)
+ self._update_timer.timeout.connect(self._update)
+
self._update()
+ 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 +84,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)
diff --git a/cura/Machines/Models/SettingVisibilityPresetsModel.py b/cura/Machines/Models/SettingVisibilityPresetsModel.py
index 79131521f2..baa8e3ed29 100644
--- a/cura/Machines/Models/SettingVisibilityPresetsModel.py
+++ b/cura/Machines/Models/SettingVisibilityPresetsModel.py
@@ -6,6 +6,7 @@ from typing import Optional, List
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from UM.Logger import Logger
+from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.i18n import i18nCatalog
@@ -18,14 +19,20 @@ class SettingVisibilityPresetsModel(QObject):
onItemsChanged = pyqtSignal()
activePresetChanged = pyqtSignal()
- def __init__(self, preferences, parent = None):
+ def __init__(self, preferences: Preferences, parent = None) -> None:
super().__init__(parent)
self._items = [] # type: List[SettingVisibilityPreset]
+ self._custom_preset = SettingVisibilityPreset(preset_id = "custom", name = "Custom selection", weight = -100)
+
self._populate()
basic_item = self.getVisibilityPresetById("basic")
- basic_visibile_settings = ";".join(basic_item.settings)
+ 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
@@ -42,7 +49,8 @@ class SettingVisibilityPresetsModel(QObject):
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")
@@ -59,9 +67,7 @@ class SettingVisibilityPresetsModel(QObject):
def _populate(self) -> None:
from cura.CuraApplication import CuraApplication
items = [] # type: List[SettingVisibilityPreset]
-
- custom_preset = SettingVisibilityPreset(preset_id="custom", name ="Custom selection", weight = -100)
- items.append(custom_preset)
+ items.append(self._custom_preset)
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
setting_visibility_preset = SettingVisibilityPreset()
try:
@@ -77,7 +83,7 @@ class SettingVisibilityPresetsModel(QObject):
self.setItems(items)
@pyqtProperty("QVariantList", notify = onItemsChanged)
- def items(self):
+ def items(self) -> List[SettingVisibilityPreset]:
return self._items
def setItems(self, items: List[SettingVisibilityPreset]) -> None:
@@ -87,7 +93,7 @@ class SettingVisibilityPresetsModel(QObject):
@pyqtSlot(str)
def setActivePreset(self, preset_id: str) -> None:
- if preset_id == self._active_preset_item.presetId:
+ 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
@@ -96,7 +102,7 @@ class SettingVisibilityPresetsModel(QObject):
Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id)
return
- need_to_save_to_custom = self._active_preset_item.presetId == "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")
@@ -117,7 +123,9 @@ class SettingVisibilityPresetsModel(QObject):
@pyqtProperty(str, notify = activePresetChanged)
def activePreset(self) -> str:
- return self._active_preset_item.presetId
+ if self._active_preset_item is not None:
+ return self._active_preset_item.presetId
+ return ""
def _onPreferencesChanged(self, name: str) -> None:
if name != "general/visible_settings":
@@ -149,7 +157,12 @@ class SettingVisibilityPresetsModel(QObject):
else:
item_to_set = matching_preset_item
+ # 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.presetId)
+ 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/QualityGroup.py b/cura/Machines/QualityGroup.py
index 535ba453f8..f5bcbb0de8 100644
--- a/cura/Machines/QualityGroup.py
+++ b/cura/Machines/QualityGroup.py
@@ -4,6 +4,9 @@
from typing import Dict, Optional, List, Set
from PyQt5.QtCore import QObject, pyqtSlot
+
+from UM.Util import parseBool
+
from cura.Machines.ContainerNode import ContainerNode
@@ -29,6 +32,7 @@ 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:
@@ -51,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 fc8262de52..34cc9ce4b2 100644
--- a/cura/Machines/QualityManager.py
+++ b/cura/Machines/QualityManager.py
@@ -16,7 +16,7 @@ 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
@@ -235,7 +235,7 @@ class QualityManager(QObject):
for quality_type, quality_node in node.quality_type_map.items():
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
- quality_group.node_for_global = quality_node
+ quality_group.setGlobalNode(quality_node)
quality_group_dict[quality_type] = quality_group
break
@@ -337,7 +337,7 @@ class QualityManager(QObject):
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.
@@ -367,7 +367,7 @@ class QualityManager(QObject):
if node and node.quality_type_map:
for quality_type, quality_node in node.quality_type_map.items():
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
- quality_group.node_for_global = quality_node
+ quality_group.setGlobalNode(quality_node)
quality_group_dict[quality_type] = quality_group
break
@@ -538,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/VariantManager.py b/cura/Machines/VariantManager.py
index f6feb70e09..eaaa9fc5f0 100644
--- a/cura/Machines/VariantManager.py
+++ b/cura/Machines/VariantManager.py
@@ -107,7 +107,7 @@ class VariantManager:
break
return variant_node
- return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
+ 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()
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
index f75ad9c9f9..762d0db069 100644
--- a/cura/OAuth2/AuthorizationHelpers.py
+++ b/cura/OAuth2/AuthorizationHelpers.py
@@ -81,9 +81,14 @@ class AuthorizationHelpers:
# \param access_token: The encoded JWT token.
# \return: Dict containing some profile data.
def parseJWT(self, access_token: str) -> Optional["UserProfile"]:
- token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
- "Authorization": "Bearer {}".format(access_token)
- })
+ try:
+ token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
+ "Authorization": "Bearer {}".format(access_token)
+ })
+ except ConnectionError:
+ # Connection was suddenly dropped. Nothing we can do about that.
+ Logger.logException("e", "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
diff --git a/cura/OAuth2/AuthorizationService.py b/cura/OAuth2/AuthorizationService.py
index 4355891139..1e98dc9cee 100644
--- a/cura/OAuth2/AuthorizationService.py
+++ b/cura/OAuth2/AuthorizationService.py
@@ -52,8 +52,11 @@ class AuthorizationService:
if not self._user_profile:
# If no user profile was stored locally, we try to get it from JWT.
self._user_profile = self._parseJWT()
- if not self._user_profile:
+
+ 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
@@ -83,9 +86,11 @@ class AuthorizationService:
if not self.getUserProfile():
# We check if we can get the user profile.
# If we can't get it, that means the access token (JWT) was invalid or expired.
+ Logger.log("w", "Unable to get the user profile.")
return None
if self._auth_data is None:
+ Logger.log("d", "No auth data to retrieve the access_token from")
return None
return self._auth_data.access_token
diff --git a/cura/OAuth2/Models.py b/cura/OAuth2/Models.py
index 83fc22554f..0515e789e6 100644
--- a/cura/OAuth2/Models.py
+++ b/cura/OAuth2/Models.py
@@ -1,4 +1,6 @@
# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
from typing import Optional
diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py
index e11f70a54c..e863689e21 100644
--- a/cura/PrintInformation.py
+++ b/cura/PrintInformation.py
@@ -14,8 +14,7 @@ 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
@@ -361,7 +360,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:
@@ -395,28 +394,14 @@ class PrintInformation(QObject):
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, 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_times_per_feature:
self._initPrintTimesPerFeature(self._active_build_plate)
diff --git a/cura/PrinterOutput/ConfigurationModel.py b/cura/PrinterOutput/ConfigurationModel.py
index 89e609c913..f9d0c7e36b 100644
--- a/cura/PrinterOutput/ConfigurationModel.py
+++ b/cura/PrinterOutput/ConfigurationModel.py
@@ -54,7 +54,7 @@ class ConfigurationModel(QObject):
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/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
index 35d2ce014a..47a6caf3e5 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
@@ -55,7 +58,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._gcode = [] # type: List[str]
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
- 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:
@@ -125,7 +129,7 @@ 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.
@@ -133,7 +137,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
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)
@@ -143,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()
@@ -160,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:
@@ -178,49 +193,89 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._createNetworkManager()
assert (self._manager is not None)
- def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+ ## 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] = None,
+ on_finished: Optional[Callable[[QNetworkReply], None]] = None,
+ on_progress: Optional[Callable[[int, int], None]] = None) -> None:
self._validateManager()
- request = self._createEmptyRequest(target)
- self._last_request_time = time()
- if self._manager is not None:
- reply = self._manager.put(request, data.encode())
- self._registerOnFinishedCallback(reply, on_finished)
- else:
- Logger.log("e", "Could not find manager.")
- def delete(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+ request = self._createEmptyRequest(url, content_type = content_type)
+ self._last_request_time = time()
+
+ 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)
+
+ 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(target)
- self._last_request_time = time()
- if self._manager is not None:
- reply = self._manager.deleteResource(request)
- self._registerOnFinishedCallback(reply, on_finished)
- else:
- Logger.log("e", "Could not find manager.")
- def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+ 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(target)
- self._last_request_time = time()
- if self._manager is not None:
- reply = self._manager.get(request)
- self._registerOnFinishedCallback(reply, on_finished)
- else:
- Logger.log("e", "Could not find manager.")
- def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
+ 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)
+
+ ## 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(target)
- self._last_request_time = time()
- if self._manager is not None:
- reply = self._manager.post(request, data.encode())
- if on_progress is not None:
- reply.uploadProgress.connect(on_progress)
- self._registerOnFinishedCallback(reply, on_finished)
- else:
- Logger.log("e", "Could not find manager.")
- def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
+ request = self._createEmptyRequest(url)
+ self._last_request_time = time()
+
+ 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: Optional[Callable[[int, int], None]] = None) -> QNetworkReply:
self._validateManager()
request = self._createEmptyRequest(target, content_type=None)
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
@@ -282,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:
diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py
index 25b168e6fd..a77ac81909 100644
--- a/cura/PrinterOutput/PrintJobOutputModel.py
+++ b/cura/PrinterOutput/PrintJobOutputModel.py
@@ -118,17 +118,40 @@ 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:
+ time_elapsed = max(float(self.timeElapsed), 1.0) # Prevent a division by zero exception
+ result = time_elapsed / self.timeTotal
+ 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
diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py
index 969aa3c460..dbdf8c986c 100644
--- a/cura/PrinterOutputDevice.py
+++ b/cura/PrinterOutputDevice.py
@@ -1,5 +1,7 @@
# 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
@@ -12,9 +14,6 @@ from UM.Signal import signalemitter
from UM.Qt.QtApplication import QtApplication
from UM.FlameProfiler import pyqtSlot
-from enum import IntEnum # For the connection state tracking.
-from typing import Callable, List, Optional, Union
-
MYPY = False
if MYPY:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
@@ -28,11 +27,18 @@ 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.
@@ -46,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()
@@ -62,33 +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_updater = None #type: Optional[FirmwareUpdater]
- 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)
@@ -110,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:
@@ -131,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)
@@ -174,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:
@@ -207,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)
@@ -238,4 +258,4 @@ class PrinterOutputDevice(QObject, OutputDevice):
if not self._firmware_updater:
return
- self._firmware_updater.updateFirmware(firmware_file)
\ No newline at end of file
+ self._firmware_updater.updateFirmware(firmware_file)
diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py
index 39124c5ba3..9bdb6a2c0e 100644
--- a/cura/Scene/ConvexHullDecorator.py
+++ b/cura/Scene/ConvexHullDecorator.py
@@ -187,7 +187,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
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
@@ -239,7 +242,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
# 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)
+ _, idx = numpy.unique(vertex_byte_view, return_index = True)
vertex_data = vertex_data[idx] # Select the unique rows by index.
hull = Polygon(vertex_data)
@@ -272,7 +275,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
head_and_fans = self._getHeadAndFans().intersectionConvexHulls(mirrored)
# Min head hull is used for the push free
- convex_hull = self._compute2DConvexHeadFull()
+ convex_hull = self._compute2DConvexHull()
if convex_hull:
return convex_hull.getMinkowskiHull(head_and_fans)
return None
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/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py
index 3cfca1a944..133e04e8fc 100644
--- a/cura/Settings/ContainerManager.py
+++ b/cura/Settings/ContainerManager.py
@@ -419,13 +419,13 @@ 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: QUrl):
+ @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)
diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py
index 11640adc0f..9f44d075e0 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,7 +183,7 @@ 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:
@@ -221,13 +221,13 @@ class CuraContainerRegistry(ContainerRegistry):
# 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 !", "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...
@@ -274,11 +274,12 @@ class CuraContainerRegistry(ContainerRegistry):
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)
@@ -290,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
diff --git a/cura/Settings/CuraFormulaFunctions.py b/cura/Settings/CuraFormulaFunctions.py
index 1db01857f8..9ef80bd3d4 100644
--- a/cura/Settings/CuraFormulaFunctions.py
+++ b/cura/Settings/CuraFormulaFunctions.py
@@ -5,6 +5,7 @@ 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
@@ -38,7 +39,11 @@ class CuraFormulaFunctions:
extruder_position = int(machine_manager.defaultExtruderPosition)
global_stack = machine_manager.activeMachine
- extruder_stack = global_stack.extruders[str(extruder_position)]
+ 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):
diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py
index 9089ba96e9..a459d65ba3 100755
--- a/cura/Settings/ExtruderManager.py
+++ b/cura/Settings/ExtruderManager.py
@@ -63,7 +63,7 @@ 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
@@ -83,8 +83,9 @@ class ExtruderManager(QObject):
# \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:
@@ -144,7 +145,7 @@ class ExtruderManager(QObject):
@pyqtSlot(result = QObject)
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
- return self.getExtruderStack(self._active_extruder_index)
+ return self.getExtruderStack(self.activeExtruderIndex)
## Get an extruder stack by index
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
@@ -300,12 +301,7 @@ class ExtruderManager(QObject):
global_stack = self._application.getGlobalContainerStack()
if not global_stack:
return []
-
- result_tuple_list = sorted(list(global_stack.extruders.items()), key = lambda x: int(x[0]))
- result_list = [item[1] for item in result_tuple_list]
-
- machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value")
- return result_list[: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
@@ -344,6 +340,7 @@ 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.
diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py
index d7faedb71c..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()
diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py
index 52fc502bfc..076cebf60d 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.
#
@@ -160,7 +148,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 = []
@@ -172,7 +160,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
for extruder in Application.getInstance().getExtruderManager().getActiveExtruderStacks():
- position = extruder.getMetaDataEntry("position", default = "0") # Get the position
+ position = extruder.getMetaDataEntry("position", default = "0")
try:
position = int(position)
except ValueError:
@@ -183,7 +171,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 +184,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 +204,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 da1ec61254..8dba0f5204 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, pyqtSlot
+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
@@ -42,13 +42,23 @@ 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
+ extrudersChanged = 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
@@ -87,6 +97,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
diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py
index f321ce94a6..5763d2bcab 100755
--- a/cura/Settings/MachineManager.py
+++ b/cura/Settings/MachineManager.py
@@ -3,6 +3,8 @@
import collections
import time
+import re
+import unicodedata
from typing import Any, Callable, List, Dict, TYPE_CHECKING, Optional, cast
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
@@ -21,7 +23,7 @@ from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique
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
@@ -62,9 +64,7 @@ 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)
@@ -74,7 +74,7 @@ class MachineManager(QObject):
self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
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)
@@ -86,12 +86,14 @@ class MachineManager(QObject):
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)
@@ -115,15 +117,15 @@ class MachineManager(QObject):
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
+ 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.
@@ -156,7 +158,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()
@@ -174,6 +176,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:
@@ -201,7 +204,7 @@ class MachineManager(QObject):
extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != empty_variant_container else None
self._current_printer_configuration.extruderConfigurations.append(extruder_configuration)
- # an empty build plate configuration from the network printer is presented as an empty string, so use "" for an
+ # 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()
@@ -247,7 +250,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())
@@ -261,7 +264,7 @@ class MachineManager(QObject):
if global_variant.getMetaDataEntry("hardware_type") != "buildplate":
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 != empty_material_container:
self._global_container_stack.setMaterial(empty_material_container)
@@ -271,11 +274,6 @@ class MachineManager(QObject):
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:
@@ -295,6 +293,7 @@ class MachineManager(QObject):
self.activeMaterialChanged.emit()
self.rootMaterialChanged.emit()
+ self.numberExtrudersEnabledChanged.emit()
def _onContainersChanged(self, container: ContainerInterface) -> None:
self._instance_container_timer.start()
@@ -419,7 +418,7 @@ 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().getActiveExtruderStacks()
- count = 1 # we start with the global stack
+ 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:
@@ -438,12 +437,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 = ExtruderManager.getInstance().getActiveExtruderStacks()
for stack in stacks:
- if stack.getTop().findInstances():
+ if stack.getTop().getNumInstances() != 0:
return True
return False
@@ -453,10 +452,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 = ExtruderManager.getInstance().getActiveExtruderStacks()
+ 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.
@@ -516,10 +515,30 @@ class MachineManager(QObject):
return ""
@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:
+ connection_type = int(self._global_container_stack.getMetaDataEntry("connection_type", ConnectionType.NotConnected.value))
+ return connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]
+ return False
+
+ @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 activeMachineHasActiveNetworkConnection(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 activeMachineHasActiveCloudConnection(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)
+
def activeMachineNetworkKey(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("um_network_key", "")
@@ -616,6 +635,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,
@@ -646,7 +673,7 @@ class MachineManager(QObject):
new_value = self._active_container_stack.getProperty(key, "value")
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
@@ -662,7 +689,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)
@@ -731,7 +758,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]
@@ -739,7 +766,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:
@@ -864,7 +891,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()
@@ -909,21 +936,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
@@ -932,10 +956,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:
@@ -1001,12 +1024,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()
@@ -1080,7 +1103,6 @@ class MachineManager(QObject):
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.
#
@@ -1139,7 +1161,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.
@@ -1209,7 +1231,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():
@@ -1310,17 +1332,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("um_network_key", self.activeMachineNetworkKey())
new_machine.setMetaDataEntry("connect_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)
@@ -1380,10 +1403,10 @@ 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:
+ 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.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)
@@ -1419,7 +1442,7 @@ class MachineManager(QObject):
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:
@@ -1522,6 +1545,10 @@ 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 = empty_quality_container.getName()
@@ -1531,9 +1558,32 @@ class MachineManager(QObject):
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/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/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_app.py b/cura_app.py
index fea47e5a8b..bd6136f982 100755
--- a/cura_app.py
+++ b/cura_app.py
@@ -17,12 +17,6 @@ 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"]:
diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py
index 9ee2ef0dd4..55296979b5 100755
--- a/plugins/3MFReader/ThreeMFWorkspaceReader.py
+++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py
@@ -794,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
diff --git a/plugins/3MFReader/plugin.json b/plugins/3MFReader/plugin.json
index 5e41975752..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/3MFWriter/plugin.json b/plugins/3MFWriter/plugin.json
index 9ec4fb0c20..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": 5,
+ "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 7e5cf2dd3b..651abb0cac 100755
--- a/plugins/ChangeLogPlugin/ChangeLog.txt
+++ b/plugins/ChangeLogPlugin/ChangeLog.txt
@@ -943,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/plugin.json b/plugins/ChangeLogPlugin/plugin.json
index e09a08564a..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": 5,
+ "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..7c1f8faa83
--- /dev/null
+++ b/plugins/CuraDrive/src/DriveApiService.py
@@ -0,0 +1,168 @@
+# 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 []
+
+ backup_list_request = requests.get(self.BACKUP_URL, headers = {
+ "Authorization": "Bearer {}".format(access_token)
+ })
+
+ # 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..060f1496f1
--- /dev/null
+++ b/plugins/CuraDrive/src/DrivePluginExtension.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import os
+from datetime import datetime
+from typing import Optional, List, Dict, Any
+
+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 = CuraApplication.getInstance().getPluginRegistry().getPluginPath("CuraDrive")
+ 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 292330576b..2eabe62366 100644
--- a/plugins/CuraEngineBackend/Cura.proto
+++ b/plugins/CuraEngineBackend/Cura.proto
@@ -29,7 +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;
+ string name = 6; //Mesh name
}
message Progress
@@ -58,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)
@@ -108,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 58bc74f3f1..ef0898bb04 100755
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -86,8 +86,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)
@@ -203,7 +203,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()
@@ -229,6 +229,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:
@@ -245,7 +246,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.
@@ -253,7 +254,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)
@@ -322,7 +323,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
@@ -331,10 +332,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:
@@ -362,10 +363,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:
@@ -386,7 +387,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
@@ -395,28 +396,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
@@ -424,7 +425,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 )
@@ -442,7 +443,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:
@@ -451,7 +452,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:
@@ -518,7 +519,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)
@@ -567,7 +568,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()
@@ -613,7 +614,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:
@@ -632,7 +633,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.
@@ -832,7 +833,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 594bf3a43e..3cc23130ea 100644
--- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
+++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
@@ -137,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
@@ -195,7 +196,7 @@ class ProcessSlicedLayersJob(Job):
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 79b1e5249c..d3882a1209 100644
--- a/plugins/CuraEngineBackend/StartSliceJob.py
+++ b/plugins/CuraEngineBackend/StartSliceJob.py
@@ -66,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.
@@ -315,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.
diff --git a/plugins/CuraEngineBackend/plugin.json b/plugins/CuraEngineBackend/plugin.json
index 111698d8d1..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": 5,
- "version": "1.0.0",
+ "api": "6.0",
+ "version": "1.0.1",
"i18n-catalog": "cura"
}
diff --git a/plugins/CuraProfileReader/plugin.json b/plugins/CuraProfileReader/plugin.json
index 66a2a6a56b..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/CuraProfileWriter/plugin.json b/plugins/CuraProfileWriter/plugin.json
index 16c8c34152..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": 5,
+ "api": "6.0",
"i18n-catalog":"cura"
}
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
index 415931b7ec..13b2e69b63 100644
--- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
@@ -76,7 +76,7 @@ class FirmwareUpdateChecker(Extension):
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,
+ self._check_job = FirmwareUpdateCheckerJob(silent = silent,
machine_name = container_name, metadata = metadata,
callback = self._onActionTriggered)
self._check_job.start()
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
index 4c60b95824..a1460cca3f 100644
--- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
@@ -25,15 +25,14 @@ class FirmwareUpdateCheckerJob(Job):
ZERO_VERSION = Version(STRING_ZERO_VERSION)
EPSILON_VERSION = Version(STRING_EPSILON_VERSION)
- def __init__(self, container, silent, machine_name, metadata, callback) -> None:
+ def __init__(self, silent, machine_name, metadata, callback) -> None:
super().__init__()
- self._container = container
self.silent = silent
self._callback = callback
self._machine_name = machine_name
self._metadata = metadata
- self._lookups = None # type:Optional[FirmwareUpdateCheckerLookup]
+ 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:
@@ -45,7 +44,6 @@ class FirmwareUpdateCheckerJob(Job):
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
def parseVersionResponse(self, response: str) -> Version:
@@ -70,9 +68,6 @@ class FirmwareUpdateCheckerJob(Job):
return max_version
def run(self):
- if self._lookups is None:
- self._lookups = FirmwareUpdateCheckerLookup(self._machine_name, self._metadata)
-
try:
# Initialize a Preference that stores the last version checked for this printer.
Application.getInstance().getPreferences().addPreference(
@@ -83,16 +78,18 @@ class FirmwareUpdateCheckerJob(Job):
application_version = Application.getInstance().getVersion()
self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)}
- # get machine name from the definition container
- machine_name = self._container.definition.getName()
-
# If it is not None, then we compare between the checked_version and the current_version
machine_id = self._lookups.getMachineId()
if machine_id is not None:
- Logger.log("i", "You have a(n) {0} in the printer list. Let's check the firmware!".format(machine_name))
+ Logger.log("i", "You have a(n) {0} in the printer list. Do firmware-check.".format(self._machine_name))
current_version = self.getCurrentVersion()
+ # 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 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))
@@ -100,18 +97,20 @@ class FirmwareUpdateCheckerJob(Job):
# 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(setting_key_str, current_version)
- Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, 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 = FirmwareUpdateCheckerMessage(machine_id, machine_name, self._lookups.getRedirectUserUrl())
+ 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(machine_name))
+ 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
index a21ad3f0e5..c78e6f6025 100644
--- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py
@@ -18,7 +18,7 @@ class FirmwareUpdateCheckerLookup:
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"):
+ for check_url in machine_json.get("check_urls", []):
self._check_urls.append(check_url)
self._redirect_user = machine_json.get("update_url")
diff --git a/plugins/FirmwareUpdateChecker/plugin.json b/plugins/FirmwareUpdateChecker/plugin.json
index cbbd41e420..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": 5,
+ "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..8cd7083f16
--- /dev/null
+++ b/plugins/FirmwareUpdateChecker/tests/TestFirmwareUpdateChecker.py
@@ -0,0 +1,63 @@
+# 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
+
+from plugins.FirmwareUpdateChecker.FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob
+from plugins.FirmwareUpdateChecker.FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup
+
+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 = 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 = 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/FirmwareUpdater/FirmwareUpdaterMachineAction.py b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py
index 0a3e3a0ff0..59552775b6 100644
--- a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py
+++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py
@@ -57,7 +57,7 @@ class FirmwareUpdaterMachineAction(MachineAction):
outputDeviceCanUpdateFirmwareChanged = pyqtSignal()
@pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged)
def firmwareUpdater(self) -> Optional["FirmwareUpdater"]:
- if self._active_output_device and self._active_output_device.activePrinter.getController().can_update_firmware:
+ if self._active_output_device and self._active_output_device.activePrinter and self._active_output_device.activePrinter.getController().can_update_firmware:
self._active_firmware_updater = self._active_output_device.getFirmwareUpdater()
return self._active_firmware_updater
diff --git a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml
index 9a56dbb20a..b5b6c15f50 100644
--- a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml
+++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml
@@ -22,7 +22,7 @@ Cura.MachineAction
{
id: firmwareUpdaterMachineAction
anchors.fill: parent;
- UM.I18nCatalog { id: catalog; name:"cura"}
+ UM.I18nCatalog { id: catalog; name: "cura"}
spacing: UM.Theme.getSize("default_margin").height
Label
diff --git a/plugins/FirmwareUpdater/plugin.json b/plugins/FirmwareUpdater/plugin.json
index 3e09eab2b5..c1034e5e42 100644
--- a/plugins/FirmwareUpdater/plugin.json
+++ b/plugins/FirmwareUpdater/plugin.json
@@ -1,8 +1,8 @@
{
"name": "Firmware Updater",
"author": "Ultimaker B.V.",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Provides a machine actions for updating firmware.",
- "api": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/GCodeGzReader/plugin.json b/plugins/GCodeGzReader/plugin.json
index 3bd6a4097d..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/GCodeGzWriter/plugin.json b/plugins/GCodeGzWriter/plugin.json
index 4c6497317b..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/GCodeProfileReader/plugin.json b/plugins/GCodeProfileReader/plugin.json
index 9677628c85..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py
index 6fe2cb5260..baf21d47ce 100644
--- a/plugins/GCodeReader/FlavorParser.py
+++ b/plugins/GCodeReader/FlavorParser.py
@@ -364,6 +364,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/plugin.json b/plugins/GCodeReader/plugin.json
index 75b4d0cd4f..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/GCodeWriter/plugin.json b/plugins/GCodeWriter/plugin.json
index 3bbbab8b95..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml
index 12c6aa8dde..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
diff --git a/plugins/ImageReader/plugin.json b/plugins/ImageReader/plugin.json
index 08195863e8..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": 5,
+ "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 cd577218d5..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 179f5444e0..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": 5,
+ "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 004b4e3cfc..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,7 +13,8 @@ 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 : ""
@@ -23,7 +24,7 @@ Cura.MachineAction
target: base.extrudersModel
onModelChanged:
{
- var extruderCount = base.extrudersModel.rowCount();
+ var extruderCount = base.extrudersModel.count;
base.extruderTabsCount = extruderCount;
}
}
diff --git a/plugins/MachineSettingsAction/plugin.json b/plugins/MachineSettingsAction/plugin.json
index 571658e40a..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
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/plugin.json b/plugins/ModelChecker/plugin.json
index 3753c0cc88..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": 5,
+ "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..5fda32db9e
--- /dev/null
+++ b/plugins/MonitorStage/MonitorMain.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.10
+import QtQuick.Controls 1.4
+
+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
+
+ color: UM.Theme.getColor("viewport_overlay")
+ anchors.fill: parent
+
+ // 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
+ }
+ 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
+ }
+}
\ 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 bdaf53a36c..0468e6319b 100644
--- a/plugins/MonitorStage/__init__.py
+++ b/plugins/MonitorStage/__init__.py
@@ -7,14 +7,16 @@ 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 88b53840e0..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": 5,
+ "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 5d4e17a102..0e2bd88619 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
@@ -265,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")
diff --git a/plugins/PerObjectSettingsTool/plugin.json b/plugins/PerObjectSettingsTool/plugin.json
index 15fde63387..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
index 1a1ea92d10..78f9cc0516 100644
--- a/plugins/PostProcessingPlugin/PostProcessingPlugin.py
+++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
@@ -32,7 +32,8 @@ class PostProcessingPlugin(QObject, Extension):
def __init__(self, parent = None) -> None:
QObject.__init__(self, parent)
Extension.__init__(self)
- self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup)
+ 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
@@ -54,14 +55,14 @@ class PostProcessingPlugin(QObject, Extension):
def selectedScriptDefinitionId(self) -> Optional[str]:
try:
return self._script_list[self._selected_script_index].getDefinitionId()
- except:
+ except IndexError:
return ""
@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.
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml
index d492e06462..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,18 +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
@@ -84,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())
@@ -102,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
@@ -121,6 +129,7 @@ UM.Dialog
}
}
}
+
Button
{
id: removeButton
@@ -141,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")
@@ -176,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")
@@ -211,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")
@@ -252,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
@@ -271,26 +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;
@@ -300,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
@@ -315,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
@@ -342,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
}
@@ -398,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
}
@@ -462,6 +476,7 @@ UM.Dialog
Cura.SettingUnknown { }
}
}
+
rightButtons: Button
{
text: catalog.i18nc("@action:button", "Close")
@@ -469,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/plugin.json b/plugins/PostProcessingPlugin/plugin.json
index fea061e93b..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": 5,
+ "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 919b06d28e..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
},
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/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..b0f862dc48 100644
--- a/plugins/PrepareStage/PrepareStage.py
+++ b/plugins/PrepareStage/PrepareStage.py
@@ -1,19 +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.
import os.path
from UM.Application import Application
+from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources
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")
+ self.addDisplayComponent("menu", menu_component_path)
diff --git a/plugins/PrepareStage/plugin.json b/plugins/PrepareStage/plugin.json
index f0464313c7..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": 5,
+ "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..04241783e9
--- /dev/null
+++ b/plugins/PreviewStage/PreviewMain.qml
@@ -0,0 +1,18 @@
+// 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.0 as UM
+import Cura 1.0 as Cura
+
+
+Loader
+{
+ id: previewMain
+
+ source: UM.Controller.activeView != null && UM.Controller.activeView.mainComponent != null ? UM.Controller.activeView.mainComponent : ""
+}
\ 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/plugin.json b/plugins/RemovableDriveOutputDevice/plugin.json
index 36bb9ae186..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": 5,
+ "version": "1.0.1",
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/SimulationView/LayerSlider.qml b/plugins/SimulationView/LayerSlider.qml
index 1552506969..88f298d1f5 100644
--- a/plugins/SimulationView/LayerSlider.qml
+++ b/plugins/SimulationView/LayerSlider.qml
@@ -13,23 +13,20 @@ 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
@@ -80,7 +77,7 @@ Item
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
}
- // slider track
+ // Slider track
Rectangle
{
id: track
@@ -90,8 +87,6 @@ Item
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
- border.width: sliderRoot.trackBorderWidth
- border.color: sliderRoot.trackBorderColor
visible: sliderRoot.layersVisible
}
@@ -106,7 +101,7 @@ Item
anchors.horizontalCenter: sliderRoot.horizontalCenter
visible: sliderRoot.layersVisible
- // set the new value when dragging
+ // Set the new value when dragging
function onHandleDragged()
{
sliderRoot.manuallyChanged = true
@@ -140,9 +135,10 @@ Item
Rectangle
{
- width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
+ width: sliderRoot.trackThickness
height: parent.height + sliderRoot.handleSize
anchors.centerIn: parent
+ radius: sliderRoot.trackRadius
color: sliderRoot.rangeHandleColor
}
@@ -275,7 +271,7 @@ Item
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
@@ -385,9 +381,9 @@ Item
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
diff --git a/plugins/SimulationView/PathSlider.qml b/plugins/SimulationView/PathSlider.qml
index f3c28fb5f7..c7a43c6407 100644
--- a/plugins/SimulationView/PathSlider.qml
+++ b/plugins/SimulationView/PathSlider.qml
@@ -14,19 +14,17 @@ 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
@@ -68,8 +66,6 @@ Item
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
- border.width: sliderRoot.trackBorderWidth
- border.color: sliderRoot.trackBorderColor
visible: sliderRoot.pathsVisible
}
@@ -86,9 +82,10 @@ Item
Rectangle
{
- height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
+ height: sliderRoot.trackThickness
width: parent.width + sliderRoot.handleSize
anchors.centerIn: parent
+ radius: sliderRoot.trackRadius
color: sliderRoot.rangeColor
}
}
diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py
index 0ae8b4d9e4..3b2db2efac 100644
--- a/plugins/SimulationView/SimulationView.py
+++ b/plugins/SimulationView/SimulationView.py
@@ -16,6 +16,7 @@ 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
@@ -26,8 +27,8 @@ from UM.View.GL.OpenGL import OpenGL
from UM.View.GL.OpenGLContext import OpenGLContext
from UM.View.GL.ShaderProgram import ShaderProgram
-from UM.View.View import View
from UM.i18n import i18nCatalog
+from cura.CuraView import CuraView
from cura.Scene.ConvexHullNode import ConvexHullNode
from cura.CuraApplication import CuraApplication
@@ -48,15 +49,15 @@ 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) -> None:
- super().__init__()
+ def __init__(self, parent = None) -> None:
+ super().__init__(parent)
self._max_layers = 0
self._current_layer_num = 0
@@ -113,6 +114,16 @@ 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"))
+ 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"))
@@ -331,12 +342,16 @@ class SimulationView(View):
return self._extruder_count
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) -> float:
return self._max_feedrate
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) -> float:
diff --git a/plugins/SimulationView/SimulationView.qml b/plugins/SimulationView/SimulationView.qml
deleted file mode 100644
index be124157fb..0000000000
--- a/plugins/SimulationView/SimulationView.qml
+++ /dev/null
@@ -1,808 +0,0 @@
-// 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.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:
- {
- // 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)
- }
- }
-
- // 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:
- {
- // 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)
- }
- }
-
- // 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
- layerSlider.manuallyChanged = true
- pathSlider.manuallyChanged = true
- }
-
- function resumeSimulation()
- {
- UM.SimulationView.setSimulationRunning(true)
- iconSource = "./resources/simulation_pause.svg"
- 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 (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)
- }
- }
- // 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.
- 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 de2b9335d8..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))) {
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 93df98068f..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/SliceInfoPlugin/plugin.json b/plugins/SliceInfoPlugin/plugin.json
index 939e5ff235..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py
index b9ad5c8829..797d6dabec 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()
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 e70ec224dd..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": 5,
+ "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 7af35e0fb5..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
diff --git a/plugins/Toolbox/plugin.json b/plugins/Toolbox/plugin.json
index 2557185524..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": 5,
+ "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 9a98c998b0..d15d98eed7 100644
--- a/plugins/Toolbox/resources/qml/Toolbox.qml
+++ b/plugins/Toolbox/resources/qml/Toolbox.qml
@@ -14,17 +14,17 @@ Window
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
{
@@ -38,7 +38,7 @@ Window
{
id: mainView
width: parent.width
- z: -1
+ z: parent.z - 1
anchors
{
top: header.bottom
@@ -95,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 4aaea20813..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
@@ -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
{
@@ -86,14 +88,16 @@ Item
Label
{
text: catalog.i18nc("@label", "Website") + ":"
- 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", "Email") + ":"
- font: UM.Theme.getFont("very_small")
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
+ renderType: Text.NativeRendering
}
}
Column
@@ -118,10 +122,11 @@ Item
}
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
}
Label
@@ -134,10 +139,11 @@ Item
}
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 8524b7d1e5..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
@@ -61,9 +61,15 @@ Item
id: labelStyle
text: control.text
color: control.enabled ? (control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive")
- font: UM.Theme.getFont("default_bold")
- horizontalAlignment: Text.AlignRight
+ 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 a48cb2ee3f..db4e8c628f 100644
--- a/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.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
@@ -36,12 +36,19 @@ Item
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 || technicalDataSheetUrl !== undefined ||
- safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined)
+ safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined ||
+ materialWebsiteUrl !== undefined)
Item
{
@@ -60,6 +67,7 @@ Item
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
+ renderType: Text.NativeRendering
}
TableView
@@ -83,7 +91,7 @@ Item
model: packageData.supported_configs
headerDelegate: Rectangle
{
- color: UM.Theme.getColor("sidebar")
+ color: UM.Theme.getColor("main_background")
height: UM.Theme.getSize("toolbox_chart_row").height
Label
{
@@ -92,6 +100,7 @@ Item
text: styleData.value || ""
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
+ renderType: Text.NativeRendering
}
Rectangle
{
@@ -111,6 +120,7 @@ Item
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
}
}
itemDelegate: Item
@@ -123,6 +133,7 @@ Item
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
}
}
@@ -137,6 +148,7 @@ Item
elide: Text.ElideRight
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
}
}
@@ -180,7 +192,8 @@ Item
anchors.top: combatibilityItem.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height / 2
visible: base.technicalDataSheetUrl !== undefined ||
- base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined
+ base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined ||
+ base.materialWebsiteUrl !== undefined
height: visible ? contentHeight : 0
text:
{
@@ -208,11 +221,22 @@ Item
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)
+ }
+
return result
}
- 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
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml b/plugins/Toolbox/resources/qml/ToolboxConfirmUninstallResetDialog.qml
index 2c5d08aa72..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
@@ -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 2e5eae098c..4e44ea7d0b 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDetailList.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDetailList.qml
@@ -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 437a2ef351..fef2732af9 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml
@@ -1,11 +1,13 @@
// 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
@@ -24,7 +26,7 @@ Item
right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width
}
- height: UM.Theme.getSize("toolbox_detail_header").height
+ height: childrenRect.height + 3 * UM.Theme.getSize("default_margin").width
Rectangle
{
id: thumbnail
@@ -37,7 +39,7 @@ Item
leftMargin: UM.Theme.getSize("wide_margin").width
topMargin: UM.Theme.getSize("wide_margin").height
}
- color: white //Always a white background for image (regardless of theme).
+ color: UM.Theme.getColor("main_background")
Image
{
anchors.fill: parent
@@ -55,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 === null ? "" : (details.name || "")
- font: UM.Theme.getFont("large")
+ 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
@@ -81,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
@@ -116,11 +134,54 @@ 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 === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
- font: UM.Theme.getFont("very_small")
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
+ renderType: Text.NativeRendering
}
Label
{
@@ -133,8 +194,9 @@ Item
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
{
@@ -144,34 +206,25 @@ Item
{
return ""
}
- if (details.author_email)
- {
- return "" + details.author_name + ""
- }
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 === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
- font: UM.Theme.getFont("very_small")
+ 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 9061a8e06b..43f97baf3f 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.7
+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: 6
+ maximumLineCount: 25
elide: Text.ElideRight
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
+ renderType: Text.NativeRendering
}
}
diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml
index cd1e4cdbda..87fc5d6955 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml
@@ -1,40 +1,69 @@
// 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
+
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()
}
- // 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 +73,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 887140bbfa..a11c6ee963 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.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.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
{
@@ -14,91 +15,13 @@ Item
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
@@ -128,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/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 4fb70541d2..c8c1e56c82 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.
// 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
@@ -13,92 +13,79 @@ Rectangle
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")
+ }
+ 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/ToolboxInstalledPage.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml
index e683f89823..a85a69cbac 100644
--- a/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxInstalledPage.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.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
@@ -21,44 +21,40 @@ ScrollView
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
{
@@ -70,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 bea3893504..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,39 +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([])
+ 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 a31facf75a..d94fdf6bb7 100644
--- a/plugins/Toolbox/src/PackagesModel.py
+++ b/plugins/Toolbox/src/PackagesModel.py
@@ -33,20 +33,25 @@ 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 = []
@@ -99,6 +104,10 @@ class PackagesModel(ListModel):
"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.
diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py
index 4f3722d7ba..42cb5d52a8 100644
--- a/plugins/Toolbox/src/Toolbox.py
+++ b/plugins/Toolbox/src/Toolbox.py
@@ -13,11 +13,11 @@ 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
@@ -31,17 +31,14 @@ 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._sdk_version = None # type: Optional[Union[str, int]]
- self._cloud_api_version = None # type: Optional[int]
- self._cloud_api_root = None # type: Optional[str]
+ self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int]
+ self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: int
+ self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str
self._api_url = None # type: Optional[str]
# Network:
@@ -50,47 +47,35 @@ class Toolbox(QObject, Extension):
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_headers = [] # type: List[Tuple[bytes, bytes]]
+ self._updateRequestHeader()
+
+
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]]
- # Data:
- self._metadata = {
+ # 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": []
+ "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": AuthorsModel(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:
# ----------------------------------------------------------------------
@@ -120,6 +105,7 @@ class Toolbox(QObject, Extension):
self._restart_dialog_message = "" # type: str
self._application.initializationFinished.connect(self._onAppInitialized)
+ self._application.getCuraAPI().account.loginStateChanged.connect(self._updateRequestHeader)
# Signals:
# --------------------------------------------------------------------------
@@ -139,12 +125,38 @@ class Toolbox(QObject, Extension):
showLicenseDialog = pyqtSignal()
uninstallVariablesChanged = pyqtSignal()
+ 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 = [] # 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:
return self._license_dialog_plugin_name
@@ -168,9 +180,6 @@ 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,
@@ -178,44 +187,9 @@ class Toolbox(QObject, Extension):
)
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))
+ "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) -> Union[int, str]:
- if not hasattr(cura, "CuraVersion"):
- return self._application.getAPIVersion().getMajor()
- if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
- return self._application.getAPIVersion().getMajor()
- if not cura.CuraVersion.CuraSDKVersion: # type: ignore
- return self._application.getAPIVersion().getMajor()
- return cura.CuraVersion.CuraSDKVersion # type: ignore
-
@pyqtSlot()
def browsePackages(self) -> None:
# Create the network manager:
@@ -231,12 +205,6 @@ class Toolbox(QObject, Extension):
# Make remote requests:
self._makeRequestByType("packages")
self._makeRequestByType("authors")
- # TODO: Uncomment in the future when the tag-filtered api calls work in the cloud server
- # self._makeRequestByType("plugins_showcase")
- # self._makeRequestByType("plugins_available")
- # self._makeRequestByType("materials_showcase")
- # self._makeRequestByType("materials_available")
- # self._makeRequestByType("materials_generic")
# Gather installed packages:
self._updateInstalledModels()
@@ -267,12 +235,17 @@ class Toolbox(QObject, Extension):
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": plugin_data["plugin"]["api"],
+ "sdk_version": highest_sdk_version_supported,
"author": {
"author_id": plugin_data["plugin"]["author"],
"display_name": plugin_data["plugin"]["author"]
@@ -281,7 +254,7 @@ class Toolbox(QObject, Extension):
"description": plugin_data["plugin"]["description"]
}
return formatted
- except:
+ except KeyError:
Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data))
return None
@@ -319,13 +292,10 @@ class Toolbox(QObject, Extension):
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._metadata["plugins_installed"] = all_packages["plugin"] + list(self._old_plugin_metadata.values())
- self._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
+ 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)
@@ -479,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
@@ -491,11 +461,8 @@ class Toolbox(QObject, Extension):
def canUpdate(self, package_id: str) -> bool:
local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None:
- Logger.log("i", "Could not find package [%s] as installed in the package manager, fall back to check the old plugins",
- package_id)
local_package = self.getOldPluginPackageMetadata(package_id)
if local_package is None:
- Logger.log("i", "Could not find package [%s] in the old plugins", package_id)
return False
remote_package = self.getRemotePackage(package_id)
@@ -511,7 +478,10 @@ class Toolbox(QObject, Extension):
# 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:
- can_upgrade = local_package.get("sdk_version", 0) < remote_package.get("sdk_version", 0)
+ # 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
@@ -542,8 +512,8 @@ class Toolbox(QObject, Extension):
@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
@@ -551,7 +521,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, result = int)
def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
count = 0
- for package in self._metadata["packages"]:
+ for package in self._server_response_data["packages"]:
if package["package_type"] == "material":
if package["author"]["author_id"] == author_id:
count += 1
@@ -565,34 +535,31 @@ class Toolbox(QObject, Extension):
# Check for plugins that were installed with the old plugin browser
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 getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
return self._old_plugin_metadata.get(plugin_id)
- def loadingComplete(self) -> bool:
+ 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", "Marketplace: 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", "Marketplace: 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"):
@@ -601,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)
@@ -609,15 +577,15 @@ class Toolbox(QObject, Extension):
@pyqtSlot()
def cancelDownload(self) -> None:
- Logger.log("i", "Marketplace: User cancelled the download of a package.")
+ 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
@@ -643,22 +611,8 @@ class Toolbox(QObject, Extension):
self.resetDownload()
return
- # HACK: These request are not handled independently at this moment, but together from the "packages" call
- do_not_handle = [
- "materials_available",
- "materials_showcase",
- "materials_generic",
- "plugins_available",
- "plugins_showcase",
- ]
-
if reply.operation() == QNetworkAccessManager.GetOperation:
- for type, url in self._request_urls.items():
-
- # HACK: Do nothing because we'll handle these from the "packages" call
- if type in do_not_handle:
- continue
-
+ for response_type, url in self._request_urls.items():
if reply.url() == url:
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
try:
@@ -671,39 +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._metadata[type] = json_data["data"]
- self._models[type].setMetadata(self._metadata[type])
+ self._server_response_data[response_type] = json_data["data"]
+ self._models[response_type].setMetadata(self._server_response_data[response_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"})
- self.buildMaterialsModels()
- self.buildPluginsModels()
- 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", "Marketplace: 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
@@ -713,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
@@ -723,10 +678,10 @@ class Toolbox(QObject, Extension):
self._onDownloadComplete(file_path)
def _onDownloadComplete(self, file_path: str) -> None:
- Logger.log("i", "Marketplace: Download complete.")
+ Logger.log("i", "Download complete.")
package_info = self._package_manager.getPackageInfo(file_path)
if not package_info:
- Logger.log("w", "Marketplace: 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)
@@ -735,7 +690,6 @@ class Toolbox(QObject, Extension):
return
self.install(file_path)
- return
# Getter & Setters for Properties:
# --------------------------------------------------------------------------
@@ -758,8 +712,9 @@ 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)
@@ -767,16 +722,18 @@ class Toolbox(QObject, Extension):
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:
@@ -784,48 +741,48 @@ class Toolbox(QObject, Extension):
# 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 cast(PackagesModel, self._models["plugins_available"])
+ return self._plugins_available_model
- @pyqtProperty(QObject, notify = metadataChanged)
+ @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 cast(AuthorsModel, self._models["materials_available"])
+ return self._materials_available_model
- @pyqtProperty(QObject, notify = metadataChanged)
+ @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", "Marketplace: 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()
@@ -833,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", "Marketplace: 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()
@@ -841,21 +798,21 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str)
def removeFilters(self, model_type: str) -> None:
if not self._models[model_type]:
- Logger.log("w", "Marketplace: 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 buildMaterialsModels(self) -> None:
- self._metadata["materials_showcase"] = []
- self._metadata["materials_available"] = []
- self._metadata["materials_generic"] = []
+ def reBuildMaterialsModels(self) -> None:
+ materials_showcase_metadata = []
+ materials_available_metadata = []
+ materials_generic_metadata = []
- processed_authors = [] # type: List[str]
+ processed_authors = [] # type: List[str]
- for item in self._metadata["packages"]:
+ for item in self._server_response_data["packages"]:
if item["package_type"] == "material":
author = item["author"]
@@ -864,30 +821,29 @@ class Toolbox(QObject, Extension):
# Generic materials to be in the same section
if "generic" in item["tags"]:
- self._metadata["materials_generic"].append(item)
+ materials_generic_metadata.append(item)
else:
if "showcase" in item["tags"]:
- self._metadata["materials_showcase"].append(author)
+ materials_showcase_metadata.append(author)
else:
- self._metadata["materials_available"].append(author)
+ materials_available_metadata.append(author)
processed_authors.append(author["author_id"])
- self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
- self._models["materials_available"].setMetadata(self._metadata["materials_available"])
- self._models["materials_generic"].setMetadata(self._metadata["materials_generic"])
+ 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 buildPluginsModels(self) -> None:
- self._metadata["plugins_showcase"] = []
- self._metadata["plugins_available"] = []
+ def reBuildPluginsModels(self) -> None:
+ plugins_showcase_metadata = []
+ plugins_available_metadata = []
- for item in self._metadata["packages"]:
+ for item in self._server_response_data["packages"]:
if item["package_type"] == "plugin":
-
if "showcase" in item["tags"]:
- self._metadata["plugins_showcase"].append(item)
+ plugins_showcase_metadata.append(item)
else:
- self._metadata["plugins_available"].append(item)
+ plugins_available_metadata.append(item)
- self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
- self._models["plugins_available"].setMetadata(self._metadata["plugins_available"])
+ self._plugins_showcase_model.setMetadata(plugins_showcase_metadata)
+ self._plugins_available_model.setMetadata(plugins_available_metadata)
diff --git a/plugins/UFPWriter/plugin.json b/plugins/UFPWriter/plugin.json
index ab590353e0..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": 5,
+ "api": "6.0",
"i18n-catalog": "cura"
}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/__init__.py b/plugins/UM3NetworkPrinting/__init__.py
index e2ad5a2b12..3da7795589 100644
--- a/plugins/UM3NetworkPrinting/__init__.py
+++ b/plugins/UM3NetworkPrinting/__init__.py
@@ -1,11 +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
+
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/plugin.json b/plugins/UM3NetworkPrinting/plugin.json
index d415338374..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": 5,
+ "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
index 7e5c254e5c..6f054f9c19 100644
--- a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml
+++ b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml
@@ -8,11 +8,13 @@ import UM 1.3 as UM
import Cura 1.0 as Cura
Rectangle {
+ id: base
property var iconSource: null;
- color: clickArea.containsMouse ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary"); // "Cura Blue"
+ color: "#0a0850" // TODO: Theme!
height: width;
radius: Math.round(0.5 * width);
- width: 36 * screenScaleFactor;
+ width: 24 * screenScaleFactor;
+ property var enabled: true
UM.RecolorImage {
id: icon;
@@ -29,12 +31,18 @@ Rectangle {
MouseArea {
id: clickArea;
anchors.fill: parent;
- hoverEnabled: true;
+ hoverEnabled: base.enabled
onClicked: {
- if (OutputDevice.activeCameraUrl != "") {
- OutputDevice.setActiveCameraUrl("");
- } else {
- OutputDevice.setActiveCameraUrl(modelData.cameraUrl);
+ if (base.enabled)
+ {
+ if (OutputDevice.activeCameraUrl != "")
+ {
+ OutputDevice.setActiveCameraUrl("")
+ }
+ else
+ {
+ OutputDevice.setActiveCameraUrl(modelData.cameraUrl)
+ }
}
}
}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml b/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml
deleted file mode 100644
index 068c369a3f..0000000000
--- a/plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml
+++ /dev/null
@@ -1,110 +0,0 @@
-// 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
-
-Component {
- Rectangle {
- id: base;
- property var shadowRadius: UM.Theme.getSize("monitor_shadow_radius").width;
- property var cornerRadius: UM.Theme.getSize("monitor_corner_radius").width;
- anchors.fill: parent;
- color: UM.Theme.getColor("sidebar");
- visible: OutputDevice != null;
-
- UM.I18nCatalog {
- id: catalog;
- name: "cura";
- }
-
- Label {
- id: printingLabel;
- anchors {
- left: parent.left;
- leftMargin: 4 * UM.Theme.getSize("default_margin").width;
- margins: 2 * UM.Theme.getSize("default_margin").width;
- right: parent.right;
- top: parent.top;
- }
- color: UM.Theme.getColor("text");
- elide: Text.ElideRight;
- font: UM.Theme.getFont("large");
- text: catalog.i18nc("@label", "Printing");
- }
-
- Label {
- id: managePrintersLabel;
- anchors {
- bottom: printingLabel.bottom;
- right: printerScrollView.right;
- rightMargin: 4 * UM.Theme.getSize("default_margin").width;
- }
- color: UM.Theme.getColor("primary"); // "Cura Blue"
- font: UM.Theme.getFont("default");
- linkColor: UM.Theme.getColor("primary"); // "Cura Blue"
- text: catalog.i18nc("@label link to connect manager", "Manage printers");
- }
-
- MouseArea {
- anchors.fill: managePrintersLabel;
- hoverEnabled: true;
- onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel();
- onEntered: managePrintersLabel.font.underline = true;
- onExited: managePrintersLabel.font.underline = false;
- }
-
- // Skeleton loading
- Column {
- id: skeletonLoader;
- anchors {
- left: parent.left;
- leftMargin: UM.Theme.getSize("wide_margin").width;
- right: parent.right;
- rightMargin: UM.Theme.getSize("wide_margin").width;
- top: printingLabel.bottom;
- topMargin: UM.Theme.getSize("default_margin").height;
- }
- spacing: UM.Theme.getSize("default_margin").height - 10;
- visible: printerList.count === 0;
-
- PrinterCard {
- printer: null;
- }
- PrinterCard {
- printer: null;
- }
- }
-
- // Actual content
- ScrollView {
- id: printerScrollView;
- anchors {
- bottom: parent.bottom;
- left: parent.left;
- right: parent.right;
- top: printingLabel.bottom;
- topMargin: UM.Theme.getSize("default_margin").height;
- }
- style: UM.Theme.styles.scrollview;
-
- ListView {
- id: printerList;
- property var currentIndex: -1;
- anchors {
- fill: parent;
- leftMargin: UM.Theme.getSize("wide_margin").width;
- rightMargin: UM.Theme.getSize("wide_margin").width;
- }
- delegate: PrinterCard {
- printer: modelData;
- }
- model: OutputDevice.printers;
- spacing: UM.Theme.getSize("default_margin").height - 10;
- }
- }
- }
-}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml b/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml
deleted file mode 100644
index d210ab40f3..0000000000
--- a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml
+++ /dev/null
@@ -1,132 +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.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;
- property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight");
- property var cornerRadius: UM.Theme.getSize("monitor_corner_radius").width;
- color: UM.Theme.getColor("viewport_background");
- height: maximumHeight;
- onVisibleChanged: {
- if (monitorFrame != null && !monitorFrame.visible) {
- OutputDevice.setActiveCameraUrl("");
- }
- }
- width: maximumWidth;
-
- UM.I18nCatalog {
- id: catalog;
- name: "cura";
- }
-
- Label {
- id: manageQueueLabel;
- anchors {
- bottom: queuedLabel.bottom;
- right: queuedPrintJobs.right;
- rightMargin: 3 * UM.Theme.getSize("default_margin").width;
- }
- color: UM.Theme.getColor("primary");
- font: UM.Theme.getFont("default");
- linkColor: UM.Theme.getColor("primary");
- text: catalog.i18nc("@label link to connect manager", "Manage queue");
- }
-
- MouseArea {
- anchors.fill: manageQueueLabel;
- hoverEnabled: true;
- onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel();
- onEntered: manageQueueLabel.font.underline = true;
- onExited: manageQueueLabel.font.underline = false;
- }
-
- Label {
- id: queuedLabel;
- anchors {
- left: queuedPrintJobs.left;
- leftMargin: 3 * UM.Theme.getSize("default_margin").width + 5 * screenScaleFactor;
- top: parent.top;
- topMargin: 2 * UM.Theme.getSize("default_margin").height;
- }
- color: UM.Theme.getColor("text");
- font: UM.Theme.getFont("large");
- text: catalog.i18nc("@label", "Queued");
- }
-
- Column {
- id: skeletonLoader;
- anchors {
- bottom: parent.bottom;
- bottomMargin: UM.Theme.getSize("default_margin").height;
- horizontalCenter: parent.horizontalCenter;
- top: queuedLabel.bottom;
- topMargin: UM.Theme.getSize("default_margin").height;
- }
- visible: !queuedPrintJobs.visible;
- width: Math.min(800 * screenScaleFactor, maximumWidth);
-
- PrintJobInfoBlock {
- anchors {
- left: parent.left;
- leftMargin: UM.Theme.getSize("default_margin").width;
- right: parent.right;
- rightMargin: UM.Theme.getSize("default_margin").width;
- }
- printJob: null; // Use as skeleton
- }
-
- PrintJobInfoBlock {
- anchors {
- left: parent.left;
- leftMargin: UM.Theme.getSize("default_margin").width;
- right: parent.right;
- rightMargin: UM.Theme.getSize("default_margin").width;
- }
- printJob: null; // Use as skeleton
- }
- }
-
- ScrollView {
- id: queuedPrintJobs;
- anchors {
- top: queuedLabel.bottom;
- topMargin: UM.Theme.getSize("default_margin").height;
- horizontalCenter: parent.horizontalCenter;
- bottomMargin: UM.Theme.getSize("default_margin").height;
- bottom: parent.bottom;
- }
- style: UM.Theme.styles.scrollview;
- visible: OutputDevice.receivedPrintJobs;
- width: Math.min(800 * screenScaleFactor, maximumWidth);
-
- ListView {
- id: printJobList;
- anchors.fill: parent;
- delegate: PrintJobInfoBlock {
- anchors {
- left: parent.left;
- leftMargin: UM.Theme.getSize("default_margin").width;
- right: parent.right;
- rightMargin: UM.Theme.getSize("default_margin").width;
- }
- printJob: modelData;
- }
- model: OutputDevice.queuedPrintJobs;
- spacing: UM.Theme.getSize("default_margin").height - 2 * UM.Theme.getSize("monitor_shadow_radius").width;
- }
- }
-
- PrinterVideoStream {
- anchors.fill: parent;
- cameraUrl: OutputDevice.activeCameraUrl;
- visible: OutputDevice.activeCameraUrl != "";
- }
- }
-}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml
index 58443115a9..3883a7e285 100644
--- a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml
+++ b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml
@@ -28,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()
}
@@ -64,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
}
@@ -72,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:")
}
@@ -182,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
@@ -204,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)
}
@@ -219,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
{
@@ -231,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)
@@ -268,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 : ""
}
}
@@ -294,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)
@@ -315,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." )
}
@@ -358,9 +371,10 @@ 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
diff --git a/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml b/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml
new file mode 100644
index 0000000000..d4c123652d
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml
@@ -0,0 +1,87 @@
+// 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
+
+// TODO: Theme & documentation!
+// 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: "#CCCCCC"
+ property color headerBackgroundColor: "white"
+ property color headerHoverColor: "#e8f2fc"
+ property color drawerBackgroundColor: "white"
+ 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
+ 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
+ Behavior on height
+ {
+ NumberAnimation
+ {
+ duration: 100
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/HorizontalLine.qml b/plugins/UM3NetworkPrinting/resources/qml/HorizontalLine.qml
deleted file mode 100644
index aeb92697ad..0000000000
--- a/plugins/UM3NetworkPrinting/resources/qml/HorizontalLine.qml
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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
-
-Rectangle {
- color: UM.Theme.getColor("monitor_lining_light"); // TODO: Maybe theme separately? Maybe not.
- height: UM.Theme.getSize("default_lining").height;
- width: parent.width;
-}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorBuildplateConfiguration.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorBuildplateConfiguration.qml
new file mode 100644
index 0000000000..192a5a7f76
--- /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" : "#eeeeee" // TODO: Theme!
+ radius: Math.floor(height / 2)
+ }
+
+ UM.RecolorImage
+ {
+ id: buildplateIcon
+ anchors.centerIn: parent
+ color: "#0a0850" // TODO: Theme! (Standard purple)
+ height: parent.height
+ source: "../svg/icons/buildplate.svg"
+ width: height
+ visible: buildplate
+ }
+ }
+
+ Label
+ {
+ id: buildplateLabel
+ color: "#191919" // TODO: Theme!
+ 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..de24ee5a8c
--- /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: "#fff6f6f6" // TODO: Theme!
+ }
+ GradientStop
+ {
+ position: 1.0
+ color: "#66f6f6f6" // TODO: Theme!
+ }
+ }
+ }
+ 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 ? "#e8f2fc" : "#ffffff" // TODO: Theme!
+ border.width: 1 * screenScaleFactor // TODO: Theme!
+ border.color: "#cccccc" // TODO: Theme!
+ 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: "#152950" // TODO: Theme!
+ 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 ? "#e8f2fc" : "#ffffff" // TODO: Theme!
+ border.width: 1 * screenScaleFactor // TODO: Theme!
+ border.color: "#cccccc" // TODO: Theme!
+ 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: "#152950" // TODO: Theme!
+ 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: "#66f6f6f6" // TODO: Theme!
+ }
+ GradientStop
+ {
+ position: 1.0
+ color: "#fff6f6f6" // TODO: Theme!
+ }
+ }
+ }
+ 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 ? "#777777" : "#d8d8d8" // TODO: Theme!
+ 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 += "
";
- for (var i = 0; i < printJob.configurationChanges.length; i++) {
- var change = printJob.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 = "";
- }
- result += "
Print with the recommended settings for the selected printer, material and quality."),
- item: sidebarSimple
- })
- modesListModel.append({
- text: catalog.i18nc("@title:tab", "Custom"),
- tooltipText: catalog.i18nc("@tooltip", "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."),
- item: sidebarAdvanced
- })
-
- var index = Math.round(UM.Preferences.getValue("cura/active_mode"))
-
- if(index != null && !isNaN(index))
- {
- currentModeIndex = index;
- }
- else
- {
- currentModeIndex = 0;
- }
- }
-
- UM.SettingPropertyProvider
- {
- id: machineExtruderCount
-
- containerStack: Cura.MachineManager.activeMachine
- key: "machine_extruder_count"
- watchedProperties: [ "value" ]
- storeIndex: 0
- }
-
- UM.SettingPropertyProvider
- {
- id: machineHeatedBed
-
- containerStack: Cura.MachineManager.activeMachine
- key: "machine_heated_bed"
- watchedProperties: [ "value" ]
- storeIndex: 0
- }
-}
diff --git a/resources/qml/PrimaryButton.qml b/resources/qml/PrimaryButton.qml
new file mode 100644
index 0000000000..fca63d2cdb
--- /dev/null
+++ b/resources/qml/PrimaryButton.qml
@@ -0,0 +1,20 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+
+import UM 1.4 as UM
+import Cura 1.1 as Cura
+
+
+Cura.ActionButton
+{
+ shadowEnabled: true
+ shadowColor: enabled ? UM.Theme.getColor("primary_button_shadow"): UM.Theme.getColor("action_button_disabled_shadow")
+ color: UM.Theme.getColor("primary_button")
+ textColor: UM.Theme.getColor("primary_button_text")
+ outlineColor: "transparent"
+ disabledColor: UM.Theme.getColor("action_button_disabled")
+ textDisabledColor: UM.Theme.getColor("action_button_disabled_text")
+ hoverColor: UM.Theme.getColor("primary_button_hover")
+}
\ No newline at end of file
diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml
index 12e95d1e89..d44acf0adb 100644
--- a/resources/qml/PrintMonitor.qml
+++ b/resources/qml/PrintMonitor.qml
@@ -11,136 +11,176 @@ import Cura 1.0 as Cura
import "PrinterOutput"
-Column
+
+Item
{
- id: printMonitor
+ id: base
+ UM.I18nCatalog { id: catalog; name: "cura"}
+
+ function showTooltip(item, position, text)
+ {
+ tooltip.text = text;
+ position = item.mapToItem(base, position.x - UM.Theme.getSize("default_arrow").width, position.y);
+ tooltip.show(position);
+ }
+
+ function hideTooltip()
+ {
+ tooltip.hide();
+ }
+
+ function strPadLeft(string, pad, length) {
+ return (new Array(length + 1).join(pad) + string).slice(-length);
+ }
+
+ function getPrettyTime(time)
+ {
+ var hours = Math.floor(time / 3600)
+ time -= hours * 3600
+ var minutes = Math.floor(time / 60);
+ time -= minutes * 60
+ var seconds = Math.floor(time);
+
+ var finalTime = strPadLeft(hours, "0", 2) + ":" + strPadLeft(minutes, "0", 2) + ":" + strPadLeft(seconds, "0", 2);
+ return finalTime;
+ }
+
property var connectedDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
property var activePrinter: connectedDevice != null ? connectedDevice.activePrinter : null
property var activePrintJob: activePrinter != null ? activePrinter.activePrintJob: null
- Cura.ExtrudersModel
+ PrintSetupTooltip
{
- id: extrudersModel
- simpleNames: true
+ id: tooltip
}
- OutputDeviceHeader
+ Column
{
- outputDevice: connectedDevice
- }
+ id: printMonitor
- Rectangle
- {
- color: UM.Theme.getColor("sidebar_lining")
- width: parent.width
- height: childrenRect.height
+ anchors.fill: parent
- Flow
+ property var extrudersModel: CuraApplication.getExtrudersModel()
+
+ OutputDeviceHeader
{
- id: extrudersGrid
- spacing: UM.Theme.getSize("sidebar_lining_thin").width
+ outputDevice: connectedDevice
+ }
+
+ Rectangle
+ {
+ color: UM.Theme.getColor("wide_lining")
width: parent.width
+ height: childrenRect.height
- Repeater
+ Flow
{
- id: extrudersRepeater
- model: activePrinter != null ? activePrinter.extruders : null
+ id: extrudersGrid
+ spacing: UM.Theme.getSize("thick_lining").width
+ width: parent.width
- ExtruderBox
+ Repeater
{
- color: UM.Theme.getColor("sidebar")
- width: index == machineExtruderCount.properties.value - 1 && index % 2 == 0 ? extrudersGrid.width : Math.round(extrudersGrid.width / 2 - UM.Theme.getSize("sidebar_lining_thin").width / 2)
- extruderModel: modelData
+ id: extrudersRepeater
+ model: activePrinter != null ? activePrinter.extruders : null
+
+ ExtruderBox
+ {
+ color: UM.Theme.getColor("main_background")
+ width: index == machineExtruderCount.properties.value - 1 && index % 2 == 0 ? extrudersGrid.width : Math.round(extrudersGrid.width / 2 - UM.Theme.getSize("thick_lining").width / 2)
+ extruderModel: modelData
+ }
}
}
}
- }
- Rectangle
- {
- color: UM.Theme.getColor("sidebar_lining")
- width: parent.width
- height: UM.Theme.getSize("sidebar_lining_thin").width
- }
-
- HeatedBedBox
- {
- visible: {
- if(activePrinter != null && activePrinter.bedTemperature != -1)
- {
- return true
- }
- return false
- }
- printerModel: activePrinter
- }
-
- UM.SettingPropertyProvider
- {
- id: bedTemperature
- containerStack: Cura.MachineManager.activeMachine
- key: "material_bed_temperature"
- watchedProperties: ["value", "minimum_value", "maximum_value", "resolve"]
- storeIndex: 0
-
- property var resolve: Cura.MachineManager.activeStack != Cura.MachineManager.activeMachine ? properties.resolve : "None"
- }
-
- UM.SettingPropertyProvider
- {
- id: machineExtruderCount
- containerStack: Cura.MachineManager.activeMachine
- key: "machine_extruder_count"
- watchedProperties: ["value"]
- }
-
- ManualPrinterControl
- {
- printerModel: activePrinter
- visible: activePrinter != null ? activePrinter.canControlManually : false
- }
-
-
- MonitorSection
- {
- label: catalog.i18nc("@label", "Active print")
- width: base.width
- visible: activePrinter != null
- }
-
-
- MonitorItem
- {
- label: catalog.i18nc("@label", "Job Name")
- value: activePrintJob != null ? activePrintJob.name : ""
- width: base.width
- visible: activePrinter != null
- }
-
- MonitorItem
- {
- label: catalog.i18nc("@label", "Printing Time")
- value: activePrintJob != null ? getPrettyTime(activePrintJob.timeTotal) : ""
- width: base.width
- visible: activePrinter != null
- }
-
- MonitorItem
- {
- label: catalog.i18nc("@label", "Estimated time left")
- value: activePrintJob != null ? getPrettyTime(activePrintJob.timeTotal - activePrintJob.timeElapsed) : ""
- visible:
+ Rectangle
{
- if(activePrintJob == null)
+ color: UM.Theme.getColor("wide_lining")
+ width: parent.width
+ height: UM.Theme.getSize("thick_lining").width
+ }
+
+ HeatedBedBox
+ {
+ visible:
{
+ if(activePrinter != null && activePrinter.bedTemperature != -1)
+ {
+ return true
+ }
return false
}
-
- return (activePrintJob.state == "printing" ||
- activePrintJob.state == "resuming" ||
- activePrintJob.state == "pausing" ||
- activePrintJob.state == "paused")
+ printerModel: activePrinter
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: bedTemperature
+ containerStack: Cura.MachineManager.activeMachine
+ key: "material_bed_temperature"
+ watchedProperties: ["value", "minimum_value", "maximum_value", "resolve"]
+ storeIndex: 0
+
+ property var resolve: Cura.MachineManager.activeStack != Cura.MachineManager.activeMachine ? properties.resolve : "None"
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: machineExtruderCount
+ containerStack: Cura.MachineManager.activeMachine
+ key: "machine_extruder_count"
+ watchedProperties: ["value"]
+ }
+
+ ManualPrinterControl
+ {
+ printerModel: activePrinter
+ visible: activePrinter != null ? activePrinter.canControlManually : false
+ }
+
+
+ MonitorSection
+ {
+ label: catalog.i18nc("@label", "Active print")
+ width: base.width
+ visible: activePrinter != null
+ }
+
+
+ MonitorItem
+ {
+ label: catalog.i18nc("@label", "Job Name")
+ value: activePrintJob != null ? activePrintJob.name : ""
+ width: base.width
+ visible: activePrinter != null
+ }
+
+ MonitorItem
+ {
+ label: catalog.i18nc("@label", "Printing Time")
+ value: activePrintJob != null ? getPrettyTime(activePrintJob.timeTotal) : ""
+ width: base.width
+ visible: activePrinter != null
+ }
+
+ MonitorItem
+ {
+ label: catalog.i18nc("@label", "Estimated time left")
+ value: activePrintJob != null ? getPrettyTime(activePrintJob.timeTotal - activePrintJob.timeElapsed) : ""
+ visible:
+ {
+ if(activePrintJob == null)
+ {
+ return false
+ }
+
+ return (activePrintJob.state == "printing" ||
+ activePrintJob.state == "resuming" ||
+ activePrintJob.state == "pausing" ||
+ activePrintJob.state == "paused")
+ }
+ width: base.width
}
- width: base.width
}
-}
+}
\ No newline at end of file
diff --git a/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml b/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml
new file mode 100644
index 0000000000..98bb5c0405
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/Custom/CustomPrintSetup.qml
@@ -0,0 +1,131 @@
+// 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.0
+
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+
+Item
+{
+ id: customPrintSetup
+
+ property real padding: UM.Theme.getSize("default_margin").width
+ property bool multipleExtruders: extrudersModel.count > 1
+
+ property var extrudersModel: CuraApplication.getExtrudersModel()
+
+ // Profile selector row
+ GlobalProfileSelector
+ {
+ id: globalProfileRow
+ anchors
+ {
+ top: parent.top
+ topMargin: parent.padding
+ left: parent.left
+ leftMargin: parent.padding
+ right: parent.right
+ rightMargin: parent.padding
+ }
+ }
+
+ UM.TabRow
+ {
+ id: tabBar
+
+ visible: multipleExtruders // The tab row is only visible when there are more than 1 extruder
+
+ anchors
+ {
+ top: globalProfileRow.bottom
+ topMargin: UM.Theme.getSize("default_margin").height
+ left: parent.left
+ leftMargin: parent.padding
+ right: parent.right
+ rightMargin: parent.padding
+ }
+
+ Repeater
+ {
+ id: repeater
+ model: extrudersModel
+ delegate: UM.TabRowButton
+ {
+ contentItem: Item
+ {
+ Cura.ExtruderIcon
+ {
+ anchors.horizontalCenter: parent.horizontalCenter
+ materialColor: model.color
+ extruderEnabled: model.enabled
+ }
+ }
+ onClicked:
+ {
+ Cura.ExtruderManager.setActiveExtruderIndex(tabBar.currentIndex)
+ }
+ }
+ }
+
+ //When active extruder changes for some other reason, switch tabs.
+ //Don't directly link currentIndex to Cura.ExtruderManager.activeExtruderIndex!
+ //This causes a segfault in Qt 5.11. Something with VisualItemModel removing index -1. We have to use setCurrentIndex instead.
+ Connections
+ {
+ target: Cura.ExtruderManager
+ onActiveExtruderChanged:
+ {
+ tabBar.setCurrentIndex(Cura.ExtruderManager.activeExtruderIndex);
+ }
+ }
+
+ //When the model of the extruders is rebuilt, the list of extruders is briefly emptied and rebuilt.
+ //This causes the currentIndex of the tab to be in an invalid position which resets it to 0.
+ //Therefore we need to change it back to what it was: The active extruder index.
+ Connections
+ {
+ target: repeater.model
+ onModelChanged:
+ {
+ tabBar.setCurrentIndex(Cura.ExtruderManager.activeExtruderIndex)
+ }
+ }
+ }
+
+ Rectangle
+ {
+ anchors
+ {
+ top: tabBar.visible ? tabBar.bottom : globalProfileRow.bottom
+ topMargin: -UM.Theme.getSize("default_lining").width
+ left: parent.left
+ leftMargin: parent.padding
+ right: parent.right
+ rightMargin: parent.padding
+ bottom: parent.bottom
+ }
+ z: tabBar.z - 1
+ // Don't show the border when only one extruder
+
+ border.color: tabBar.visible ? UM.Theme.getColor("lining") : "transparent"
+ border.width: UM.Theme.getSize("default_lining").width
+
+ color: UM.Theme.getColor("main_background")
+ Cura.SettingView
+ {
+ anchors
+ {
+ fill: parent
+ topMargin: UM.Theme.getSize("default_margin").height
+ leftMargin: UM.Theme.getSize("default_margin").width
+ // Small space for the scrollbar
+ rightMargin: UM.Theme.getSize("narrow_margin").width
+ // Compensate for the negative margin in the parent
+ bottomMargin: UM.Theme.getSize("default_lining").width
+ }
+ }
+ }
+}
diff --git a/resources/qml/PrintSetupSelector/Custom/GlobalProfileSelector.qml b/resources/qml/PrintSetupSelector/Custom/GlobalProfileSelector.qml
new file mode 100644
index 0000000000..32c07a52a6
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/Custom/GlobalProfileSelector.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.7
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Styles 1.1
+import QtQuick.Layouts 1.2
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Item
+{
+ id: globalProfileRow
+ height: childrenRect.height
+
+ Label
+ {
+ id: globalProfileLabel
+ anchors
+ {
+ top: parent.top
+ bottom: parent.bottom
+ left: parent.left
+ right: globalProfileSelection.left
+ }
+ text: catalog.i18nc("@label", "Profile")
+ font: UM.Theme.getFont("medium")
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ ToolButton
+ {
+ id: globalProfileSelection
+
+ text: generateActiveQualityText()
+ width: UM.Theme.getSize("print_setup_big_item").width
+ height: UM.Theme.getSize("print_setup_big_item").height
+ anchors
+ {
+ top: parent.top
+ right: parent.right
+ }
+ tooltip: Cura.MachineManager.activeQualityOrQualityChangesName
+ style: UM.Theme.styles.print_setup_header_button
+ activeFocusOnPress: true
+ menu: Cura.ProfileMenu { }
+
+ function generateActiveQualityText()
+ {
+ var result = Cura.MachineManager.activeQualityOrQualityChangesName
+ if (Cura.MachineManager.isActiveQualityExperimental)
+ {
+ result += " (Experimental)"
+ }
+
+ if (Cura.MachineManager.isActiveQualitySupported)
+ {
+ if (Cura.MachineManager.activeQualityLayerHeight > 0)
+ {
+ result += " "
+ result += " - "
+ result += Cura.MachineManager.activeQualityLayerHeight + "mm"
+ result += ""
+ }
+ }
+
+ return result
+ }
+
+ UM.SimpleButton
+ {
+ id: customisedSettings
+
+ visible: Cura.MachineManager.hasUserSettings
+ width: UM.Theme.getSize("print_setup_icon").width
+ height: UM.Theme.getSize("print_setup_icon").height
+
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ anchors.rightMargin: Math.round(UM.Theme.getSize("setting_preferences_button_margin").width - UM.Theme.getSize("thick_margin").width)
+
+ color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button");
+ iconSource: UM.Theme.getIcon("star")
+
+ onClicked:
+ {
+ forceActiveFocus();
+ Cura.Actions.manageProfiles.trigger()
+ }
+ onEntered:
+ {
+ var content = catalog.i18nc("@tooltip","Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.")
+ base.showTooltip(globalProfileRow, Qt.point(-UM.Theme.getSize("default_margin").width, 0), content)
+ }
+ onExited: base.hideTooltip()
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/PrintSetupSelector/PrintSetupSelector.qml b/resources/qml/PrintSetupSelector/PrintSetupSelector.qml
new file mode 100644
index 0000000000..48ac07679d
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/PrintSetupSelector.qml
@@ -0,0 +1,35 @@
+// 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.0
+
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+Cura.ExpandableComponent
+{
+ id: printSetupSelector
+
+ property bool preSlicedData: PrintInformation.preSliced
+
+ contentPadding: UM.Theme.getSize("default_lining").width
+ contentHeaderTitle: catalog.i18nc("@label", "Print settings")
+ enabled: !preSlicedData
+ disabledText: catalog.i18nc("@label shown when we load a Gcode file", "Print setup disabled. G code file can not be modified.")
+
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
+
+ headerItem: PrintSetupSelectorHeader {}
+
+ property var extrudersModel: CuraApplication.getExtrudersModel()
+
+ contentItem: PrintSetupSelectorContents {}
+
+ onExpandedChanged: UM.Preferences.setValue("view/settings_visible", expanded)
+ Component.onCompleted: expanded = UM.Preferences.getValue("view/settings_visible")
+}
\ No newline at end of file
diff --git a/resources/qml/PrintSetupSelector/PrintSetupSelectorContents.qml b/resources/qml/PrintSetupSelector/PrintSetupSelectorContents.qml
new file mode 100644
index 0000000000..e5de8bfb7d
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/PrintSetupSelectorContents.qml
@@ -0,0 +1,202 @@
+// 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.0 as Cura
+
+import "Recommended"
+import "Custom"
+
+Item
+{
+ id: content
+
+ width: UM.Theme.getSize("print_setup_widget").width - 2 * UM.Theme.getSize("default_margin").width
+ height: contents.height + buttonRow.height
+
+ enum Mode
+ {
+ Recommended = 0,
+ Custom = 1
+ }
+
+ // Set the current mode index to the value that is stored in the preferences or Recommended mode otherwise.
+ property int currentModeIndex:
+ {
+ var index = Math.round(UM.Preferences.getValue("cura/active_mode"))
+
+ if (index != null && !isNaN(index))
+ {
+ return index
+ }
+ return PrintSetupSelectorContents.Mode.Recommended
+ }
+ onCurrentModeIndexChanged: UM.Preferences.setValue("cura/active_mode", currentModeIndex)
+
+ Item
+ {
+ id: contents
+ // Use the visible property instead of checking the currentModeIndex. That creates a binding that
+ // evaluates the new height every time the visible property changes.
+ height: recommendedPrintSetup.visible ? recommendedPrintSetup.height : customPrintSetup.height
+
+ anchors
+ {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+
+ RecommendedPrintSetup
+ {
+ id: recommendedPrintSetup
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ }
+ visible: currentModeIndex == PrintSetupSelectorContents.Mode.Recommended
+ }
+
+ CustomPrintSetup
+ {
+ id: customPrintSetup
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ }
+ height: UM.Preferences.getValue("view/settings_list_height") - UM.Theme.getSize("default_margin").height
+ Connections
+ {
+ target: UM.Preferences
+ onPreferenceChanged:
+ {
+ if (preference !== "view/settings_list_height" && preference !== "general/window_height" && preference !== "general/window_state")
+ {
+ return;
+ }
+
+ customPrintSetup.height =
+ Math.min
+ (
+ UM.Preferences.getValue("view/settings_list_height"),
+ base.height - (customPrintSetup.mapToItem(null, 0, 0).y + buttonRow.height + UM.Theme.getSize("default_margin").height)
+ );
+ }
+ }
+ visible: currentModeIndex == PrintSetupSelectorContents.Mode.Custom
+ }
+ }
+
+ Rectangle
+ {
+ id: buttonsSeparator
+
+ // The buttonsSeparator is inside the contents. This is to avoid a double line in the bottom
+ anchors.bottom: contents.bottom
+ width: parent.width
+ height: UM.Theme.getSize("default_lining").height
+ color: UM.Theme.getColor("lining")
+ }
+
+ Item
+ {
+ id: buttonRow
+ property real padding: UM.Theme.getSize("default_margin").width
+ height: childrenRect.height + 2 * padding
+
+ anchors
+ {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+
+ Cura.SecondaryButton
+ {
+ id: recommendedButton
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.margins: parent.padding
+ leftPadding: UM.Theme.getSize("default_margin").width
+ rightPadding: UM.Theme.getSize("default_margin").width
+ text: catalog.i18nc("@button", "Recommended")
+ iconSource: UM.Theme.getIcon("arrow_left")
+ visible: currentModeIndex == PrintSetupSelectorContents.Mode.Custom
+ onClicked: currentModeIndex = PrintSetupSelectorContents.Mode.Recommended
+ }
+
+ Cura.SecondaryButton
+ {
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.margins: UM.Theme.getSize("default_margin").width
+ leftPadding: UM.Theme.getSize("default_margin").width
+ rightPadding: UM.Theme.getSize("default_margin").width
+ text: catalog.i18nc("@button", "Custom")
+ iconSource: UM.Theme.getIcon("arrow_right")
+ isIconOnRightSide: true
+ visible: currentModeIndex == PrintSetupSelectorContents.Mode.Recommended
+ onClicked: currentModeIndex = PrintSetupSelectorContents.Mode.Custom
+ }
+
+ //Invisible area at the bottom with which you can resize the panel.
+ MouseArea
+ {
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ top: recommendedButton.bottom
+ topMargin: UM.Theme.getSize("default_lining").height
+ }
+ cursorShape: Qt.SplitVCursor
+ visible: currentModeIndex == PrintSetupSelectorContents.Mode.Custom
+ drag
+ {
+ target: parent
+ axis: Drag.YAxis
+ }
+ onMouseYChanged:
+ {
+ if(drag.active)
+ {
+ // position of mouse relative to dropdown align vertical centre of mouse area to cursor
+ // v------------------------------v v------------v
+ var h = mouseY + buttonRow.y + content.y - height / 2 | 0;
+ if(h < 200 * screenScaleFactor) //Enforce a minimum size.
+ {
+ h = 200 * screenScaleFactor;
+ }
+
+ //Absolute mouse Y position in the window, to prevent it from going outside the window.
+ var mouse_absolute_y = mapToGlobal(mouseX, mouseY).y - UM.Preferences.getValue("general/window_top");
+ if(mouse_absolute_y > base.height)
+ {
+ h -= mouse_absolute_y - base.height;
+ }
+
+ UM.Preferences.setValue("view/settings_list_height", h);
+ }
+ }
+
+ UM.RecolorImage
+ {
+ width: parent.width * 0.05
+ height: parent.height * 0.3
+
+ anchors.centerIn: parent
+
+ source: UM.Theme.getIcon("grip_lines")
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml b/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml
new file mode 100644
index 0000000000..96b244d803
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/PrintSetupSelectorHeader.qml
@@ -0,0 +1,88 @@
+// 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 QtQuick.Layouts 1.3
+
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+RowLayout
+{
+ property string enabledText: catalog.i18nc("@label:Should be short", "On")
+ property string disabledText: catalog.i18nc("@label:Should be short", "Off")
+
+ Cura.IconWithText
+ {
+ source: UM.Theme.getIcon("category_layer_height")
+ text:
+ {
+ if (Cura.MachineManager.activeStack)
+ {
+ var text = Cura.MachineManager.activeQualityOrQualityChangesName
+ if (!Cura.MachineManager.hasNotSupportedQuality)
+ {
+ text += " " + layerHeight.properties.value + "mm"
+ text += Cura.MachineManager.isActiveQualityExperimental ? " - " + catalog.i18nc("@label", "Experimental") : ""
+ }
+ return text
+ }
+ return ""
+ }
+ font: UM.Theme.getFont("medium")
+
+ UM.SettingPropertyProvider
+ {
+ id: layerHeight
+ containerStack: Cura.MachineManager.activeStack
+ key: "layer_height"
+ watchedProperties: ["value"]
+ }
+ }
+
+ Cura.IconWithText
+ {
+ source: UM.Theme.getIcon("category_infill")
+ text: Cura.MachineManager.activeStack ? parseInt(infillDensity.properties.value) + "%" : "0%"
+ font: UM.Theme.getFont("medium")
+
+ UM.SettingPropertyProvider
+ {
+ id: infillDensity
+ containerStack: Cura.MachineManager.activeStack
+ key: "infill_sparse_density"
+ watchedProperties: ["value"]
+ }
+ }
+
+ Cura.IconWithText
+ {
+ source: UM.Theme.getIcon("category_support")
+ text: supportEnabled.properties.value == "True" ? enabledText : disabledText
+ font: UM.Theme.getFont("medium")
+
+ UM.SettingPropertyProvider
+ {
+ id: supportEnabled
+ containerStack: Cura.MachineManager.activeMachine
+ key: "support_enable"
+ watchedProperties: ["value"]
+ }
+ }
+
+ Cura.IconWithText
+ {
+ source: UM.Theme.getIcon("category_adhesion")
+ text: platformAdhesionType.properties.value != "skirt" && platformAdhesionType.properties.value != "none" ? enabledText : disabledText
+ font: UM.Theme.getFont("medium")
+
+ UM.SettingPropertyProvider
+ {
+ id: platformAdhesionType
+ containerStack: Cura.MachineManager.activeMachine
+ key: "adhesion_type"
+ watchedProperties: [ "value"]
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedAdhesionSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedAdhesionSelector.qml
new file mode 100644
index 0000000000..941199707c
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedAdhesionSelector.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.7
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+
+//
+// Adhesion
+//
+Item
+{
+ id: enableAdhesionRow
+ height: childrenRect.height
+
+ property real labelColumnWidth: Math.round(width / 3)
+
+ Cura.IconWithText
+ {
+ id: enableAdhesionRowTitle
+ anchors.top: parent.top
+ anchors.left: parent.left
+ source: UM.Theme.getIcon("category_adhesion")
+ text: catalog.i18nc("@label", "Adhesion")
+ font: UM.Theme.getFont("medium")
+ width: labelColumnWidth
+ }
+
+ Item
+ {
+ id: enableAdhesionContainer
+ height: enableAdhesionCheckBox.height
+
+ anchors
+ {
+ left: enableAdhesionRowTitle.right
+ right: parent.right
+ verticalCenter: enableAdhesionRowTitle.verticalCenter
+ }
+
+ CheckBox
+ {
+ id: enableAdhesionCheckBox
+ anchors.verticalCenter: parent.verticalCenter
+
+ property alias _hovered: adhesionMouseArea.containsMouse
+
+ //: Setting enable printing build-plate adhesion helper checkbox
+ style: UM.Theme.styles.checkbox
+ enabled: recommendedPrintSetup.settingsEnabled
+
+ visible: platformAdhesionType.properties.enabled == "True"
+ checked: platformAdhesionType.properties.value != "skirt" && platformAdhesionType.properties.value != "none"
+
+ MouseArea
+ {
+ id: adhesionMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+
+ onClicked:
+ {
+ var adhesionType = "skirt"
+ if (!parent.checked)
+ {
+ // Remove the "user" setting to see if the rest of the stack prescribes a brim or a raft
+ platformAdhesionType.removeFromContainer(0)
+ adhesionType = platformAdhesionType.properties.value
+ if(adhesionType == "skirt" || adhesionType == "none")
+ {
+ // If the rest of the stack doesn't prescribe an adhesion-type, default to a brim
+ adhesionType = "brim"
+ }
+ }
+ platformAdhesionType.setPropertyValue("value", adhesionType)
+ }
+
+ onEntered:
+ {
+ base.showTooltip(enableAdhesionCheckBox, Qt.point(-enableAdhesionContainer.x - UM.Theme.getSize("thick_margin").width, 0),
+ catalog.i18nc("@label", "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."));
+ }
+ onExited: base.hideTooltip()
+ }
+ }
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: platformAdhesionType
+ containerStack: Cura.MachineManager.activeMachine
+ removeUnusedValue: false //Doesn't work with settings that are resolved.
+ key: "adhesion_type"
+ watchedProperties: [ "value", "enabled" ]
+ storeIndex: 0
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedInfillDensitySelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedInfillDensitySelector.qml
new file mode 100644
index 0000000000..19f199fea6
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedInfillDensitySelector.qml
@@ -0,0 +1,255 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+
+//
+// Infill
+//
+Item
+{
+ id: infillRow
+ height: childrenRect.height
+
+ property real labelColumnWidth: Math.round(width / 3)
+
+ // Create a binding to update the icon when the infill density changes
+ Binding
+ {
+ target: infillRowTitle
+ property: "source"
+ value:
+ {
+ var density = parseInt(infillDensity.properties.value)
+ if (parseInt(infillSteps.properties.value) != 0)
+ {
+ return UM.Theme.getIcon("gradual")
+ }
+ if (density <= 0)
+ {
+ return UM.Theme.getIcon("hollow")
+ }
+ if (density < 40)
+ {
+ return UM.Theme.getIcon("sparse")
+ }
+ if (density < 90)
+ {
+ return UM.Theme.getIcon("dense")
+ }
+ return UM.Theme.getIcon("solid")
+ }
+ }
+
+ // We use a binding to make sure that after manually setting infillSlider.value it is still bound to the property provider
+ Binding
+ {
+ target: infillSlider
+ property: "value"
+ value: parseInt(infillDensity.properties.value)
+ }
+
+ // Here are the elements that are shown in the left column
+ Cura.IconWithText
+ {
+ id: infillRowTitle
+ anchors.top: parent.top
+ anchors.left: parent.left
+ source: UM.Theme.getIcon("category_infill")
+ text: catalog.i18nc("@label", "Infill") + " (%)"
+ font: UM.Theme.getFont("medium")
+ width: labelColumnWidth
+ }
+
+ Item
+ {
+ id: infillSliderContainer
+ height: childrenRect.height
+
+ anchors
+ {
+ left: infillRowTitle.right
+ right: parent.right
+ verticalCenter: infillRowTitle.verticalCenter
+ }
+
+ Slider
+ {
+ id: infillSlider
+
+ width: parent.width
+ height: UM.Theme.getSize("print_setup_slider_handle").height // The handle is the widest element of the slider
+
+ minimumValue: 0
+ maximumValue: 100
+ stepSize: 1
+ tickmarksEnabled: true
+
+ // disable slider when gradual support is enabled
+ enabled: parseInt(infillSteps.properties.value) == 0
+
+ // set initial value from stack
+ value: parseInt(infillDensity.properties.value)
+
+ style: SliderStyle
+ {
+ //Draw line
+ groove: Item
+ {
+ Rectangle
+ {
+ height: UM.Theme.getSize("print_setup_slider_groove").height
+ width: control.width - UM.Theme.getSize("print_setup_slider_handle").width
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ color: control.enabled ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
+ }
+ }
+
+ handle: Rectangle
+ {
+ id: handleButton
+ color: control.enabled ? UM.Theme.getColor("primary") : UM.Theme.getColor("quality_slider_unavailable")
+ implicitWidth: UM.Theme.getSize("print_setup_slider_handle").width
+ implicitHeight: implicitWidth
+ radius: Math.round(implicitWidth / 2)
+ }
+
+ tickmarks: Repeater
+ {
+ id: repeater
+ model: control.maximumValue / control.stepSize + 1
+
+ Rectangle
+ {
+ color: control.enabled ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
+ implicitWidth: UM.Theme.getSize("print_setup_slider_tickmarks").width
+ implicitHeight: UM.Theme.getSize("print_setup_slider_tickmarks").height
+ anchors.verticalCenter: parent.verticalCenter
+
+ // Do not use Math.round otherwise the tickmarks won't be aligned
+ x: ((styleData.handleWidth / 2) - (implicitWidth / 2) + (index * ((repeater.width - styleData.handleWidth) / (repeater.count-1))))
+ radius: Math.round(implicitWidth / 2)
+ visible: (index % 10) == 0 // Only show steps of 10%
+
+ Label
+ {
+ text: index
+ font: UM.Theme.getFont("default")
+ visible: (index % 20) == 0 // Only show steps of 20%
+ anchors.horizontalCenter: parent.horizontalCenter
+ y: UM.Theme.getSize("thin_margin").height
+ renderType: Text.NativeRendering
+ color: UM.Theme.getColor("quality_slider_available")
+ }
+ }
+ }
+ }
+
+ onValueChanged:
+ {
+ // Don't round the value if it's already the same
+ if (parseInt(infillDensity.properties.value) == infillSlider.value)
+ {
+ return
+ }
+
+ // Round the slider value to the nearest multiple of 10 (simulate step size of 10)
+ var roundedSliderValue = Math.round(infillSlider.value / 10) * 10
+
+ // Update the slider value to represent the rounded value
+ infillSlider.value = roundedSliderValue
+
+ // Update value only if the Recomended mode is Active,
+ // Otherwise if I change the value in the Custom mode the Recomended view will try to repeat
+ // same operation
+ var active_mode = UM.Preferences.getValue("cura/active_mode")
+
+ if (active_mode == 0 || active_mode == "simple")
+ {
+ Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue)
+ Cura.MachineManager.resetSettingForAllExtruders("infill_line_distance")
+ }
+ }
+ }
+ }
+
+ // Gradual Support Infill Checkbox
+ CheckBox
+ {
+ id: enableGradualInfillCheckBox
+ property alias _hovered: enableGradualInfillMouseArea.containsMouse
+
+ anchors.top: infillSliderContainer.bottom
+ anchors.topMargin: UM.Theme.getSize("wide_margin").height
+ anchors.left: infillSliderContainer.left
+
+ text: catalog.i18nc("@label", "Gradual infill")
+ style: UM.Theme.styles.checkbox
+ enabled: recommendedPrintSetup.settingsEnabled
+ visible: infillSteps.properties.enabled == "True"
+ checked: parseInt(infillSteps.properties.value) > 0
+
+ MouseArea
+ {
+ id: enableGradualInfillMouseArea
+
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: true
+
+ property var previousInfillDensity: parseInt(infillDensity.properties.value)
+
+ onClicked:
+ {
+ // Set to 90% only when enabling gradual infill
+ var newInfillDensity;
+ if (parseInt(infillSteps.properties.value) == 0)
+ {
+ previousInfillDensity = parseInt(infillDensity.properties.value)
+ newInfillDensity = 90
+ } else {
+ newInfillDensity = previousInfillDensity
+ }
+ Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", String(newInfillDensity))
+
+ var infill_steps_value = 0
+ if (parseInt(infillSteps.properties.value) == 0)
+ {
+ infill_steps_value = 5
+ }
+
+ Cura.MachineManager.setSettingForAllExtruders("gradual_infill_steps", "value", infill_steps_value)
+ }
+
+ onEntered: base.showTooltip(enableGradualInfillCheckBox, Qt.point(-infillSliderContainer.x - UM.Theme.getSize("thick_margin").width, 0),
+ catalog.i18nc("@label", "Gradual infill will gradually increase the amount of infill towards the top."))
+
+ onExited: base.hideTooltip()
+ }
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: infillDensity
+ containerStackId: Cura.MachineManager.activeStackId
+ key: "infill_sparse_density"
+ watchedProperties: [ "value" ]
+ storeIndex: 0
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: infillSteps
+ containerStackId: Cura.MachineManager.activeStackId
+ key: "gradual_infill_steps"
+ watchedProperties: ["value", "enabled"]
+ storeIndex: 0
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml
new file mode 100644
index 0000000000..44b3abf7cd
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml
@@ -0,0 +1,86 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Item
+{
+ id: recommendedPrintSetup
+
+ height: childrenRect.height + 2 * padding
+
+ property Action configureSettings
+
+ property bool settingsEnabled: Cura.ExtruderManager.activeExtruderStackId || extrudersEnabledCount.properties.value == 1
+ property real padding: UM.Theme.getSize("thick_margin").width
+
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
+
+ Column
+ {
+ width: parent.width - 2 * parent.padding
+ spacing: UM.Theme.getSize("wide_margin").height
+
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ margins: parent.padding
+ }
+
+ // TODO
+ property real firstColumnWidth: Math.round(width / 3)
+
+ RecommendedQualityProfileSelector
+ {
+ width: parent.width
+ // TODO Create a reusable component with these properties to not define them separately for each component
+ labelColumnWidth: parent.firstColumnWidth
+ }
+
+ RecommendedInfillDensitySelector
+ {
+ width: parent.width
+ // TODO Create a reusable component with these properties to not define them separately for each component
+ labelColumnWidth: parent.firstColumnWidth
+ }
+
+ RecommendedSupportSelector
+ {
+ width: parent.width
+ // TODO Create a reusable component with these properties to not define them separately for each component
+ labelColumnWidth: parent.firstColumnWidth
+ }
+
+ RecommendedAdhesionSelector
+ {
+ width: parent.width
+ // TODO Create a reusable component with these properties to not define them separately for each component
+ labelColumnWidth: parent.firstColumnWidth
+ }
+
+ RecommendedTroubleshootingGuides
+ {
+ width: parent.width
+ }
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: extrudersEnabledCount
+ containerStack: Cura.MachineManager.activeMachine
+ key: "extruders_enabled_count"
+ watchedProperties: [ "value" ]
+ storeIndex: 0
+ }
+}
diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml
new file mode 100644
index 0000000000..801e76382b
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml
@@ -0,0 +1,455 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+
+//
+// Quality profile
+//
+Item
+{
+ id: qualityRow
+ height: childrenRect.height
+
+ property real labelColumnWidth: Math.round(width / 3)
+ property real settingsColumnWidth: width - labelColumnWidth
+
+ Timer
+ {
+ id: qualitySliderChangeTimer
+ interval: 50
+ running: false
+ repeat: false
+ onTriggered:
+ {
+ var item = Cura.QualityProfilesDropDownMenuModel.getItem(qualitySlider.value);
+ Cura.MachineManager.activeQualityGroup = item.quality_group;
+ }
+ }
+
+ Component.onCompleted: qualityModel.update()
+
+ Connections
+ {
+ target: Cura.QualityProfilesDropDownMenuModel
+ onItemsChanged: qualityModel.update()
+ }
+
+ Connections {
+ target: base
+ onVisibleChanged:
+ {
+ // update needs to be called when the widgets are visible, otherwise the step width calculation
+ // will fail because the width of an invisible item is 0.
+ if (visible)
+ {
+ qualityModel.update();
+ }
+ }
+ }
+
+ ListModel
+ {
+ id: qualityModel
+
+ property var totalTicks: 0
+ property var availableTotalTicks: 0
+ property var existingQualityProfile: 0
+
+ property var qualitySliderActiveIndex: 0
+ property var qualitySliderStepWidth: 0
+ property var qualitySliderAvailableMin: 0
+ property var qualitySliderAvailableMax: 0
+ property var qualitySliderMarginRight: 0
+
+ function update ()
+ {
+ reset()
+
+ var availableMin = -1
+ var availableMax = -1
+
+ for (var i = 0; i < Cura.QualityProfilesDropDownMenuModel.rowCount(); i++)
+ {
+ var qualityItem = Cura.QualityProfilesDropDownMenuModel.getItem(i)
+
+ // Add each quality item to the UI quality model
+ qualityModel.append(qualityItem)
+
+ // Set selected value
+ if (Cura.MachineManager.activeQualityType == qualityItem.quality_type)
+ {
+ // set to -1 when switching to user created profile so all ticks are clickable
+ if (Cura.MachineManager.hasCustomQuality)
+ {
+ qualityModel.qualitySliderActiveIndex = -1
+ }
+ else
+ {
+ qualityModel.qualitySliderActiveIndex = i
+ }
+
+ qualityModel.existingQualityProfile = 1
+ }
+
+ // Set min available
+ if (qualityItem.available && availableMin == -1)
+ {
+ availableMin = i
+ }
+
+ // Set max available
+ if (qualityItem.available)
+ {
+ availableMax = i
+ }
+ }
+
+ // Set total available ticks for active slider part
+ if (availableMin != -1)
+ {
+ qualityModel.availableTotalTicks = availableMax - availableMin + 1
+ }
+
+ // Calculate slider values
+ calculateSliderStepWidth(qualityModel.totalTicks)
+ calculateSliderMargins(availableMin, availableMax, qualityModel.totalTicks)
+
+ qualityModel.qualitySliderAvailableMin = availableMin
+ qualityModel.qualitySliderAvailableMax = availableMax
+ }
+
+ function calculateSliderStepWidth (totalTicks)
+ {
+ // Do not use Math.round otherwise the tickmarks won't be aligned
+ qualityModel.qualitySliderStepWidth = totalTicks != 0 ?
+ ((settingsColumnWidth - UM.Theme.getSize("print_setup_slider_handle").width) / (totalTicks)) : 0
+ }
+
+ function calculateSliderMargins (availableMin, availableMax, totalTicks)
+ {
+ if (availableMin == -1 || (availableMin == 0 && availableMax == 0))
+ {
+ // Do not use Math.round otherwise the tickmarks won't be aligned
+ qualityModel.qualitySliderMarginRight = settingsColumnWidth
+ }
+ else if (availableMin == availableMax)
+ {
+ // Do not use Math.round otherwise the tickmarks won't be aligned
+ qualityModel.qualitySliderMarginRight = (totalTicks - availableMin) * qualitySliderStepWidth
+ }
+ else
+ {
+ // Do not use Math.round otherwise the tickmarks won't be aligned
+ qualityModel.qualitySliderMarginRight = (totalTicks - availableMax) * qualitySliderStepWidth
+ }
+ }
+
+ function reset () {
+ qualityModel.clear()
+ qualityModel.availableTotalTicks = 0
+ qualityModel.existingQualityProfile = 0
+
+ // check, the ticks count cannot be less than zero
+ qualityModel.totalTicks = Math.max(0, Cura.QualityProfilesDropDownMenuModel.rowCount() - 1)
+ }
+ }
+
+ // Here are the elements that are shown in the left column
+ Item
+ {
+ id: titleRow
+ width: labelColumnWidth
+ height: childrenRect.height
+
+ Cura.IconWithText
+ {
+ id: qualityRowTitle
+ source: UM.Theme.getIcon("category_layer_height")
+ text: catalog.i18nc("@label", "Layer Height")
+ font: UM.Theme.getFont("medium")
+ anchors.left: parent.left
+ anchors.right: customisedSettings.left
+ }
+
+ UM.SimpleButton
+ {
+ id: customisedSettings
+
+ visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.MachineManager.hasCustomQuality
+ height: visible ? UM.Theme.getSize("print_setup_icon").height : 0
+ width: height
+ anchors
+ {
+ right: parent.right
+ rightMargin: UM.Theme.getSize("default_margin").width
+ leftMargin: UM.Theme.getSize("default_margin").width
+ verticalCenter: parent.verticalCenter
+ }
+
+ color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
+ iconSource: UM.Theme.getIcon("reset")
+
+ onClicked:
+ {
+ // if the current profile is user-created, switch to a built-in quality
+ Cura.MachineManager.resetToUseDefaultQuality()
+ }
+ onEntered:
+ {
+ var tooltipContent = catalog.i18nc("@tooltip","You have modified some profile settings. If you want to change these go to custom mode.")
+ base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, 0), tooltipContent)
+ }
+ onExited: base.hideTooltip()
+ }
+ }
+
+ // Show titles for the each quality slider ticks
+ Item
+ {
+ anchors.left: speedSlider.left
+ anchors.top: speedSlider.bottom
+ height: childrenRect.height
+
+ Repeater
+ {
+ model: qualityModel
+
+ Label
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.top: parent.top
+ // The height has to be set manually, otherwise it's not automatically calculated in the repeater
+ height: UM.Theme.getSize("default_margin").height
+ color: (Cura.MachineManager.activeMachine != null && Cura.QualityProfilesDropDownMenuModel.getItem(index).available) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
+ text:
+ {
+ var result = ""
+ if(Cura.MachineManager.activeMachine != null)
+ {
+ result = Cura.QualityProfilesDropDownMenuModel.getItem(index).layer_height
+
+ if(result == undefined)
+ {
+ result = "";
+ }
+ else
+ {
+ result = Number(Math.round(result + "e+2") + "e-2"); //Round to 2 decimals. Javascript makes this difficult...
+ if (result == undefined || result != result) //Parse failure.
+ {
+ result = "";
+ }
+ }
+ }
+ return result
+ }
+
+ x:
+ {
+ // Make sure the text aligns correctly with each tick
+ if (qualityModel.totalTicks == 0)
+ {
+ // If there is only one tick, align it centrally
+ return Math.round(((settingsColumnWidth) - width) / 2)
+ }
+ else if (index == 0)
+ {
+ return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index
+ }
+ else if (index == qualityModel.totalTicks)
+ {
+ return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index - width
+ }
+ else
+ {
+ return Math.round((settingsColumnWidth / qualityModel.totalTicks) * index - (width / 2))
+ }
+ }
+ font: UM.Theme.getFont("default")
+ }
+ }
+ }
+
+ // Print speed slider
+ // Two sliders are created, one at the bottom with the unavailable qualities
+ // and the other at the top with the available quality profiles and so the handle to select them.
+ Item
+ {
+ id: speedSlider
+ height: childrenRect.height
+
+ anchors
+ {
+ left: titleRow.right
+ right: parent.right
+ verticalCenter: titleRow.verticalCenter
+ }
+
+ // Draw unavailable slider
+ Slider
+ {
+ id: unavailableSlider
+
+ width: parent.width
+ height: qualitySlider.height // Same height as the slider that is on top
+ updateValueWhileDragging : false
+ tickmarksEnabled: true
+
+ minimumValue: 0
+ // maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly
+ // speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available)
+ maximumValue: qualityModel.totalTicks
+ stepSize: 1
+
+ style: SliderStyle
+ {
+ //Draw Unvailable line
+ groove: Item
+ {
+ Rectangle
+ {
+ height: UM.Theme.getSize("print_setup_slider_groove").height
+ width: control.width - UM.Theme.getSize("print_setup_slider_handle").width
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ color: UM.Theme.getColor("quality_slider_unavailable")
+ }
+ }
+
+ handle: Item {}
+
+ tickmarks: Repeater
+ {
+ id: qualityRepeater
+ model: qualityModel.totalTicks > 0 ? qualityModel : 0
+
+ Rectangle
+ {
+ color: Cura.QualityProfilesDropDownMenuModel.getItem(index).available ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
+ implicitWidth: UM.Theme.getSize("print_setup_slider_tickmarks").width
+ implicitHeight: UM.Theme.getSize("print_setup_slider_tickmarks").height
+ anchors.verticalCenter: parent.verticalCenter
+
+ // Do not use Math.round otherwise the tickmarks won't be aligned
+ x: ((UM.Theme.getSize("print_setup_slider_handle").width / 2) - (implicitWidth / 2) + (qualityModel.qualitySliderStepWidth * index))
+ radius: Math.round(implicitWidth / 2)
+ }
+ }
+ }
+
+ // Create a mouse area on top of the unavailable profiles to show a specific tooltip
+ MouseArea
+ {
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: !Cura.MachineManager.hasCustomQuality
+ onEntered:
+ {
+ var tooltipContent = catalog.i18nc("@tooltip", "This quality profile is not available for your current material and nozzle configuration. Please change these to enable this quality profile")
+ base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent)
+ }
+ onExited: base.hideTooltip()
+ }
+ }
+
+ // Draw available slider
+ Slider
+ {
+ id: qualitySlider
+
+ width: qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks - 1) + UM.Theme.getSize("print_setup_slider_handle").width
+ height: UM.Theme.getSize("print_setup_slider_handle").height // The handle is the widest element of the slider
+ enabled: qualityModel.totalTicks > 0 && !Cura.SimpleModeSettingsManager.isProfileCustomized
+ visible: qualityModel.availableTotalTicks > 0
+ updateValueWhileDragging : false
+
+ anchors
+ {
+ right: parent.right
+ rightMargin: qualityModel.qualitySliderMarginRight
+ }
+
+ minimumValue: qualityModel.qualitySliderAvailableMin >= 0 ? qualityModel.qualitySliderAvailableMin : 0
+ // maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly
+ // speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available)
+ maximumValue: qualityModel.qualitySliderAvailableMax >= 1 ? qualityModel.qualitySliderAvailableMax : 1
+ stepSize: 1
+
+ value: qualityModel.qualitySliderActiveIndex
+
+ style: SliderStyle
+ {
+ // Draw Available line
+ groove: Item
+ {
+ Rectangle
+ {
+ height: UM.Theme.getSize("print_setup_slider_groove").height
+ width: control.width - UM.Theme.getSize("print_setup_slider_handle").width
+ anchors.verticalCenter: parent.verticalCenter
+
+ // Do not use Math.round otherwise the tickmarks won't be aligned
+ x: UM.Theme.getSize("print_setup_slider_handle").width / 2
+ color: UM.Theme.getColor("quality_slider_available")
+ }
+ }
+
+ handle: Rectangle
+ {
+ id: qualityhandleButton
+ color: UM.Theme.getColor("primary")
+ implicitWidth: UM.Theme.getSize("print_setup_slider_handle").width
+ implicitHeight: implicitWidth
+ radius: Math.round(implicitWidth / 2)
+ visible: !Cura.SimpleModeSettingsManager.isProfileCustomized && !Cura.MachineManager.hasCustomQuality && qualityModel.existingQualityProfile
+ }
+ }
+
+ onValueChanged:
+ {
+ // only change if an active machine is set and the slider is visible at all.
+ if (Cura.MachineManager.activeMachine != null && visible)
+ {
+ // prevent updating during view initializing. Trigger only if the value changed by user
+ if (qualitySlider.value != qualityModel.qualitySliderActiveIndex && qualityModel.qualitySliderActiveIndex != -1)
+ {
+ // start updating with short delay
+ qualitySliderChangeTimer.start()
+ }
+ }
+ }
+
+ // This mouse area is only used to capture the onHover state and don't propagate it to the unavailable mouse area
+ MouseArea
+ {
+ anchors.fill: parent
+ hoverEnabled: true
+ acceptedButtons: Qt.NoButton
+ enabled: !Cura.MachineManager.hasCustomQuality
+ }
+ }
+
+ // This mouse area will only take the mouse events and show a tooltip when the profile in use is
+ // a user created profile
+ MouseArea
+ {
+ anchors.fill: parent
+ hoverEnabled: true
+ visible: Cura.MachineManager.hasCustomQuality
+
+ onEntered:
+ {
+ var tooltipContent = catalog.i18nc("@tooltip", "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab")
+ base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent)
+ }
+ onExited: base.hideTooltip()
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml
new file mode 100644
index 0000000000..0e834ac4df
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedSupportSelector.qml
@@ -0,0 +1,206 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+
+//
+// Enable support
+//
+Item
+{
+ id: enableSupportRow
+ height: childrenRect.height
+
+ property real labelColumnWidth: Math.round(width / 3)
+
+ Cura.IconWithText
+ {
+ id: enableSupportRowTitle
+ anchors.top: parent.top
+ anchors.left: parent.left
+ visible: enableSupportCheckBox.visible
+ source: UM.Theme.getIcon("category_support")
+ text: catalog.i18nc("@label", "Support")
+ font: UM.Theme.getFont("medium")
+ width: labelColumnWidth
+ }
+
+ Item
+ {
+ id: enableSupportContainer
+ height: enableSupportCheckBox.height
+
+ anchors
+ {
+ left: enableSupportRowTitle.right
+ right: parent.right
+ verticalCenter: enableSupportRowTitle.verticalCenter
+ }
+
+ CheckBox
+ {
+ id: enableSupportCheckBox
+ anchors.verticalCenter: parent.verticalCenter
+
+ property alias _hovered: enableSupportMouseArea.containsMouse
+
+ style: UM.Theme.styles.checkbox
+ enabled: recommendedPrintSetup.settingsEnabled
+
+ visible: supportEnabled.properties.enabled == "True"
+ checked: supportEnabled.properties.value == "True"
+
+ MouseArea
+ {
+ id: enableSupportMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+
+ onClicked: supportEnabled.setPropertyValue("value", supportEnabled.properties.value != "True")
+
+ onEntered:
+ {
+ base.showTooltip(enableSupportCheckBox, Qt.point(-enableSupportContainer.x - UM.Theme.getSize("thick_margin").width, 0),
+ catalog.i18nc("@label", "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."))
+ }
+ onExited: base.hideTooltip()
+ }
+ }
+
+ ComboBox
+ {
+ id: supportExtruderCombobox
+
+ height: UM.Theme.getSize("print_setup_big_item").height
+ anchors
+ {
+ left: enableSupportCheckBox.right
+ right: parent.right
+ leftMargin: UM.Theme.getSize("thick_margin").width
+ rightMargin: UM.Theme.getSize("thick_margin").width
+ verticalCenter: parent.verticalCenter
+ }
+
+ style: UM.Theme.styles.combobox_color
+ enabled: recommendedPrintSetup.settingsEnabled
+ visible: enableSupportCheckBox.visible && (supportEnabled.properties.value == "True") && (extrudersEnabledCount.properties.value > 1)
+ textRole: "text" // this solves that the combobox isn't populated in the first time Cura is started
+
+ model: extruderModel
+
+ property alias _hovered: supportExtruderMouseArea.containsMouse
+ property string color_override: "" // for manually setting values
+ property string color: // is evaluated automatically, but the first time is before extruderModel being filled
+ {
+ var current_extruder = extruderModel.get(currentIndex)
+ color_override = ""
+ if (current_extruder === undefined) return ""
+ return (current_extruder.color) ? current_extruder.color : ""
+ }
+
+ currentIndex:
+ {
+ if (supportExtruderNr.properties == null)
+ {
+ return Cura.MachineManager.defaultExtruderPosition
+ }
+ else
+ {
+ var extruder = parseInt(supportExtruderNr.properties.value)
+ if ( extruder === -1)
+ {
+ return Cura.MachineManager.defaultExtruderPosition
+ }
+ return extruder;
+ }
+ }
+
+ onActivated: supportExtruderNr.setPropertyValue("value", String(index))
+
+ MouseArea
+ {
+ id: supportExtruderMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: recommendedPrintSetup.settingsEnabled
+ acceptedButtons: Qt.NoButton
+ onEntered:
+ {
+ base.showTooltip(supportExtruderCombobox, Qt.point(-enableSupportContainer.x - supportExtruderCombobox.x - UM.Theme.getSize("thick_margin").width, 0),
+ catalog.i18nc("@label", "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."));
+ }
+ onExited: base.hideTooltip()
+
+ }
+
+ function updateCurrentColor()
+ {
+ var current_extruder = extruderModel.get(currentIndex)
+ if (current_extruder !== undefined)
+ {
+ supportExtruderCombobox.color_override = current_extruder.color
+ }
+ }
+ }
+ }
+
+ ListModel
+ {
+ id: extruderModel
+ Component.onCompleted: populateExtruderModel()
+ }
+
+ //: Model used to populate the extrudelModel
+ property var extruders: CuraApplication.getExtrudersModel()
+ Connections
+ {
+ target: extruders
+ onModelChanged: populateExtruderModel()
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: supportEnabled
+ containerStack: Cura.MachineManager.activeMachine
+ key: "support_enable"
+ watchedProperties: [ "value", "enabled", "description" ]
+ storeIndex: 0
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: supportExtruderNr
+ containerStack: Cura.MachineManager.activeMachine
+ key: "support_extruder_nr"
+ watchedProperties: [ "value" ]
+ storeIndex: 0
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: machineExtruderCount
+ containerStack: Cura.MachineManager.activeMachine
+ key: "machine_extruder_count"
+ watchedProperties: ["value"]
+ storeIndex: 0
+ }
+
+ function populateExtruderModel()
+ {
+ extruderModel.clear()
+ for (var extruderNumber = 0; extruderNumber < extruders.rowCount(); extruderNumber++)
+ {
+ extruderModel.append({
+ text: extruders.getItem(extruderNumber).name,
+ color: extruders.getItem(extruderNumber).color
+ })
+ }
+ supportExtruderCombobox.updateCurrentColor()
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedTroubleshootingGuides.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedTroubleshootingGuides.qml
new file mode 100644
index 0000000000..e6706421bb
--- /dev/null
+++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedTroubleshootingGuides.qml
@@ -0,0 +1,36 @@
+// Copyright (c) 2019 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.10
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+
+Item
+{
+ id: tipsCell
+ anchors.top: adhesionCheckBox.visible ? adhesionCheckBox.bottom : (enableSupportCheckBox.visible ? supportExtruderCombobox.bottom : infillCellRight.bottom)
+ anchors.topMargin: Math.round(UM.Theme.getSize("default_margin").height * 2)
+ anchors.left: parent.left
+ width: parent.width
+ height: tipsText.contentHeight * tipsText.lineCount
+
+ Label
+ {
+ id: tipsText
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.right: parent.right
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width
+ anchors.top: parent.top
+ wrapMode: Text.WordWrap
+ text: catalog.i18nc("@label", "Need help improving your prints? Read the Ultimaker Troubleshooting Guides").arg("https://ultimaker.com/en/troubleshooting")
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ linkColor: UM.Theme.getColor("text_link")
+ onLinkActivated: Qt.openUrlExternally(link)
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/SidebarTooltip.qml b/resources/qml/PrintSetupTooltip.qml
similarity index 87%
rename from resources/qml/SidebarTooltip.qml
rename to resources/qml/PrintSetupTooltip.qml
index 29199481f6..6b1538d849 100644
--- a/resources/qml/SidebarTooltip.qml
+++ b/resources/qml/PrintSetupTooltip.qml
@@ -2,9 +2,7 @@
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
-import QtQuick.Controls 1.1
-import QtQuick.Controls.Styles 1.1
-import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.3
import UM 1.0 as UM
@@ -36,14 +34,15 @@ UM.PointingRectangle {
}
}
base.opacity = 1;
- target = Qt.point(40 , position.y + Math.round(UM.Theme.getSize("tooltip_arrow_margins").height / 2))
+ target = Qt.point(position.x + 1, position.y + Math.round(UM.Theme.getSize("tooltip_arrow_margins").height / 2))
}
function hide() {
base.opacity = 0;
}
- Label {
+ Label
+ {
id: label;
anchors {
top: parent.top;
@@ -57,5 +56,6 @@ UM.PointingRectangle {
textFormat: Text.RichText
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("tooltip_text");
+ renderType: Text.NativeRendering
}
}
diff --git a/resources/qml/PrinterOutput/ExtruderBox.qml b/resources/qml/PrinterOutput/ExtruderBox.qml
index f0abd4cd6c..a19c02b0dd 100644
--- a/resources/qml/PrinterOutput/ExtruderBox.qml
+++ b/resources/qml/PrinterOutput/ExtruderBox.qml
@@ -12,8 +12,10 @@ Item
property alias color: background.color
property var extruderModel
property var position: index
+ property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
+
implicitWidth: parent.width
- implicitHeight: UM.Theme.getSize("sidebar_extruder_box").height
+ implicitHeight: UM.Theme.getSize("print_setup_extruder_box").height
UM.SettingPropertyProvider
{
@@ -45,7 +47,7 @@ Item
{
id: extruderTargetTemperature
text: Math.round(extruderModel.targetHotendTemperature) + "°C"
- font: UM.Theme.getFont("small")
+ font: UM.Theme.getFont("default_bold")
color: UM.Theme.getColor("text_inactive")
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
@@ -78,7 +80,7 @@ Item
id: extruderCurrentTemperature
text: Math.round(extruderModel.hotendTemperature) + "°C"
color: UM.Theme.getColor("text")
- font: UM.Theme.getFont("large")
+ font: UM.Theme.getFont("large_bold")
anchors.right: extruderTargetTemperature.left
anchors.top: parent.top
anchors.margins: UM.Theme.getSize("default_margin").width
@@ -324,7 +326,7 @@ Item
return UM.Theme.getColor("action_button_text");
}
}
- font: UM.Theme.getFont("action_button")
+ font: UM.Theme.getFont("medium")
text:
{
if(extruderModel == null)
diff --git a/resources/qml/PrinterOutput/HeatedBedBox.qml b/resources/qml/PrinterOutput/HeatedBedBox.qml
index 9de66ad0be..77421c8aad 100644
--- a/resources/qml/PrinterOutput/HeatedBedBox.qml
+++ b/resources/qml/PrinterOutput/HeatedBedBox.qml
@@ -1,10 +1,10 @@
// Copyright (c) 2017 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.Controls.Styles 1.1
-import QtQuick.Layouts 1.1
+import QtQuick 2.10
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Layouts 1.3
import UM 1.2 as UM
import Cura 1.0 as Cura
@@ -12,12 +12,13 @@ import Cura 1.0 as Cura
Item
{
implicitWidth: parent.width
- height: visible ? UM.Theme.getSize("sidebar_extruder_box").height : 0
+ height: visible ? UM.Theme.getSize("print_setup_extruder_box").height : 0
property var printerModel
+ property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
Rectangle
{
- color: UM.Theme.getColor("sidebar")
+ color: UM.Theme.getColor("main_background")
anchors.fill: parent
Label //Build plate label.
@@ -34,7 +35,7 @@ Item
{
id: bedTargetTemperature
text: printerModel != null ? printerModel.targetBedTemperature + "°C" : ""
- font: UM.Theme.getFont("small")
+ font: UM.Theme.getFont("default_bold")
color: UM.Theme.getColor("text_inactive")
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
@@ -66,7 +67,7 @@ Item
{
id: bedCurrentTemperature
text: printerModel != null ? printerModel.bedTemperature + "°C" : ""
- font: UM.Theme.getFont("large")
+ font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text")
anchors.right: bedTargetTemperature.left
anchors.top: parent.top
@@ -114,7 +115,7 @@ Item
{
return false; //Can't preheat if not connected.
}
- if (!connectedPrinter.acceptsCommands)
+ if (connectedPrinter == null || !connectedPrinter.acceptsCommands)
{
return false; //Not allowed to do anything.
}
@@ -319,7 +320,7 @@ Item
return UM.Theme.getColor("action_button_text");
}
}
- font: UM.Theme.getFont("action_button")
+ font: UM.Theme.getFont("medium")
text:
{
if(printerModel == null)
diff --git a/resources/qml/PrinterOutput/ManualPrinterControl.qml b/resources/qml/PrinterOutput/ManualPrinterControl.qml
index 70961a2eb2..106ae7db03 100644
--- a/resources/qml/PrinterOutput/ManualPrinterControl.qml
+++ b/resources/qml/PrinterOutput/ManualPrinterControl.qml
@@ -1,103 +1,26 @@
// Copyright (c) 2017 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.Controls.Styles 1.1
-import QtQuick.Layouts 1.1
+import QtQuick 2.10
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Layouts 1.3
-import UM 1.2 as UM
+import UM 1.3 as UM
import Cura 1.0 as Cura
+import "."
+
+
Item
{
- property var printerModel
+ property var printerModel: null
property var activePrintJob: printerModel != null ? printerModel.activePrintJob : null
+ property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
+
implicitWidth: parent.width
implicitHeight: childrenRect.height
- Component
- {
- id: monitorButtonStyle
-
- 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("action_button_active_border");
- }
- else if(control.hovered)
- {
- return UM.Theme.getColor("action_button_hovered_border");
- }
- return UM.Theme.getColor("action_button_border");
- }
- color:
- {
- if(!control.enabled)
- {
- return UM.Theme.getColor("action_button_disabled");
- }
- else if(control.pressed)
- {
- return UM.Theme.getColor("action_button_active");
- }
- else if(control.hovered)
- {
- return UM.Theme.getColor("action_button_hovered");
- }
- return UM.Theme.getColor("action_button");
- }
- Behavior on color
- {
- ColorAnimation
- {
- duration: 50
- }
- }
- }
-
- label: Item
- {
- UM.RecolorImage
- {
- anchors.verticalCenter: parent.verticalCenter
- anchors.horizontalCenter: parent.horizontalCenter
- width: Math.floor(control.width / 2)
- height: Math.floor(control.height / 2)
- sourceSize.width: width
- sourceSize.height: width
- color:
- {
- if(!control.enabled)
- {
- return UM.Theme.getColor("action_button_disabled_text");
- }
- else if(control.pressed)
- {
- return UM.Theme.getColor("action_button_active_text");
- }
- else if(control.hovered)
- {
- return UM.Theme.getColor("action_button_hovered_text");
- }
- return UM.Theme.getColor("action_button_text");
- }
- source: control.iconSource
- }
- }
- }
- }
-
Column
{
enabled:
@@ -180,7 +103,7 @@ Item
Layout.preferredWidth: width
Layout.preferredHeight: height
iconSource: UM.Theme.getIcon("arrow_top");
- style: monitorButtonStyle
+ style: UM.Theme.styles.monitor_button_style
width: height
height: UM.Theme.getSize("setting_control").height
@@ -197,7 +120,7 @@ Item
Layout.preferredWidth: width
Layout.preferredHeight: height
iconSource: UM.Theme.getIcon("arrow_left");
- style: monitorButtonStyle
+ style: UM.Theme.styles.monitor_button_style
width: height
height: UM.Theme.getSize("setting_control").height
@@ -214,7 +137,7 @@ Item
Layout.preferredWidth: width
Layout.preferredHeight: height
iconSource: UM.Theme.getIcon("arrow_right");
- style: monitorButtonStyle
+ style: UM.Theme.styles.monitor_button_style
width: height
height: UM.Theme.getSize("setting_control").height
@@ -231,7 +154,7 @@ Item
Layout.preferredWidth: width
Layout.preferredHeight: height
iconSource: UM.Theme.getIcon("arrow_bottom");
- style: monitorButtonStyle
+ style: UM.Theme.styles.monitor_button_style
width: height
height: UM.Theme.getSize("setting_control").height
@@ -248,7 +171,7 @@ Item
Layout.preferredWidth: width
Layout.preferredHeight: height
iconSource: UM.Theme.getIcon("home");
- style: monitorButtonStyle
+ style: UM.Theme.styles.monitor_button_style
width: height
height: UM.Theme.getSize("setting_control").height
@@ -278,7 +201,7 @@ Item
Button
{
iconSource: UM.Theme.getIcon("arrow_top");
- style: monitorButtonStyle
+ style: UM.Theme.styles.monitor_button_style
width: height
height: UM.Theme.getSize("setting_control").height
@@ -291,7 +214,7 @@ Item
Button
{
iconSource: UM.Theme.getIcon("home");
- style: monitorButtonStyle
+ style: UM.Theme.styles.monitor_button_style
width: height
height: UM.Theme.getSize("setting_control").height
@@ -304,7 +227,7 @@ Item
Button
{
iconSource: UM.Theme.getIcon("arrow_bottom");
- style: monitorButtonStyle
+ style: UM.Theme.styles.monitor_button_style
width: height
height: UM.Theme.getSize("setting_control").height
@@ -356,72 +279,7 @@ Item
checked: distancesRow.currentDistance == model.value
onClicked: distancesRow.currentDistance = model.value
- style: ButtonStyle {
- background: Rectangle {
- border.width: control.checked ? UM.Theme.getSize("default_lining").width * 2 : UM.Theme.getSize("default_lining").width
- border.color:
- {
- if(!control.enabled)
- {
- return UM.Theme.getColor("action_button_disabled_border");
- }
- else if (control.checked || control.pressed)
- {
- return UM.Theme.getColor("action_button_active_border");
- }
- else if(control.hovered)
- {
- return UM.Theme.getColor("action_button_hovered_border");
- }
- return UM.Theme.getColor("action_button_border");
- }
- color:
- {
- if(!control.enabled)
- {
- return UM.Theme.getColor("action_button_disabled");
- }
- else if (control.checked || control.pressed)
- {
- return UM.Theme.getColor("action_button_active");
- }
- else if (control.hovered)
- {
- return UM.Theme.getColor("action_button_hovered");
- }
- return UM.Theme.getColor("action_button");
- }
- Behavior on color { ColorAnimation { duration: 50; } }
- Label {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- anchors.leftMargin: UM.Theme.getSize("default_lining").width * 2
- anchors.rightMargin: UM.Theme.getSize("default_lining").width * 2
- color:
- {
- if(!control.enabled)
- {
- return UM.Theme.getColor("action_button_disabled_text");
- }
- else if (control.checked || control.pressed)
- {
- return UM.Theme.getColor("action_button_active_text");
- }
- else if (control.hovered)
- {
- return UM.Theme.getColor("action_button_hovered_text");
- }
- return UM.Theme.getColor("action_button_text");
- }
- font: UM.Theme.getFont("default")
- text: control.text
- horizontalAlignment: Text.AlignHCenter
- elide: Text.ElideMiddle
- }
- }
- label: Item { }
- }
+ style: UM.Theme.styles.monitor_checkable_button_style
}
}
}
@@ -462,7 +320,7 @@ Item
if (printerModel == null) {
return false // Can't send custom commands if not connected.
}
- if (!connectedPrinter.acceptsCommands) {
+ if (connectedPrinter == null || !connectedPrinter.acceptsCommands) {
return false // Not allowed to do anything
}
if (connectedPrinter.jobState == "printing" || connectedPrinter.jobState == "pre_print" || connectedPrinter.jobState == "resuming" || connectedPrinter.jobState == "pausing" || connectedPrinter.jobState == "paused" || connectedPrinter.jobState == "error" || connectedPrinter.jobState == "offline") {
@@ -551,4 +409,4 @@ Item
}
ExclusiveGroup { id: distanceGroup }
}
-}
\ No newline at end of file
+}
diff --git a/resources/qml/PrinterOutput/MonitorItem.qml b/resources/qml/PrinterOutput/MonitorItem.qml
index cad8d2f7f3..a26ec20f64 100644
--- a/resources/qml/PrinterOutput/MonitorItem.qml
+++ b/resources/qml/PrinterOutput/MonitorItem.qml
@@ -15,6 +15,8 @@ Item
property string value: ""
height: childrenRect.height;
+ property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
+
Row
{
height: UM.Theme.getSize("setting_control").height
diff --git a/resources/qml/PrinterOutput/MonitorSection.qml b/resources/qml/PrinterOutput/MonitorSection.qml
index 6ed762362d..1d9df777b6 100644
--- a/resources/qml/PrinterOutput/MonitorSection.qml
+++ b/resources/qml/PrinterOutput/MonitorSection.qml
@@ -1,10 +1,10 @@
// Copyright (c) 2017 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.Controls.Styles 1.1
-import QtQuick.Layouts 1.1
+import QtQuick 2.10
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Layouts 1.3
import UM 1.2 as UM
import Cura 1.0 as Cura
@@ -13,7 +13,8 @@ Item
{
id: base
property string label
- height: childrenRect.height;
+ height: childrenRect.height
+
Rectangle
{
color: UM.Theme.getColor("setting_category")
@@ -26,8 +27,8 @@ Item
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
text: label
- font: UM.Theme.getFont("setting_category")
+ font: UM.Theme.getFont("default")
color: UM.Theme.getColor("setting_category_text")
}
}
-}
\ No newline at end of file
+}
diff --git a/resources/qml/PrinterOutput/OutputDeviceHeader.qml b/resources/qml/PrinterOutput/OutputDeviceHeader.qml
index b5ed1b7b4e..47f855266b 100644
--- a/resources/qml/PrinterOutput/OutputDeviceHeader.qml
+++ b/resources/qml/PrinterOutput/OutputDeviceHeader.qml
@@ -31,7 +31,7 @@ Item
Label
{
id: outputDeviceNameLabel
- font: UM.Theme.getFont("large")
+ font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text")
anchors.left: parent.left
anchors.top: parent.top
@@ -43,10 +43,10 @@ Item
{
id: outputDeviceAddressLabel
text: (outputDevice != null && outputDevice.address != null) ? outputDevice.address : ""
- font: UM.Theme.getFont("small")
+ font: UM.Theme.getFont("default_bold")
color: UM.Theme.getColor("text_inactive")
- anchors.top: parent.top
- anchors.right: parent.right
+ anchors.top: outputDeviceNameLabel.bottom
+ anchors.left: parent.left
anchors.margins: UM.Theme.getSize("default_margin").width
}
@@ -54,7 +54,7 @@ Item
{
text: outputDevice != null ? "" : catalog.i18nc("@info:status", "The printer is not connected.")
color: outputDevice != null && outputDevice.acceptsCommands ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text")
- font: UM.Theme.getFont("very_small")
+ font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
diff --git a/resources/qml/PrinterSelector/MachineSelector.qml b/resources/qml/PrinterSelector/MachineSelector.qml
new file mode 100644
index 0000000000..cd5e041606
--- /dev/null
+++ b/resources/qml/PrinterSelector/MachineSelector.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.7
+import QtQuick.Controls 2.3
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Cura.ExpandablePopup
+{
+ id: machineSelector
+
+ property bool isNetworkPrinter: Cura.MachineManager.activeMachineHasActiveNetworkConnection
+ property bool isCloudPrinter: Cura.MachineManager.activeMachineHasActiveCloudConnection
+ property bool isGroup: Cura.MachineManager.activeMachineIsGroup
+
+ contentPadding: UM.Theme.getSize("default_lining").width
+ contentAlignment: Cura.ExpandablePopup.ContentAlignment.AlignLeft
+
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
+
+ headerItem: Cura.IconWithText
+ {
+ text:
+ {
+ if (isNetworkPrinter && Cura.MachineManager.activeMachineNetworkGroupName != "")
+ {
+ return Cura.MachineManager.activeMachineNetworkGroupName
+ }
+ return Cura.MachineManager.activeMachineName
+ }
+ source:
+ {
+ if (isGroup)
+ {
+ return UM.Theme.getIcon("printer_group")
+ }
+ else if (isNetworkPrinter || isCloudPrinter)
+ {
+ return UM.Theme.getIcon("printer_single")
+ }
+ else
+ {
+ return ""
+ }
+ }
+ font: UM.Theme.getFont("medium")
+ iconColor: UM.Theme.getColor("machine_selector_printer_icon")
+ iconSize: source != "" ? UM.Theme.getSize("machine_selector_icon").width: 0
+
+ UM.RecolorImage
+ {
+ anchors
+ {
+ bottom: parent.bottom
+ left: parent.left
+ leftMargin: UM.Theme.getSize("thick_margin").width
+ }
+
+ source:
+ {
+ if (isNetworkPrinter)
+ {
+ return UM.Theme.getIcon("printer_connected")
+ }
+ else if (isCloudPrinter)
+ {
+ return UM.Theme.getIcon("printer_cloud_connected")
+ }
+ else
+ {
+ return ""
+ }
+ }
+
+ width: UM.Theme.getSize("printer_status_icon").width
+ height: UM.Theme.getSize("printer_status_icon").height
+
+ color: UM.Theme.getColor("primary")
+ visible: isNetworkPrinter || isCloudPrinter
+
+ // Make a themable circle in the background so we can change it in other themes
+ Rectangle
+ {
+ id: iconBackground
+ anchors.centerIn: parent
+ // Make it a bit bigger so there is an outline
+ width: parent.width + 2 * UM.Theme.getSize("default_lining").width
+ height: parent.height + 2 * UM.Theme.getSize("default_lining").height
+ radius: Math.round(width / 2)
+ color: UM.Theme.getColor("main_background")
+ z: parent.z - 1
+ }
+ }
+ }
+
+ contentItem: Item
+ {
+ id: popup
+ width: UM.Theme.getSize("machine_selector_widget_content").width
+
+ ScrollView
+ {
+ id: scroll
+ width: parent.width
+ clip: true
+ leftPadding: UM.Theme.getSize("default_lining").width
+ rightPadding: UM.Theme.getSize("default_lining").width
+
+ MachineSelectorList
+ {
+ // Can't use parent.width since the parent is the flickable component and not the ScrollView
+ width: scroll.width - scroll.leftPadding - scroll.rightPadding
+ property real maximumHeight: UM.Theme.getSize("machine_selector_widget_content").height - buttonRow.height
+
+ // We use an extra property here, since we only want to to be informed about the content size changes.
+ onContentHeightChanged:
+ {
+ scroll.height = Math.min(contentHeight, maximumHeight)
+ popup.height = scroll.height + buttonRow.height
+ }
+
+ Component.onCompleted:
+ {
+ scroll.height = Math.min(contentHeight, maximumHeight)
+ popup.height = scroll.height + buttonRow.height
+ }
+
+ }
+ }
+
+ Rectangle
+ {
+ id: separator
+
+ anchors.top: scroll.bottom
+ width: parent.width
+ height: UM.Theme.getSize("default_lining").height
+ color: UM.Theme.getColor("lining")
+ }
+
+ Row
+ {
+ id: buttonRow
+
+ // The separator is inside the buttonRow. This is to avoid some weird behaviours with the scroll bar.
+ anchors.top: separator.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ padding: UM.Theme.getSize("default_margin").width
+ spacing: UM.Theme.getSize("default_margin").width
+
+ Cura.SecondaryButton
+ {
+ leftPadding: UM.Theme.getSize("default_margin").width
+ rightPadding: UM.Theme.getSize("default_margin").width
+ text: catalog.i18nc("@button", "Add printer")
+ onClicked:
+ {
+ toggleContent()
+ Cura.Actions.addMachine.trigger()
+ }
+ }
+
+ Cura.SecondaryButton
+ {
+ leftPadding: UM.Theme.getSize("default_margin").width
+ rightPadding: UM.Theme.getSize("default_margin").width
+ text: catalog.i18nc("@button", "Manage printers")
+ onClicked:
+ {
+ toggleContent()
+ Cura.Actions.configureMachines.trigger()
+ }
+ }
+ }
+ }
+}
diff --git a/resources/qml/PrinterSelector/MachineSelectorButton.qml b/resources/qml/PrinterSelector/MachineSelectorButton.qml
new file mode 100644
index 0000000000..39e63d27c3
--- /dev/null
+++ b/resources/qml/PrinterSelector/MachineSelectorButton.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.7
+import QtQuick.Controls 2.1
+
+import UM 1.1 as UM
+import Cura 1.0 as Cura
+
+Button
+{
+ id: machineSelectorButton
+
+ width: parent.width
+ height: UM.Theme.getSize("action_button").height
+ leftPadding: UM.Theme.getSize("thick_margin").width
+ rightPadding: UM.Theme.getSize("thick_margin").width
+ checkable: true
+ hoverEnabled: true
+
+ property var outputDevice: null
+ property var printerTypesList: []
+
+ function updatePrinterTypesList()
+ {
+ printerTypesList = (checked && (outputDevice != null)) ? outputDevice.uniquePrinterTypes : []
+ }
+
+ contentItem: Item
+ {
+ width: machineSelectorButton.width - machineSelectorButton.leftPadding
+ height: UM.Theme.getSize("action_button").height
+
+ Label
+ {
+ id: buttonText
+ anchors
+ {
+ left: parent.left
+ right: printerTypes.left
+ verticalCenter: parent.verticalCenter
+ }
+ text: machineSelectorButton.text
+ color: UM.Theme.getColor("text")
+ font: UM.Theme.getFont("medium")
+ visible: text != ""
+ renderType: Text.NativeRendering
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ Row
+ {
+ id: printerTypes
+ width: childrenRect.width
+
+ anchors
+ {
+ right: parent.right
+ verticalCenter: parent.verticalCenter
+ }
+ spacing: UM.Theme.getSize("narrow_margin").width
+
+ Repeater
+ {
+ model: printerTypesList
+ delegate: Cura.PrinterTypeLabel
+ {
+ text: Cura.MachineManager.getAbbreviatedMachineName(modelData)
+ }
+ }
+ }
+ }
+
+ background: Rectangle
+ {
+ id: backgroundRect
+ color: machineSelectorButton.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent"
+ radius: UM.Theme.getSize("action_button_radius").width
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: machineSelectorButton.checked ? UM.Theme.getColor("primary") : "transparent"
+ }
+
+ onClicked:
+ {
+ toggleContent()
+ Cura.MachineManager.setActiveMachine(model.id)
+ }
+
+ Connections
+ {
+ target: outputDevice
+ onUniqueConfigurationsChanged: updatePrinterTypesList()
+ }
+
+ Connections
+ {
+ target: Cura.MachineManager
+ onOutputDevicesChanged: updatePrinterTypesList()
+ }
+
+ Component.onCompleted: updatePrinterTypesList()
+}
diff --git a/resources/qml/PrinterSelector/MachineSelectorList.qml b/resources/qml/PrinterSelector/MachineSelectorList.qml
new file mode 100644
index 0000000000..5fd3515cd3
--- /dev/null
+++ b/resources/qml/PrinterSelector/MachineSelectorList.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.3
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+ListView
+{
+ id: listView
+ model: Cura.GlobalStacksModel {}
+ section.property: "hasRemoteConnection"
+ property real contentHeight: childrenRect.height
+
+ section.delegate: Label
+ {
+ text: section == "true" ? catalog.i18nc("@label", "Connected printers") : catalog.i18nc("@label", "Preset printers")
+ width: parent.width
+ height: UM.Theme.getSize("action_button").height
+ leftPadding: UM.Theme.getSize("default_margin").width
+ renderType: Text.NativeRendering
+ font: UM.Theme.getFont("medium")
+ color: UM.Theme.getColor("text_medium")
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ delegate: MachineSelectorButton
+ {
+ text: model.name
+ width: listView.width
+ outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
+
+ checked:
+ {
+ // If the machine has a remote connection
+ var result = Cura.MachineManager.activeMachineId == model.id
+ if (Cura.MachineManager.activeMachineHasRemoteConnection)
+ {
+ result |= Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["connect_group_name"]
+ }
+ return result
+ }
+ }
+}
diff --git a/resources/qml/PrinterTypeLabel.qml b/resources/qml/PrinterTypeLabel.qml
new file mode 100644
index 0000000000..cfc9e56513
--- /dev/null
+++ b/resources/qml/PrinterTypeLabel.qml
@@ -0,0 +1,35 @@
+// 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 UM 1.1 as UM
+
+// This component creates a label with the abbreviated name of a printer, with a rectangle surrounding the label.
+// It is created in a separated place in order to be reused whenever needed.
+Item
+{
+ property alias text: printerTypeLabel.text
+
+ width: UM.Theme.getSize("printer_type_label").width
+ height: UM.Theme.getSize("printer_type_label").height
+
+ Rectangle
+ {
+ anchors.fill: parent
+ color: UM.Theme.getColor("printer_type_label_background")
+ radius: UM.Theme.getSize("checkbox_radius").width
+ }
+
+ Label
+ {
+ id: printerTypeLabel
+ text: "CFFFP" // As an abbreviated name of the Custom FFF Printer
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ renderType: Text.NativeRendering
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/RoundedRectangle.qml b/resources/qml/RoundedRectangle.qml
new file mode 100644
index 0000000000..3ca05e2125
--- /dev/null
+++ b/resources/qml/RoundedRectangle.qml
@@ -0,0 +1,72 @@
+import QtQuick 2.7
+
+import UM 1.2 as UM
+
+// The rounded rectangle works mostly like a regular rectangle, but provides the option to have rounded corners on only one side of the rectangle.
+Item
+{
+ id: roundedRectangle
+ // As per the regular rectangle
+ property color color: "transparent"
+
+ // As per regular rectangle
+ property int radius: UM.Theme.getSize("default_radius").width
+
+ // On what side should the corners be shown 5 can be used if no radius is needed.
+ // 1 is down, 2 is left, 3 is up and 4 is right.
+ property int cornerSide: RoundedRectangle.Direction.None
+
+ // Simple object to ensure that border.width and border.color work
+ property BorderGroup border: BorderGroup {}
+
+ enum Direction
+ {
+ None = 0,
+ Down = 1,
+ Left = 2,
+ Up = 3,
+ Right = 4,
+ All = 5
+ }
+
+ Rectangle
+ {
+ id: background
+ anchors.fill: parent
+ radius: cornerSide != RoundedRectangle.Direction.None ? parent.radius : 0
+ color: parent.color
+ border.width: parent.border.width
+ border.color: parent.border.color
+ }
+
+ // The item that covers 2 of the corners to make them not rounded.
+ Rectangle
+ {
+ visible: cornerSide != RoundedRectangle.Direction.None && cornerSide != RoundedRectangle.Direction.All
+ height: cornerSide % 2 ? parent.radius: parent.height
+ width: cornerSide % 2 ? parent.width : parent.radius
+ color: parent.color
+ anchors
+ {
+ right: cornerSide == RoundedRectangle.Direction.Left ? parent.right: undefined
+ bottom: cornerSide == RoundedRectangle.Direction.Up ? parent.bottom: undefined
+ }
+
+ border.width: parent.border.width
+ border.color: parent.border.color
+
+ Rectangle
+ {
+ color: roundedRectangle.color
+ height: cornerSide % 2 ? roundedRectangle.border.width: roundedRectangle.height - 2 * roundedRectangle.border.width
+ width: cornerSide % 2 ? roundedRectangle.width - 2 * roundedRectangle.border.width: roundedRectangle.border.width
+ anchors
+ {
+ right: cornerSide == RoundedRectangle.Direction.Right ? parent.right : undefined
+ bottom: cornerSide == RoundedRectangle.Direction.Down ? parent.bottom: undefined
+ horizontalCenter: cornerSide % 2 ? parent.horizontalCenter: undefined
+ verticalCenter: cornerSide % 2 ? undefined: parent.verticalCenter
+ }
+ }
+ }
+}
diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml
deleted file mode 100644
index 2a0a523026..0000000000
--- a/resources/qml/SaveButton.qml
+++ /dev/null
@@ -1,409 +0,0 @@
-// Copyright (c) 2018 Ultimaker B.V.
-// Cura is released under the terms of the LGPLv3 or higher.
-
-import QtQuick 2.7
-import QtQuick.Controls 1.1
-import QtQuick.Controls.Styles 1.1
-import QtQuick.Layouts 1.1
-
-import UM 1.1 as UM
-import Cura 1.0 as Cura
-
-Item {
- id: base;
- UM.I18nCatalog { id: catalog; name:"cura"}
-
- property real progress: UM.Backend.progress
- property int backendState: UM.Backend.state
- property bool activity: CuraApplication.platformActivity
-
- property alias buttonRowWidth: saveRow.width
-
- property string fileBaseName
- property string statusText:
- {
- if(!activity)
- {
- return catalog.i18nc("@label:PrintjobStatus", "Please load a 3D model");
- }
-
- if (base.backendState == "undefined") {
- return ""
- }
-
- switch(base.backendState)
- {
- case 1:
- return catalog.i18nc("@label:PrintjobStatus", "Ready to slice");
- case 2:
- return catalog.i18nc("@label:PrintjobStatus", "Slicing...");
- case 3:
- return catalog.i18nc("@label:PrintjobStatus %1 is target operation","Ready to %1").arg(UM.OutputDeviceManager.activeDeviceShortDescription);
- case 4:
- return catalog.i18nc("@label:PrintjobStatus", "Unable to Slice");
- case 5:
- return catalog.i18nc("@label:PrintjobStatus", "Slicing unavailable");
- default:
- return "";
- }
- }
-
- function sliceOrStopSlicing() {
- try {
- if ([1, 5].indexOf(base.backendState) != -1) {
- CuraApplication.backend.forceSlice();
- } else {
- CuraApplication.backend.stopSlicing();
- }
- } catch (e) {
- console.log('Could not start or stop slicing', e)
- }
- }
-
- Label {
- id: statusLabel
- width: parent.width - 2 * UM.Theme.getSize("sidebar_margin").width
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
-
- color: UM.Theme.getColor("text")
- font: UM.Theme.getFont("default_bold")
- text: statusText;
- }
-
- Rectangle {
- id: progressBar
- width: parent.width - 2 * UM.Theme.getSize("sidebar_margin").width
- height: UM.Theme.getSize("progressbar").height
- anchors.top: statusLabel.bottom
- anchors.topMargin: Math.round(UM.Theme.getSize("sidebar_margin").height / 4)
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
- radius: UM.Theme.getSize("progressbar_radius").width
- color: UM.Theme.getColor("progressbar_background")
-
- Rectangle {
- width: Math.max(parent.width * base.progress)
- height: parent.height
- color: UM.Theme.getColor("progressbar_control")
- radius: UM.Theme.getSize("progressbar_radius").width
- visible: (base.backendState != "undefined" && base.backendState == 2) ? true : false
- }
- }
-
- // Shortcut for "save as/print/..."
- Action {
- shortcut: "Ctrl+P"
- onTriggered:
- {
- // only work when the button is enabled
- if (saveToButton.enabled) {
- saveToButton.clicked();
- }
- // prepare button
- if (prepareButton.enabled) {
- sliceOrStopSlicing();
- }
- }
- }
-
- Item {
- id: saveRow
- width: {
- // using childrenRect.width directly causes a binding loop, because setting the width affects the childrenRect
- var children_width = UM.Theme.getSize("default_margin").width;
- for (var index in children)
- {
- var child = children[index];
- if(child.visible)
- {
- children_width += child.width + child.anchors.rightMargin;
- }
- }
- return Math.min(children_width, base.width - UM.Theme.getSize("sidebar_margin").width);
- }
- height: saveToButton.height
- anchors.bottom: parent.bottom
- anchors.bottomMargin: UM.Theme.getSize("sidebar_margin").height
- anchors.right: parent.right
- clip: true
-
- Row {
- id: additionalComponentsRow
- anchors.top: parent.top
- anchors.right: saveToButton.visible ? saveToButton.left : (prepareButton.visible ? prepareButton.left : parent.right)
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
-
- spacing: UM.Theme.getSize("default_margin").width
- }
-
- Component.onCompleted: {
- saveRow.addAdditionalComponents("saveButton")
- }
-
- Connections {
- target: CuraApplication
- onAdditionalComponentsChanged: saveRow.addAdditionalComponents("saveButton")
- }
-
- function addAdditionalComponents (areaId) {
- if(areaId == "saveButton") {
- for (var component in CuraApplication.additionalComponents["saveButton"]) {
- CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow
- }
- }
- }
-
- Connections {
- target: UM.Preferences
- onPreferenceChanged:
- {
- var autoSlice = UM.Preferences.getValue("general/auto_slice");
- prepareButton.autoSlice = autoSlice;
- saveToButton.autoSlice = autoSlice;
- }
- }
-
- // Prepare button, only shows if auto_slice is off
- Button {
- id: prepareButton
-
- tooltip: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@info:tooltip","Slice current printjob") : catalog.i18nc("@info:tooltip","Cancel slicing process")
- // 1 = not started, 2 = Processing
- enabled: base.backendState != "undefined" && ([1, 2].indexOf(base.backendState) != -1) && base.activity
- visible: base.backendState != "undefined" && !autoSlice && ([1, 2, 4].indexOf(base.backendState) != -1) && base.activity
- property bool autoSlice
- height: UM.Theme.getSize("save_button_save_to_button").height
-
- anchors.top: parent.top
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
-
- // 1 = not started, 4 = error, 5 = disabled
- text: [1, 4, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@label:Printjob", "Prepare") : catalog.i18nc("@label:Printjob", "Cancel")
- onClicked:
- {
- sliceOrStopSlicing();
- }
-
- 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("action_button_active_border");
- else if(control.hovered)
- return UM.Theme.getColor("action_button_hovered_border");
- else
- return UM.Theme.getColor("action_button_border");
- }
- color:
- {
- if(!control.enabled)
- return UM.Theme.getColor("action_button_disabled");
- else if(control.pressed)
- return UM.Theme.getColor("action_button_active");
- else if(control.hovered)
- return UM.Theme.getColor("action_button_hovered");
- else
- return UM.Theme.getColor("action_button");
- }
-
- 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("action_button_active_text");
- else if(control.hovered)
- return UM.Theme.getColor("action_button_hovered_text");
- else
- return UM.Theme.getColor("action_button_text");
- }
- font: UM.Theme.getFont("action_button")
- text: control.text;
- }
- }
- label: Item { }
- }
- }
-
- Button {
- id: saveToButton
-
- tooltip: UM.OutputDeviceManager.activeDeviceDescription;
- // 3 = done, 5 = disabled
- enabled: base.backendState != "undefined" && (base.backendState == 3 || base.backendState == 5) && base.activity == true
- visible: base.backendState != "undefined" && autoSlice || ((base.backendState == 3 || base.backendState == 5) && base.activity == true)
- property bool autoSlice
- height: UM.Theme.getSize("save_button_save_to_button").height
-
- anchors.top: parent.top
- anchors.right: deviceSelectionMenu.visible ? deviceSelectionMenu.left : parent.right
- anchors.rightMargin: deviceSelectionMenu.visible ? -3 * UM.Theme.getSize("default_lining").width : UM.Theme.getSize("sidebar_margin").width
-
- text: UM.OutputDeviceManager.activeDeviceShortDescription
- onClicked:
- {
- forceActiveFocus();
- UM.OutputDeviceManager.requestWriteToDevice(UM.OutputDeviceManager.activeDevice, PrintInformation.jobName,
- { "filter_by_machine": true, "preferred_mimetypes": Cura.MachineManager.activeMachine.preferred_output_file_formats });
- }
-
- 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 { }
- }
- }
-
- Button {
- id: deviceSelectionMenu
- tooltip: catalog.i18nc("@info:tooltip","Select the active output device");
- anchors.top: parent.top
- anchors.right: parent.right
-
- anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
- width: UM.Theme.getSize("save_button_save_to_button").height
- height: UM.Theme.getSize("save_button_save_to_button").height
- // 3 = Done, 5 = Disabled
- enabled: base.backendState != "undefined" && (base.backendState == 3 || base.backendState == 5) && base.activity == true
- visible: base.backendState != "undefined" && (devicesModel.deviceCount > 1) && (base.backendState == 3 || base.backendState == 5) && base.activity == true
-
-
- style: ButtonStyle {
- background: Rectangle {
- id: deviceSelectionIcon
- 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; } }
- 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: UM.Theme.getSize("standard_arrow").width
- height: UM.Theme.getSize("standard_arrow").height
- sourceSize.width: width
- sourceSize.height: height
- 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");
- }
- source: UM.Theme.getIcon("arrow_bottom");
- }
- }
- label: Label{ }
- }
-
- menu: Menu {
- id: devicesMenu;
- Instantiator {
- model: devicesModel;
- MenuItem {
- text: model.description
- checkable: true;
- checked: model.id == UM.OutputDeviceManager.activeDevice;
- exclusiveGroup: devicesMenuGroup;
- onTriggered: {
- UM.OutputDeviceManager.setActiveDevice(model.id);
- }
- }
- onObjectAdded: devicesMenu.insertItem(index, object)
- onObjectRemoved: devicesMenu.removeItem(object)
- }
- ExclusiveGroup { id: devicesMenuGroup; }
- }
- }
- UM.OutputDevicesModel { id: devicesModel; }
- }
-}
diff --git a/resources/qml/SecondaryButton.qml b/resources/qml/SecondaryButton.qml
new file mode 100644
index 0000000000..f03d8acdfa
--- /dev/null
+++ b/resources/qml/SecondaryButton.qml
@@ -0,0 +1,20 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+
+import UM 1.4 as UM
+import Cura 1.1 as Cura
+
+
+Cura.ActionButton
+{
+ shadowEnabled: true
+ shadowColor: enabled ? UM.Theme.getColor("secondary_button_shadow"): UM.Theme.getColor("action_button_disabled_shadow")
+ color: UM.Theme.getColor("secondary_button")
+ textColor: UM.Theme.getColor("secondary_button_text")
+ outlineColor: "transparent"
+ disabledColor: UM.Theme.getColor("action_button_disabled")
+ textDisabledColor: UM.Theme.getColor("action_button_disabled_text")
+ hoverColor: UM.Theme.getColor("secondary_button_hover")
+}
\ No newline at end of file
diff --git a/resources/qml/Settings/SettingCategory.qml b/resources/qml/Settings/SettingCategory.qml
index e3202323eb..1e88867889 100644
--- a/resources/qml/Settings/SettingCategory.qml
+++ b/resources/qml/Settings/SettingCategory.qml
@@ -12,48 +12,39 @@ Button
id: base
anchors.left: parent.left
anchors.right: parent.right
- anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
- anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
+ // To avoid overlaping with the scrollBars
+ anchors.rightMargin: 2 * UM.Theme.getSize("thin_margin").width
+ hoverEnabled: true
+
background: Rectangle
{
id: backgroundRectangle
implicitHeight: UM.Theme.getSize("section").height
- color: {
- if (base.color) {
- return base.color;
- } else if (!base.enabled) {
- return UM.Theme.getColor("setting_category_disabled");
- } else if (base.hovered && base.checkable && base.checked) {
- return UM.Theme.getColor("setting_category_active_hover");
- } else if (base.pressed || (base.checkable && base.checked)) {
- return UM.Theme.getColor("setting_category_active");
- } else if (base.hovered) {
- return UM.Theme.getColor("setting_category_hover");
- } else {
- return UM.Theme.getColor("setting_category");
+ color:
+ {
+ if (base.color)
+ {
+ return base.color
}
+ else if (!base.enabled)
+ {
+ return UM.Theme.getColor("setting_category_disabled")
+ }
+ else if (base.hovered && base.checkable && base.checked)
+ {
+ return UM.Theme.getColor("setting_category_active_hover")
+ }
+ else if (base.pressed || (base.checkable && base.checked))
+ {
+ return UM.Theme.getColor("setting_category_active")
+ }
+ else if (base.hovered)
+ {
+ return UM.Theme.getColor("setting_category_hover")
+ }
+ return UM.Theme.getColor("setting_category")
}
Behavior on color { ColorAnimation { duration: 50; } }
- Rectangle
- {
- id: backgroundLiningRectangle
- height: UM.Theme.getSize("default_lining").height
- width: parent.width
- anchors.bottom: parent.bottom
- color: {
- if (!base.enabled) {
- return UM.Theme.getColor("setting_category_disabled_border");
- } else if ((base.hovered || base.activeFocus) && base.checkable && base.checked) {
- return UM.Theme.getColor("setting_category_active_hover_border");
- } else if (base.pressed || (base.checkable && base.checked)) {
- return UM.Theme.getColor("setting_category_active_border");
- } else if (base.hovered || base.activeFocus) {
- return UM.Theme.getColor("setting_category_hover_border");
- } else {
- return UM.Theme.getColor("setting_category_border");
- }
- }
- }
}
signal showTooltip(string text)
@@ -65,40 +56,47 @@ Button
property var focusItem: base
- contentItem: Item {
+ contentItem: Item
+ {
anchors.fill: parent
- anchors.left: parent.left
- Label {
+ Label
+ {
id: settingNameLabel
anchors
{
left: parent.left
leftMargin: 2 * UM.Theme.getSize("default_margin").width + UM.Theme.getSize("section_icon").width
- right: parent.right;
- verticalCenter: parent.verticalCenter;
+ right: parent.right
+ verticalCenter: parent.verticalCenter
}
text: definition.label
textFormat: Text.PlainText
renderType: Text.NativeRendering
- font: UM.Theme.getFont("setting_category")
+ font: UM.Theme.getFont("medium_bold")
color:
{
- if (!base.enabled) {
- return UM.Theme.getColor("setting_category_disabled_text");
- } else if ((base.hovered || base.activeFocus) && base.checkable && base.checked) {
- return UM.Theme.getColor("setting_category_active_hover_text");
- } else if (base.pressed || (base.checkable && base.checked)) {
- return UM.Theme.getColor("setting_category_active_text");
- } else if (base.hovered || base.activeFocus) {
- return UM.Theme.getColor("setting_category_hover_text");
- } else {
- return UM.Theme.getColor("setting_category_text");
+ if (!base.enabled)
+ {
+ return UM.Theme.getColor("setting_category_disabled_text")
+ } else if ((base.hovered || base.activeFocus) && base.checkable && base.checked)
+ {
+ return UM.Theme.getColor("setting_category_active_hover_text")
+ } else if (base.pressed || (base.checkable && base.checked))
+ {
+ return UM.Theme.getColor("setting_category_active_text")
+ } else if (base.hovered || base.activeFocus)
+ {
+ return UM.Theme.getColor("setting_category_hover_text")
+ } else
+ {
+ return UM.Theme.getColor("setting_category_text")
}
}
fontSizeMode: Text.HorizontalFit
minimumPointSize: 8
}
+
UM.RecolorImage
{
id: category_arrow
@@ -107,22 +105,8 @@ Button
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
- sourceSize.width: width
sourceSize.height: width
- color:
- {
- if (!base.enabled) {
- return UM.Theme.getColor("setting_category_disabled_text");
- } else if ((base.hovered || base.activeFocus) && base.checkable && base.checked) {
- return UM.Theme.getColor("setting_category_active_hover_text");
- } else if (base.pressed || (base.checkable && base.checked)) {
- return UM.Theme.getColor("setting_category_active_text");
- } else if (base.hovered || base.activeFocus) {
- return UM.Theme.getColor("setting_category_hover_text");
- } else {
- return UM.Theme.getColor("setting_category_text");
- }
- }
+ color: UM.Theme.getColor("setting_control_button")
source: base.checked ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_left")
}
}
@@ -132,24 +116,30 @@ Button
id: icon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.leftMargin: UM.Theme.getSize("thin_margin").width
color:
{
- if (!base.enabled) {
- return UM.Theme.getColor("setting_category_disabled_text");
- } else if((base.hovered || base.activeFocus) && base.checkable && base.checked) {
- return UM.Theme.getColor("setting_category_active_hover_text");
- } else if(base.pressed || (base.checkable && base.checked)) {
- return UM.Theme.getColor("setting_category_active_text");
- } else if(base.hovered || base.activeFocus) {
- return UM.Theme.getColor("setting_category_hover_text");
- } else {
- return UM.Theme.getColor("setting_category_text");
+ if (!base.enabled)
+ {
+ return UM.Theme.getColor("setting_category_disabled_text")
}
+ else if((base.hovered || base.activeFocus) && base.checkable && base.checked)
+ {
+ return UM.Theme.getColor("setting_category_active_hover_text")
+ }
+ else if(base.pressed || (base.checkable && base.checked))
+ {
+ return UM.Theme.getColor("setting_category_active_text")
+ }
+ else if(base.hovered || base.activeFocus)
+ {
+ return UM.Theme.getColor("setting_category_hover_text")
+ }
+ return UM.Theme.getColor("setting_category_text")
}
source: UM.Theme.getIcon(definition.icon)
- width: UM.Theme.getSize("section_icon").width;
- height: UM.Theme.getSize("section_icon").height;
+ width: UM.Theme.getSize("section_icon").width
+ height: UM.Theme.getSize("section_icon").height
sourceSize.width: width + 15 * screenScaleFactor
sourceSize.height: width + 15 * screenScaleFactor
}
@@ -159,31 +149,28 @@ Button
onClicked:
{
- if (definition.expanded) {
- settingDefinitionsModel.collapse(definition.key);
- } else {
- settingDefinitionsModel.expandRecursive(definition.key);
+ if (definition.expanded)
+ {
+ settingDefinitionsModel.collapse(definition.key)
+ }
+ else
+ {
+ settingDefinitionsModel.expandRecursive(definition.key)
}
//Set focus so that tab navigation continues from this point on.
//NB: This must be set AFTER collapsing/expanding the category so that the scroll position is correct.
- forceActiveFocus();
+ forceActiveFocus()
}
onActiveFocusChanged:
{
- if(activeFocus)
+ if (activeFocus)
{
- base.focusReceived();
+ base.focusReceived()
}
}
- Keys.onTabPressed:
- {
- base.setActiveFocusToNextSetting(true)
- }
- Keys.onBacktabPressed:
- {
- base.setActiveFocusToNextSetting(false)
- }
+ Keys.onTabPressed: base.setActiveFocusToNextSetting(true)
+ Keys.onBacktabPressed: base.setActiveFocusToNextSetting(false)
UM.SimpleButton
{
@@ -193,9 +180,10 @@ Button
height: Math.round(base.height * 0.6)
width: Math.round(base.height * 0.6)
- anchors {
+ anchors
+ {
right: inheritButton.visible ? inheritButton.left : parent.right
- // use 1.9 as the factor because there is a 0.1 difference between the settings and inheritance warning icons
+ // Use 1.9 as the factor because there is a 0.1 difference between the settings and inheritance warning icons
rightMargin: inheritButton.visible ? Math.round(UM.Theme.getSize("default_margin").width / 2) : category_arrow.width + Math.round(UM.Theme.getSize("default_margin").width * 1.9)
verticalCenter: parent.verticalCenter
}
@@ -204,9 +192,7 @@ Button
hoverColor: UM.Theme.getColor("setting_control_button_hover")
iconSource: UM.Theme.getIcon("settings")
- onClicked: {
- Cura.Actions.configureSettingVisibility.trigger(definition)
- }
+ onClicked: Cura.Actions.configureSettingVisibility.trigger(definition)
}
UM.SimpleButton
@@ -239,24 +225,18 @@ Button
onClicked:
{
- settingDefinitionsModel.expandRecursive(definition.key);
- base.checked = true;
- base.showAllHiddenInheritedSettings(definition.key);
+ settingDefinitionsModel.expandRecursive(definition.key)
+ base.checked = true
+ base.showAllHiddenInheritedSettings(definition.key)
}
color: UM.Theme.getColor("setting_control_button")
hoverColor: UM.Theme.getColor("setting_control_button_hover")
iconSource: UM.Theme.getIcon("notice")
- onEntered:
- {
- base.showTooltip(catalog.i18nc("@label","Some hidden settings use values different from their normal calculated value.\n\nClick to make these settings visible."))
- }
+ onEntered: base.showTooltip(catalog.i18nc("@label","Some hidden settings use values different from their normal calculated value.\n\nClick to make these settings visible."))
- onExited:
- {
- base.hideTooltip();
- }
+ onExited: base.hideTooltip()
UM.I18nCatalog { id: catalog; name: "cura" }
}
diff --git a/resources/qml/Settings/SettingCheckBox.qml b/resources/qml/Settings/SettingCheckBox.qml
index d37754d27c..0c7321d08a 100644
--- a/resources/qml/Settings/SettingCheckBox.qml
+++ b/resources/qml/Settings/SettingCheckBox.qml
@@ -28,37 +28,40 @@ SettingItem
// 3: material -> user changed material in materials page
// 4: variant
// 5: machine
- var value;
- if ((base.resolve != "None") && (stackLevel != 0) && (stackLevel != 1)) {
+ var value
+ if ((base.resolve != "None") && (stackLevel != 0) && (stackLevel != 1))
+ {
// We have a resolve function. Indicates that the setting is not settable per extruder and that
// we have to choose between the resolved value (default) and the global value
// (if user has explicitly set this).
- value = base.resolve;
- } else {
- value = propertyProvider.properties.value;
+ value = base.resolve
+ }
+ else
+ {
+ value = propertyProvider.properties.value
}
switch(value)
{
case "True":
- return true;
+ return true
case "False":
- return false;
+ return false
default:
- return value;
+ return value
}
}
Keys.onSpacePressed:
{
- forceActiveFocus();
- propertyProvider.setPropertyValue("value", !checked);
+ forceActiveFocus()
+ propertyProvider.setPropertyValue("value", !checked)
}
onClicked:
{
- forceActiveFocus();
- propertyProvider.setPropertyValue("value", !checked);
+ forceActiveFocus()
+ propertyProvider.setPropertyValue("value", !checked)
}
Keys.onTabPressed:
@@ -72,9 +75,9 @@ SettingItem
onActiveFocusChanged:
{
- if(activeFocus)
+ if (activeFocus)
{
- base.focusReceived();
+ base.focusReceived()
}
}
@@ -90,37 +93,38 @@ SettingItem
color:
{
- if(!enabled)
+ if (!enabled)
{
return UM.Theme.getColor("setting_control_disabled")
}
- if(control.containsMouse || control.activeFocus)
+ if (control.containsMouse || control.activeFocus)
{
return UM.Theme.getColor("setting_control_highlight")
}
return UM.Theme.getColor("setting_control")
}
+ radius: UM.Theme.getSize("setting_control_radius").width
border.width: UM.Theme.getSize("default_lining").width
border.color:
{
- if(!enabled)
+ if (!enabled)
{
return UM.Theme.getColor("setting_control_disabled_border")
}
- if(control.containsMouse || control.activeFocus)
+ if (control.containsMouse || control.activeFocus)
{
return UM.Theme.getColor("setting_control_border_highlight")
}
return UM.Theme.getColor("setting_control_border")
}
- UM.RecolorImage {
+ UM.RecolorImage
+ {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(parent.width / 2.5)
height: Math.round(parent.height / 2.5)
- sourceSize.width: width
sourceSize.height: width
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text");
source: UM.Theme.getIcon("check")
diff --git a/resources/qml/Settings/SettingComboBox.qml b/resources/qml/Settings/SettingComboBox.qml
index 76d458e427..a287e0c3ce 100644
--- a/resources/qml/Settings/SettingComboBox.qml
+++ b/resources/qml/Settings/SettingComboBox.qml
@@ -35,6 +35,7 @@ SettingItem
return UM.Theme.getColor("setting_control")
}
+ radius: UM.Theme.getSize("setting_control_radius").width
border.width: UM.Theme.getSize("default_lining").width
border.color:
{
@@ -62,7 +63,7 @@ SettingItem
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
- color: UM.Theme.getColor("setting_control_text")
+ color: UM.Theme.getColor("setting_control_button")
}
contentItem: Label
diff --git a/resources/qml/Settings/SettingExtruder.qml b/resources/qml/Settings/SettingExtruder.qml
index a9427f863a..6d39192de7 100644
--- a/resources/qml/Settings/SettingExtruder.qml
+++ b/resources/qml/Settings/SettingExtruder.qml
@@ -17,10 +17,16 @@ SettingItem
id: control
anchors.fill: parent
- model: Cura.ExtrudersModel
+ property var extrudersModel: CuraApplication.getExtrudersModel()
+
+ model: extrudersModel
+
+ Connections
{
- onModelChanged: {
- control.color = getItem(control.currentIndex).color;
+ target: extrudersModel
+ onModelChanged:
+ {
+ control.color = extrudersModel.getItem(control.currentIndex).color
}
}
@@ -104,7 +110,7 @@ SettingItem
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
- color: UM.Theme.getColor("setting_control_text");
+ color: UM.Theme.getColor("setting_control_button");
}
background: Rectangle
@@ -113,14 +119,15 @@ SettingItem
{
if (!enabled)
{
- return UM.Theme.getColor("setting_control_disabled");
+ return UM.Theme.getColor("setting_control_disabled")
}
if (control.hovered || base.activeFocus)
{
- return UM.Theme.getColor("setting_control_highlight");
+ return UM.Theme.getColor("setting_control_highlight")
}
- return UM.Theme.getColor("setting_control");
+ return UM.Theme.getColor("setting_control")
}
+ radius: UM.Theme.getSize("setting_control_radius").width
border.width: UM.Theme.getSize("default_lining").width
border.color:
{
@@ -153,20 +160,18 @@ SettingItem
elide: Text.ElideLeft
verticalAlignment: Text.AlignVCenter
- background: Rectangle
+ background: UM.RecolorImage
{
id: swatch
- height: Math.round(UM.Theme.getSize("setting_control").height / 2)
+ height: Math.round(parent.height / 2)
width: height
-
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- anchors.margins: Math.round(UM.Theme.getSize("default_margin").width / 4)
-
- border.width: UM.Theme.getSize("default_lining").width
- border.color: enabled ? UM.Theme.getColor("setting_control_border") : UM.Theme.getColor("setting_control_disabled_border")
- radius: Math.round(width / 2)
+ anchors.rightMargin: UM.Theme.getSize("thin_margin").width
+ sourceSize.width: width
+ sourceSize.height: height
+ source: UM.Theme.getIcon("extruder_button")
color: control.color
}
}
@@ -219,20 +224,18 @@ SettingItem
verticalAlignment: Text.AlignVCenter
rightPadding: swatch.width + UM.Theme.getSize("setting_unit_margin").width
- background: Rectangle
+ background: UM.RecolorImage
{
id: swatch
- height: Math.round(UM.Theme.getSize("setting_control").height / 2)
+ height: Math.round(parent.height / 2)
width: height
-
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- anchors.margins: Math.round(UM.Theme.getSize("default_margin").width / 4)
-
- border.width: UM.Theme.getSize("default_lining").width
- border.color: enabled ? UM.Theme.getColor("setting_control_border") : UM.Theme.getColor("setting_control_disabled_border")
- radius: Math.round(width / 2)
+ anchors.rightMargin: UM.Theme.getSize("thin_margin").width
+ sourceSize.width: width
+ sourceSize.height: height
+ source: UM.Theme.getIcon("extruder_button")
color: control.model.getItem(index).color
}
}
diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml
index 785562cff5..4dd53f8663 100644
--- a/resources/qml/Settings/SettingItem.qml
+++ b/resources/qml/Settings/SettingItem.qml
@@ -10,10 +10,15 @@ import Cura 1.0 as Cura
import "."
-Item {
- id: base;
+Item
+{
+ id: base
height: UM.Theme.getSize("section").height
+ anchors.left: parent.left
+ anchors.right: parent.right
+ // To avoid overlaping with the scrollBars
+ anchors.rightMargin: 2 * UM.Theme.getSize("thin_margin").width
property alias contents: controlContainer.children
property alias hovered: mouse.containsMouse
@@ -43,25 +48,25 @@ Item {
var affected_by = settingDefinitionsModel.getRequires(definition.key, "value")
var affected_by_list = ""
- for(var i in affected_by)
+ for (var i in affected_by)
{
affected_by_list += "
%1
\n".arg(affected_by[i].label)
}
var affects_list = ""
- for(var i in affects)
+ for (var i in affects)
{
affects_list += "