diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py
index 61802167a7..f071bf4057 100644
--- a/cura/BuildVolume.py
+++ b/cura/BuildVolume.py
@@ -199,7 +199,6 @@ class BuildVolume(SceneNode):
disallowed_areas = self._active_container_stack.getProperty("machine_disallowed_areas", "value")
areas = []
- skirt_size = 0.0
skirt_size = self._getSkirtSize(self._active_container_stack)
if disallowed_areas:
diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py
index 57de2959eb..e86e407902 100644
--- a/cura/CrashHandler.py
+++ b/cura/CrashHandler.py
@@ -5,17 +5,33 @@ import webbrowser
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QCoreApplication
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit
+
+from UM.Logger import Logger
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
+# 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,
+]
+
def show(exception_type, value, tb):
debug_mode = False
if QCoreApplication.instance():
debug_mode = QCoreApplication.instance().getCommandLineOption("debug-mode", False)
- traceback.print_exception(exception_type, value, tb)
+ Logger.log("c", "An uncaught exception has occurred!")
+ for line in traceback.format_exception(exception_type, value, tb):
+ for part in line.rstrip("\n").split("\n"):
+ Logger.log("c", part)
- if not debug_mode:
+ if not debug_mode and exception_type not in fatal_exception_types:
return
application = QCoreApplication.instance()
@@ -29,7 +45,7 @@ def show(exception_type, value, tb):
label = QLabel(dialog)
layout.addWidget(label)
- label.setText(catalog.i18nc("@label", "
An uncaught exception has occurred!
Please use the information below to post a bug report at http://github.com/Ultimaker/Cura/issues
"))
+ label.setText(catalog.i18nc("@label", "A fatal exception has occurred that we could not recover from!
Please use the information below to post a bug report at http://github.com/Ultimaker/Cura/issues
"))
textarea = QTextEdit(dialog)
layout.addWidget(textarea)
diff --git a/cura/CuraContainerRegistry.py b/cura/CuraContainerRegistry.py
new file mode 100644
index 0000000000..854fe030da
--- /dev/null
+++ b/cura/CuraContainerRegistry.py
@@ -0,0 +1,208 @@
+# Copyright (c) 2016 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+import os
+import os.path
+import re
+from PyQt5.QtWidgets import QMessageBox
+
+from UM.Settings.ContainerRegistry import ContainerRegistry
+from UM.Settings.ContainerStack import ContainerStack
+from UM.Settings.InstanceContainer import InstanceContainer
+from UM.Application import Application
+from UM.Logger import Logger
+from UM.Message import Message
+from UM.Platform import Platform
+from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with.
+from UM.Util import parseBool
+
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+
+class CuraContainerRegistry(ContainerRegistry):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ ## Create a name that is not empty and unique
+ # \param container_type \type{string} Type of the container (machine, quality, ...)
+ # \param current_name \type{} Current name of the container, which may be an acceptable option
+ # \param new_name \type{string} Base name, which may not be unique
+ # \param fallback_name \type{string} Name to use when (stripped) new_name is empty
+ # \return \type{string} Name that is unique for the specified type and name/id
+ def createUniqueName(self, container_type, current_name, new_name, fallback_name):
+ new_name = new_name.strip()
+ num_check = re.compile("(.*?)\s*#\d+$").match(new_name)
+ if num_check:
+ new_name = num_check.group(1)
+ if new_name == "":
+ new_name = fallback_name
+
+ unique_name = new_name
+ i = 1
+ # In case we are renaming, the current name of the container is also a valid end-result
+ while self._containerExists(container_type, unique_name) and unique_name != current_name:
+ i += 1
+ unique_name = "%s #%d" % (new_name, i)
+
+ return unique_name
+
+ ## Check if a container with of a certain type and a certain name or id exists
+ # Both the id and the name are checked, because they may not be the same and it is better if they are both unique
+ # \param container_type \type{string} Type of the container (machine, quality, ...)
+ # \param container_name \type{string} Name to check
+ def _containerExists(self, container_type, container_name):
+ container_class = ContainerStack if container_type == "machine" else InstanceContainer
+
+ return self.findContainers(container_class, id = container_name, type = container_type) or \
+ self.findContainers(container_class, name = container_name, type = container_type)
+
+ ## Exports an profile to a file
+ #
+ # \param instance_id \type{str} the ID of the profile to export.
+ # \param file_name \type{str} the full path and filename to export to.
+ # \param file_type \type{str} the file type with the format " (*.)"
+ def exportProfile(self, instance_id, file_name, file_type):
+ Logger.log('d', 'exportProfile instance_id: '+str(instance_id))
+
+ # Parse the fileType to deduce what plugin can save the file format.
+ # fileType has the format " (*.)"
+ split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
+ if split < 0: # Not found. Invalid format.
+ Logger.log("e", "Invalid file format identifier %s", file_type)
+ return
+ description = file_type[:split]
+ extension = file_type[split + 4:-1] # Leave out the " (*." and ")".
+ if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any.
+ file_name += "." + extension
+
+ # On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself.
+ if not Platform.isWindows():
+ if os.path.exists(file_name):
+ result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
+ catalog.i18nc("@label", "The file {0} already exists. Are you sure you want to overwrite it?").format(file_name))
+ if result == QMessageBox.No:
+ return
+
+ containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id)
+ if not containers:
+ return
+ container = containers[0]
+
+ profile_writer = self._findProfileWriter(extension, description)
+
+ try:
+ success = profile_writer.write(file_name, container)
+ except Exception as e:
+ Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
+ m = Message(catalog.i18nc("@info:status", "Failed to export profile to {0}: {1}", file_name, str(e)), lifetime = 0)
+ m.show()
+ return
+ if not success:
+ Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name)
+ m = Message(catalog.i18nc("@info:status", "Failed to export profile to {0}: Writer plugin reported failure.", file_name), lifetime = 0)
+ m.show()
+ return
+ m = Message(catalog.i18nc("@info:status", "Exported profile to {0}", file_name))
+ m.show()
+
+ ## Gets the plugin object matching the criteria
+ # \param extension
+ # \param description
+ # \return The plugin object matching the given extension and description.
+ def _findProfileWriter(self, extension, description):
+ pr = PluginRegistry.getInstance()
+ for plugin_id, meta_data in self._getIOPlugins("profile_writer"):
+ for supported_type in meta_data["profile_writer"]: # All file types this plugin can supposedly write.
+ supported_extension = supported_type.get("extension", None)
+ if supported_extension == extension: # This plugin supports a file type with the same extension.
+ supported_description = supported_type.get("description", None)
+ if supported_description == description: # The description is also identical. Assume it's the same file type.
+ return pr.getPluginObject(plugin_id)
+ return None
+
+ ## 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):
+ if not file_name:
+ return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from {0}: {1}", file_name, "Invalid path")}
+
+ pr = PluginRegistry.getInstance()
+ for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
+ profile_reader = pr.getPluginObject(plugin_id)
+ try:
+ profile = profile_reader.read(file_name) #Try to open the file with the profile reader.
+ except Exception as e:
+ #Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
+ Logger.log("e", "Failed to import profile from %s: %s", file_name, str(e))
+ return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from {0}: {1}", file_name, str(e))}
+ if profile: #Success!
+ profile.setReadOnly(False)
+
+ new_name = self.createUniqueName("quality", "", os.path.splitext(os.path.basename(file_name))[0],
+ catalog.i18nc("@label", "Custom profile"))
+ profile.setName(new_name)
+
+ if self._machineHasOwnQualities():
+ profile.setDefinition(self._activeDefinition())
+ if self._machineHasOwnMaterials():
+ profile.addMetaDataEntry("material", self._activeMaterialId())
+ else:
+ profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
+ ContainerRegistry.getInstance().addContainer(profile)
+
+ return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) }
+
+ #If it hasn't returned by now, none of the plugins loaded the profile successfully.
+ return { "status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type.", file_name)}
+
+ ## Gets a list of profile writer plugins
+ # \return List of tuples of (plugin_id, meta_data).
+ def _getIOPlugins(self, io_type):
+ pr = PluginRegistry.getInstance()
+ active_plugin_ids = pr.getActivePlugins()
+
+ result = []
+ for plugin_id in active_plugin_ids:
+ meta_data = pr.getMetaData(plugin_id)
+ if io_type in meta_data:
+ result.append( (plugin_id, meta_data) )
+ return result
+
+ ## Gets the active definition
+ # \return the active definition object or None if there is no definition
+ def _activeDefinition(self):
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if global_container_stack:
+ definition = global_container_stack.getBottom()
+ if definition:
+ return definition
+ return None
+
+ ## Returns true if the current machine requires its own materials
+ # \return True if the current machine requires its own materials
+ def _machineHasOwnMaterials(self):
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if global_container_stack:
+ return global_container_stack.getMetaDataEntry("has_materials", False)
+ return False
+
+ ## Gets the ID of the active material
+ # \return the ID of the active material or the empty string
+ def _activeMaterialId(self):
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if global_container_stack:
+ material = global_container_stack.findContainer({"type": "material"})
+ if material:
+ return material.getId()
+ return ""
+
+ ## Returns true if the current machien requires its own quality profiles
+ # \return true if the current machien requires its own quality profiles
+ def _machineHasOwnQualities(self):
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if global_container_stack:
+ return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False))
+ return False
diff --git a/cura/ExtruderManager.py b/cura/ExtruderManager.py
index 84c3028c72..b6739740f5 100644
--- a/cura/ExtruderManager.py
+++ b/cura/ExtruderManager.py
@@ -18,7 +18,7 @@ class ExtruderManager(QObject):
## Notify when the user switches the currently active extruder.
activeExtruderChanged = pyqtSignal()
- ## Registers listeners and such to listen to chafnges to the extruders.
+ ## Registers listeners and such to listen to changes to the extruders.
def __init__(self, parent = None):
super().__init__(parent)
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
@@ -67,12 +67,14 @@ class ExtruderManager(QObject):
self.activeExtruderChanged.emit()
def getActiveExtruderStack(self):
- try:
- return self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getBottom().getId()][str(self._active_extruder_index)]
- except AttributeError:
- return None
- except KeyError:
- return None
+ global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
+ if global_container_stack:
+ global_definition_container = UM.Application.getInstance().getGlobalContainerStack().getBottom()
+ if global_definition_container:
+ if global_definition_container.getId() in self._extruder_trains:
+ if str(self._active_extruder_index) in self._extruder_trains[global_definition_container.getId()]:
+ return self._extruder_trains[global_definition_container.getId()][str(self._active_extruder_index)]
+
## Adds all extruders of a specific machine definition to the extruder
# manager.
@@ -100,7 +102,7 @@ class ExtruderManager(QObject):
for extruder_train in extruder_trains:
self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
- ## Ensure that the extruder train stacks are linked to global stack.
+ #Ensure that the extruder train stacks are linked to global stack.
extruder_train.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
if extruder_trains:
@@ -180,7 +182,7 @@ class ExtruderManager(QObject):
quality = qualities[0]
preferred_quality_id = machine_definition.getMetaDataEntry("preferred_quality")
if preferred_quality_id:
- preferred_quality = container_registry.findInstanceContainers(id = preferred_quality_id.lower(), type = "quality")
+ preferred_quality = container_registry.findInstanceContainers(id = preferred_quality_id, type = "quality")
if len(preferred_quality) >= 1:
quality = preferred_quality[0]
else:
diff --git a/cura/ExtrudersModel.py b/cura/ExtrudersModel.py
index 960c05bd5e..3ba6c5a99a 100644
--- a/cura/ExtrudersModel.py
+++ b/cura/ExtrudersModel.py
@@ -28,9 +28,9 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
# containers.
IndexRole = Qt.UserRole + 4
- ## Colour to display if there is no material or the material has no known
+ ## List of colours to display if there is no material or the material has no known
# colour.
- defaultColour = "#FFFF00"
+ defaultColours = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
## Initialises the extruders model, defining the roles and listening for
# changes in the data.
@@ -75,7 +75,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
if self._add_global:
material = global_container_stack.findContainer({ "type": "material" })
- colour = material.getMetaDataEntry("color_code", default = self.defaultColour) if material else self.defaultColour
+ colour = material.getMetaDataEntry("color_code", default = self.defaultColours[0]) if material else self.defaultColours[0]
item = {
"id": global_container_stack.getId(),
"name": "Global",
@@ -86,12 +86,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
for extruder in manager.getMachineExtruders(global_container_stack.getBottom().getId()):
material = extruder.findContainer({ "type": "material" })
- colour = material.getMetaDataEntry("color_code", default = self.defaultColour) if material else self.defaultColour
position = extruder.getBottom().getMetaDataEntry("position", default = "0") #Position in the definition.
try:
position = int(position)
except ValueError: #Not a proper int.
position = -1
+ default_colour = self.defaultColours[position] if position >= 0 and position < len(self.defaultColours) else defaultColours[0]
+ colour = material.getMetaDataEntry("color_code", default = default_colour) if material else default_colour
item = { #Construct an item with only the relevant information.
"id": extruder.getId(),
"name": extruder.getName(),
diff --git a/cura/MachineManagerModel.py b/cura/MachineManagerModel.py
index 321f5af486..2960d48ed1 100644
--- a/cura/MachineManagerModel.py
+++ b/cura/MachineManagerModel.py
@@ -1,5 +1,5 @@
-
-import re
+# Copyright (c) 2016 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Application import Application
@@ -8,6 +8,8 @@ from UM.Preferences import Preferences
import UM.Settings
from UM.Settings.Validator import ValidatorState
from UM.Settings.InstanceContainer import InstanceContainer
+
+from cura.PrinterOutputDevice import PrinterOutputDevice
from UM.Settings.ContainerStack import ContainerStack
from . import ExtruderManager
from UM.i18n import i18nCatalog
@@ -49,6 +51,8 @@ class MachineManagerModel(QObject):
active_machine_id = Preferences.getInstance().getValue("cura/active_machine")
+ Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
+
if active_machine_id != "":
# An active machine was saved, so restore it.
self.setActiveMachine(active_machine_id)
@@ -61,16 +65,14 @@ class MachineManagerModel(QObject):
activeStackChanged = pyqtSignal()
globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed.
- globalValidationChanged = pyqtSignal() # Emitted whenever a validation inside global container is changed.
+ globalValidationChanged = pyqtSignal() # Emitted whenever a validation inside global container is changed
- @pyqtProperty("QVariantMap", notify = globalContainerChanged)
- def extrudersIds(self):
- ## Find all extruders that reference the new stack
- extruders = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(**{"machine": self._global_container_stack.getId()})
- result = {}
- for extruder in extruders:
- result[extruder.getMetaDataEntry("position")] = extruder.getId()
- return result
+ blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
+
+ outputDevicesChanged = pyqtSignal()
+
+ def _onOutputDevicesChanged(self):
+ self.outputDevicesChanged.emit()
def _onGlobalPropertyChanged(self, key, property_name):
if property_name == "value":
@@ -102,8 +104,15 @@ class MachineManagerModel(QObject):
self._global_stack_valid = not self._checkStackForErrors(self._global_container_stack)
def _onActiveExtruderStackChanged(self):
+ if self._active_container_stack and self._active_container_stack != self._global_container_stack:
+ self._active_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
+ self._active_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
+
self._active_container_stack = ExtruderManager.ExtruderManager.getInstance().getActiveExtruderStack()
- if not self._active_container_stack:
+ if self._active_container_stack:
+ self._active_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
+ self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
+ else:
self._active_container_stack = self._global_container_stack
def _onInstanceContainersChanged(self, container):
@@ -155,6 +164,10 @@ class MachineManagerModel(QObject):
Application.getInstance().setGlobalContainerStack(new_global_stack)
+ @pyqtProperty("QVariantList", notify = outputDevicesChanged)
+ def printerOutputDevices(self):
+ return [printer_output_device for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices() if isinstance(printer_output_device, PrinterOutputDevice)]
+
## Create a name that is not empty and unique
# \param container_type \type{string} Type of the container (machine, quality, ...)
# \param current_name \type{} Current name of the container, which may be an acceptable option
@@ -162,31 +175,7 @@ class MachineManagerModel(QObject):
# \param fallback_name \type{string} Name to use when (stripped) new_name is empty
# \return \type{string} Name that is unique for the specified type and name/id
def _createUniqueName(self, container_type, current_name, new_name, fallback_name):
- new_name = new_name.strip()
- num_check = re.compile("(.*?)\s*#\d+$").match(new_name)
- if num_check:
- new_name = num_check.group(1)
- if new_name == "":
- new_name = fallback_name
-
- unique_name = new_name
- i = 1
- # In case we are renaming, the current name of the container is also a valid end-result
- while self._containerExists(container_type, unique_name) and unique_name != current_name:
- i += 1
- unique_name = "%s #%d" % (new_name, i)
-
- return unique_name
-
- ## Check if a container with of a certain type and a certain name or id exists
- # Both the id and the name are checked, because they may not be the same and it is better if they are both unique
- # \param container_type \type{string} Type of the container (machine, quality, ...)
- # \param container_name \type{string} Name to check
- def _containerExists(self, container_type, container_name):
- container_class = ContainerStack if container_type == "machine" else InstanceContainer
-
- return UM.Settings.ContainerRegistry.getInstance().findContainers(container_class, id = container_name, type = container_type) or \
- UM.Settings.ContainerRegistry.getInstance().findContainers(container_class, name = container_name, type = container_type)
+ return UM.Settings.ContainerRegistry.getInstance().createUniqueName(container_type, current_name, new_name, fallback_name)
## Convenience function to check if a stack has errors.
def _checkStackForErrors(self, stack):
@@ -205,6 +194,7 @@ class MachineManagerModel(QObject):
if not self._active_container_stack:
return
+ self.blurSettings.emit()
user_settings = self._active_container_stack.getTop()
user_settings.clear()
@@ -292,6 +282,7 @@ class MachineManagerModel(QObject):
new_container_id = self.duplicateContainer(self.activeQualityId)
if new_container_id == "":
return
+ self.blurSettings.emit()
self.setActiveQuality(new_container_id)
self.updateQualityContainerFromUserContainer()
@@ -435,7 +426,7 @@ class MachineManagerModel(QObject):
@pyqtProperty(str, notify = globalContainerChanged)
def activeDefinitionId(self):
- if self._active_container_stack:
+ if self._global_container_stack:
definition = self._global_container_stack.getBottom()
if definition:
return definition.id
@@ -469,15 +460,15 @@ class MachineManagerModel(QObject):
@pyqtProperty(bool, notify = globalContainerChanged)
def hasMaterials(self):
- if self._active_container_stack:
- return bool(self._active_container_stack.getMetaDataEntry("has_materials", False))
+ if self._global_container_stack:
+ return bool(self._global_container_stack.getMetaDataEntry("has_materials", False))
return False
@pyqtProperty(bool, notify = globalContainerChanged)
def hasVariants(self):
- if self._active_container_stack:
- return bool(self._active_container_stack.getMetaDataEntry("has_variants", False))
+ if self._global_container_stack:
+ return bool(self._global_container_stack.getMetaDataEntry("has_variants", False))
return False
diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py
index 7025646294..7649106523 100644
--- a/cura/PrinterOutputDevice.py
+++ b/cura/PrinterOutputDevice.py
@@ -3,6 +3,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from enum import IntEnum # For the connection state tracking.
from UM.Logger import Logger
+from UM.Signal import signalemitter
## Printer output device adds extra interface options on top of output device.
#
@@ -13,7 +14,8 @@ from UM.Logger import Logger
# functions to actually have the implementation.
#
# For all other uses it should be used in the same way as a "regular" OutputDevice.
-class PrinterOutputDevice(OutputDevice, QObject):
+@signalemitter
+class PrinterOutputDevice(QObject, OutputDevice):
def __init__(self, device_id, parent = None):
super().__init__(device_id = device_id, parent = parent)
diff --git a/cura/SettingOverrideDecorator.py b/cura/SettingOverrideDecorator.py
index e3e3555016..24360ed992 100644
--- a/cura/SettingOverrideDecorator.py
+++ b/cura/SettingOverrideDecorator.py
@@ -51,8 +51,8 @@ class SettingOverrideDecorator(SceneNodeDecorator):
def getActiveExtruder(self):
return self._extruder_stack
- def _onSettingChanged(self, instance, property):
- if property == "value": # Only reslice if the value has changed.
+ def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function
+ if property_name == "value": # Only reslice if the value has changed.
Application.getInstance().getBackend().forceSlice()
## Makes sure that the stack upon which the container stack is placed is
@@ -78,4 +78,4 @@ class SettingOverrideDecorator(SceneNodeDecorator):
self.activeExtruderChanged.emit()
def getStack(self):
- return self._stack
\ No newline at end of file
+ return self._stack
diff --git a/cura_app.py b/cura_app.py
index 6b9f006d95..3548acedb6 100755
--- a/cura_app.py
+++ b/cura_app.py
@@ -35,6 +35,7 @@ sys.excepthook = exceptHook
import Arcus #@UnusedImport
from UM.Platform import Platform
import cura.CuraApplication
+import cura.CuraContainerRegistry
if Platform.isWindows() and hasattr(sys, "frozen"):
dirpath = os.path.expanduser("~/AppData/Local/cura/")
@@ -42,5 +43,8 @@ if Platform.isWindows() and hasattr(sys, "frozen"):
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w")
sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w")
+# Force an instance of CuraContainerRegistry to be created and reused later.
+cura.CuraContainerRegistry.CuraContainerRegistry.getInstance()
+
app = cura.CuraApplication.CuraApplication.getInstance()
app.run()
diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py
index f530091ba0..c91e414a13 100644
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -113,9 +113,6 @@ class CuraEngineBackend(Backend):
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
return [Preferences.getInstance().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, "-vv"]
- def close(self):
- self._terminate() # Forcefully shutdown the backend.
-
## Emitted when we get a message containing print duration and material amount. This also implies the slicing has finished.
# \param time The amount of time the print will take.
# \param material_amount The amount of material the print will use.
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
index 920196d447..aa4a749e92 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
@@ -61,7 +61,7 @@ Item {
}
else
{
- return extruders_model.getItem(extruderSelector.currentIndex).colour;
+ return UM.Theme.getColor("setting_control");
}
}
border.width: UM.Theme.getSize("default_lining").width
@@ -69,9 +69,22 @@ Item {
}
label: Item
{
+ Rectangle
+ {
+ id: swatch
+ height: UM.Theme.getSize("setting_control").height / 2
+ width: height
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("default_lining").width
+ anchors.verticalCenter: parent.verticalCenter
+
+ color: extruders_model.getItem(extruderSelector.currentIndex).colour
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : UM.Theme.getColor("setting_control_border")
+ }
Label
{
- anchors.left: parent.left
+ anchors.left: swatch.right
anchors.leftMargin: UM.Theme.getSize("default_lining").width
anchors.right: downArrow.left
anchors.rightMargin: UM.Theme.getSize("default_lining").width
@@ -79,7 +92,7 @@ Item {
text: extruderSelector.currentText
font: UM.Theme.getFont("default")
- color: UM.Theme.getColor("setting_control_disabled_text")
+ color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py
index cb7d1766d2..416d8cce6a 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py
@@ -24,19 +24,12 @@ class PerObjectSettingsTool(Tool):
return False
def getSelectedObjectId(self):
- try:
- selected_object = Selection.getSelectedObject(0)
- if selected_object.getParent().callDecoration("isGroup"):
- selected_object = selected_object.getParent()
- except:
- selected_object = None
+ selected_object = Selection.getSelectedObject(0)
selected_object_id = id(selected_object)
return selected_object_id
def getContainerID(self):
selected_object = Selection.getSelectedObject(0)
- if selected_object.getParent().callDecoration("isGroup"):
- selected_object = selected_object.getParent()
try:
return selected_object.callDecoration("getStack").getId()
except AttributeError:
diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py
index 68c4567450..58b75c2987 100644
--- a/plugins/USBPrinting/USBPrinterOutputDevice.py
+++ b/plugins/USBPrinting/USBPrinterOutputDevice.py
@@ -256,7 +256,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if not self.setBaudRate(baud_rate):
continue # Could not set the baud rate, go to the next
- time.sleep(1.5) # Ensure that we are not talking to the bootloader. 1.5 sec seems to be the magic number
+ time.sleep(1.5) # Ensure that we are not talking to the bootloader. 1.5 seconds seems to be the magic number
sucesfull_responses = 0
timeout_time = time.time() + 5
self._serial.write(b"\n")
diff --git a/resources/definitions/bq_hephestos.def.json b/resources/definitions/bq_hephestos.def.json
index 65d6fc38a3..4c90ae9ecd 100644
--- a/resources/definitions/bq_hephestos.def.json
+++ b/resources/definitions/bq_hephestos.def.json
@@ -10,18 +10,12 @@
"category": "Other",
"file_formats": "text/x-gcode",
"platform": "bq_hephestos_platform.stl",
- "platform_offset": {
- "value": [
- 0,
- -82,
- 0
- ]
- }
+ "platform_offset": [ 0, -82, 0]
},
"overrides": {
"machine_start_gcode": {
- "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 X0 Y0 ;move to the X/Y origin (Home)\nG28 Z0 ;move to the Z origin (Home)\nG1 Z15.0 F1200 ;move Z to position 15.0 mm\nG92 E0 ;zero the extruded length\nG1 E20 F200 ;extrude 20mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F7200 ;set feedrate to 120 mm/sec\n; -- end of START GCODE --"
+ "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 X0 Y0 ;move to the X/Y origin (Home)\nG28 Z0 ;move to the Z origin (Home)\nG1 Z15.0 F1200 ;move Z to position 15.0 mm\nG92 E0 ;zero the extruded length\nG1 E20 F200 ;extrude 20mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F7200 ;set feedrate to 120 mm/s\n; -- end of START GCODE --"
},
"machine_end_gcode": {
"default_value": "; -- END GCODE --\nM104 S0 ;set extruder temperature to zero (turned off)\nG91 ;set to relative positioning\nG1 E-20 F300 ;retract the filament a bit to release some of the pressure\nG1 Z10 ;move extruder up 10 mm\nG90 ;set to absolute positioning\nG1 X0 Y180 F1200 ;expose the platform\nM84 ;turn off steppers\n; -- end of END GCODE --"
diff --git a/resources/definitions/bq_hephestos_2.def.json b/resources/definitions/bq_hephestos_2.def.json
index 8afd4135f1..e4e58cb6c2 100644
--- a/resources/definitions/bq_hephestos_2.def.json
+++ b/resources/definitions/bq_hephestos_2.def.json
@@ -9,7 +9,7 @@
"manufacturer": "BQ",
"category": "Other",
"platform": "bq_hephestos_2_platform.stl",
- "platform_offset": { "value": [6, 1320, 0 ] },
+ "platform_offset": [6, 1320, 0 ],
"file_formats": "text/x-gcode"
},
diff --git a/resources/definitions/bq_hephestos_xl.def.json b/resources/definitions/bq_hephestos_xl.def.json
index 9cf5b685db..867f5f37aa 100644
--- a/resources/definitions/bq_hephestos_xl.def.json
+++ b/resources/definitions/bq_hephestos_xl.def.json
@@ -10,18 +10,12 @@
"category": "Other",
"file_formats": "text/x-code",
"platform": "bq_hephestos_platform.stl",
- "platform_offset": {
- "value": [
- 0,
- -82,
- 0
- ]
- }
+ "platform_offset": [ 0, -82, 0]
},
"overrides": {
"machine_start_gcode": {
- "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 X0 Y0 ;move to the X/Y origin (Home)\nG28 Z0 ;move to the Z origin (Home)\nG1 Z15.0 F1200 ;move Z to position 15.0 mm\nG92 E0 ;zero the extruded length\nG1 E20 F200 ;extrude 20mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F7200 ;set feedrate to 120 mm/sec\n; -- end of START GCODE --"
+ "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 X0 Y0 ;move to the X/Y origin (Home)\nG28 Z0 ;move to the Z origin (Home)\nG1 Z15.0 F1200 ;move Z to position 15.0 mm\nG92 E0 ;zero the extruded length\nG1 E20 F200 ;extrude 20mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F7200 ;set feedrate to 120 mm/s\n; -- end of START GCODE --"
},
"machine_end_gcode": {
"default_value": "; -- END GCODE --\nM104 S0 ;set extruder temperature to zero (turned off)\nG91 ;set to relative positioning\nG1 E-20 F300 ;retract the filament a bit to release some of the pressure\nG1 Z10 ;move extruder up 10 mm\nG90 ;set to absolute positioning\nG1 X0 Y180 F1200 ;expose the platform\nM84 ;turn off steppers\n; -- end of END GCODE --"
diff --git a/resources/definitions/bq_witbox.def.json b/resources/definitions/bq_witbox.def.json
index 77826a1576..b8d4cbf015 100644
--- a/resources/definitions/bq_witbox.def.json
+++ b/resources/definitions/bq_witbox.def.json
@@ -10,18 +10,12 @@
"category": "Other",
"file_formats": "text/x-gcode",
"platform": "bq_witbox_platform.stl",
- "platform_offset": {
- "value": [
- 0,
- -145,
- -38
- ]
- }
+ "platform_offset": [ 0, -145, -38]
},
"overrides": {
"machine_start_gcode": {
- "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 X0 Y0 ;move to the X/Y origin (Home)\nG28 Z0 ;move to the Z origin (Home)\nG1 Z15.0 F1200 ;move Z to position 15.0 mm\nG92 E0 ;zero the extruded length\nG1 E20 F200 ;extrude 20mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F7200 ;set feedrate to 120 mm/sec\n; -- end of START GCODE --"
+ "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 X0 Y0 ;move to the X/Y origin (Home)\nG28 Z0 ;move to the Z origin (Home)\nG1 Z15.0 F1200 ;move Z to position 15.0 mm\nG92 E0 ;zero the extruded length\nG1 E20 F200 ;extrude 20mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F7200 ;set feedrate to 120 mm/s\n; -- end of START GCODE --"
},
"machine_end_gcode": {
"default_value": "; -- END GCODE --\nM104 S0 ;set extruder temperature to zero (turned off)\nG91 ;set to relative positioning\nG1 E-20 F300 ;retract the filament a bit to release some of the pressure\nG90 ;set to absolute positioning\nG1 Z200 ;move the platform to the bottom\nG28 X0 Y0 ;move to the X/Y origin (Home)\nM84 ;turn off steppers\n; -- end of END GCODE --"
diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json
index 940b0bb3ed..06a736330c 100644
--- a/resources/definitions/fdmextruder.def.json
+++ b/resources/definitions/fdmextruder.def.json
@@ -23,8 +23,7 @@
"label": "Extruder",
"description": "The extruder train used for printing. This is used in multi-extrusion.",
"type": "extruder",
- "default_value": 0,
- "minimum_value": "0",
+ "default_value": "0",
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_meshgroup": false,
diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json
index 6e6474053f..c5acbd49bb 100644
--- a/resources/definitions/fdmprinter.def.json
+++ b/resources/definitions/fdmprinter.def.json
@@ -1802,7 +1802,7 @@
{
"label": "Regular/Maximum Fan Speed Threshold",
"description": "The layer time which sets the threshold between regular fan speed and maximum fan speed. Layers that print slower than this time use regular fan speed. For faster layers the fan speed gradually increases towards the maximum fan speed.",
- "unit": "sec",
+ "unit": "s",
"type": "float",
"default_value": 10,
"minimum_value": "cool_min_layer_time",
@@ -1842,7 +1842,7 @@
{
"label": "Minimum Layer Time",
"description": "The minimum time spent in a layer. This forces the printer to slow down, to at least spend the time set here in one layer. This allows the printed material to cool down properly before printing the next layer.",
- "unit": "sec",
+ "unit": "s",
"type": "float",
"default_value": 5,
"minimum_value": "0",
@@ -2751,6 +2751,7 @@
"value": "support_extruder_nr",
"minimum_value": "0",
"maximum_value": "machine_extruder_count - 1",
+ "enabled": "support_enable",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -2763,6 +2764,7 @@
"value": "support_extruder_nr",
"minimum_value": "0",
"maximum_value": "machine_extruder_count - 1",
+ "enabled": "support_enable",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -2775,6 +2777,7 @@
"value": "support_extruder_nr",
"minimum_value": "0",
"maximum_value": "machine_extruder_count - 1",
+ "enabled": "support_enable",
"settable_per_mesh": false,
"settable_per_extruder": false
}
@@ -3392,7 +3395,7 @@
{
"label": "WP Top Delay",
"description": "Delay time after an upward move, so that the upward line can harden. Only applies to Wire Printing.",
- "unit": "sec",
+ "unit": "s",
"type": "float",
"default_value": 0,
"minimum_value": "0",
@@ -3406,7 +3409,7 @@
{
"label": "WP Bottom Delay",
"description": "Delay time after a downward move. Only applies to Wire Printing.",
- "unit": "sec",
+ "unit": "s",
"type": "float",
"default_value": 0,
"minimum_value": "0",
@@ -3420,7 +3423,7 @@
{
"label": "WP Flat Delay",
"description": "Delay time between two horizontal segments. Introducing such a delay can cause better adhesion to previous layers at the connection points, while too long delays cause sagging. Only applies to Wire Printing.",
- "unit": "sec",
+ "unit": "s",
"type": "float",
"default_value": 0.1,
"minimum_value": "0",
@@ -3550,7 +3553,7 @@
"label": "WP Roof Outer Delay",
"description": "Time spent at the outer perimeters of hole which is to become a roof. Longer times can ensure a better connection. Only applies to Wire Printing.",
"type": "float",
- "unit": "sec",
+ "unit": "s",
"default_value": 0.2,
"minimum_value": "0",
"maximum_value_warning": "2.0",
diff --git a/resources/qml/AddMachineDialog.qml b/resources/qml/AddMachineDialog.qml
index 4a5e14f183..9d2d1a24ff 100644
--- a/resources/qml/AddMachineDialog.qml
+++ b/resources/qml/AddMachineDialog.qml
@@ -154,7 +154,7 @@ UM.Dialog
Button
{
- text:"save"
+ text: catalog.i18nc("@action:button", "Add Printer")
anchors.bottom: parent.bottom
anchors.right: parent.right
onClicked:
diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml
index c3dc95b454..6dd57e17c8 100644
--- a/resources/qml/Cura.qml
+++ b/resources/qml/Cura.qml
@@ -545,16 +545,13 @@ UM.MainWindow
{
//; Remove & re-add the general page as we want to use our own instead of uranium standard.
removePage(0);
- insertPage(0, catalog.i18nc("@title:tab","General"), Qt.resolvedUrl("GeneralPage.qml"));
+ insertPage(0, catalog.i18nc("@title:tab","General"), Qt.resolvedUrl("Preferences/GeneralPage.qml"));
- //: View preferences page title
- insertPage(1, catalog.i18nc("@title:tab","View"), Qt.resolvedUrl("ViewPage.qml"));
+ insertPage(2, catalog.i18nc("@title:tab", "Printers"), Qt.resolvedUrl("Preferences/MachinesPage.qml"));
- insertPage(3, catalog.i18nc("@title:tab", "Printers"), Qt.resolvedUrl("MachinesPage.qml"));
+ insertPage(3, catalog.i18nc("@title:tab", "Materials"), Qt.resolvedUrl("Preferences/MaterialsPage.qml"));
- insertPage(4, catalog.i18nc("@title:tab", "Materials"), Qt.resolvedUrl("Preferences/MaterialsPage.qml"));
-
- insertPage(5, catalog.i18nc("@title:tab", "Profiles"), Qt.resolvedUrl("Preferences/ProfilesPage.qml"));
+ insertPage(4, catalog.i18nc("@title:tab", "Profiles"), Qt.resolvedUrl("Preferences/ProfilesPage.qml"));
//Force refresh
setPage(0);
@@ -583,7 +580,7 @@ UM.MainWindow
onTriggered:
{
Cura.MachineManager.newQualityContainerFromQualityAndUser();
- preferences.setPage(5);
+ preferences.setPage(4);
preferences.show();
// Show the renameDialog after a very short delay so the preference page has time to initiate
@@ -597,7 +594,7 @@ UM.MainWindow
onTriggered:
{
preferences.visible = true;
- preferences.setPage(3);
+ preferences.setPage(2);
}
}
@@ -607,7 +604,7 @@ UM.MainWindow
onTriggered:
{
preferences.visible = true;
- preferences.setPage(5);
+ preferences.setPage(4);
}
}
@@ -617,7 +614,7 @@ UM.MainWindow
onTriggered:
{
preferences.visible = true;
- preferences.setPage(2);
+ preferences.setPage(1);
preferences.getCurrentItem().scrollToSection(source.key);
}
}
diff --git a/resources/qml/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml
similarity index 66%
rename from resources/qml/GeneralPage.qml
rename to resources/qml/Preferences/GeneralPage.qml
index 0ae783916b..2886f94d92 100644
--- a/resources/qml/GeneralPage.qml
+++ b/resources/qml/Preferences/GeneralPage.qml
@@ -39,6 +39,12 @@ UM.PreferencesPage
scaleTinyCheckbox.checked = boolCheck(UM.Preferences.getValue("mesh/scale_tiny_meshes"))
UM.Preferences.resetPreference("cura/jobname_prefix")
prefixJobNameCheckbox.checked = boolCheck(UM.Preferences.getValue("cura/jobname_prefix"))
+ UM.Preferences.resetPreference("view/show_overhang");
+ showOverhangCheckbox.checked = boolCheck(UM.Preferences.getValue("view/show_overhang"))
+ UM.Preferences.resetPreference("view/center_on_select");
+ centerOnSelectCheckbox.checked = boolCheck(UM.Preferences.getValue("view/center_on_select"))
+ UM.Preferences.resetPreference("view/top_layer_count");
+ topLayerCountCheckbox.checked = boolCheck(UM.Preferences.getValue("view/top_layer_count"))
if (plugins.model.find("id", "SliceInfoPlugin") > -1) {
UM.Preferences.resetPreference("info/send_slice_info")
@@ -50,17 +56,24 @@ UM.PreferencesPage
}
}
- ColumnLayout
+ Column
{
//: Language selection label
UM.I18nCatalog{id: catalog; name:"cura"}
- RowLayout
+ Label
+ {
+ font.bold: true
+ text: catalog.i18nc("@label","Interface")
+ }
+
+ Row
{
Label
{
id: languageLabel
text: catalog.i18nc("@label","Language:")
+ anchors.verticalCenter: languageComboBox.verticalCenter
}
ComboBox
@@ -120,6 +133,51 @@ UM.PreferencesPage
font.italic: true
}
+ Item
+ {
+ //: Spacer
+ height: UM.Theme.getSize("default_margin").height
+ width: UM.Theme.getSize("default_margin").width
+ }
+
+ Label
+ {
+ font.bold: true
+ text: catalog.i18nc("@label","Viewport behavior")
+ }
+
+ UM.TooltipArea
+ {
+ width: childrenRect.width;
+ height: childrenRect.height;
+
+ text: catalog.i18nc("@info:tooltip","Highlight unsupported areas of the model in red. Without support these areas will not print properly.")
+
+ CheckBox
+ {
+ id: showOverhangCheckbox
+
+ checked: boolCheck(UM.Preferences.getValue("view/show_overhang"))
+ onClicked: UM.Preferences.setValue("view/show_overhang", checked)
+
+ text: catalog.i18nc("@option:check","Display overhang");
+ }
+ }
+
+ UM.TooltipArea {
+ width: childrenRect.width;
+ height: childrenRect.height;
+ text: catalog.i18nc("@info:tooltip","Moves the camera so the object is in the center of the view when an object is selected")
+
+ CheckBox
+ {
+ id: centerOnSelectCheckbox
+ text: catalog.i18nc("@action:button","Center camera when item is selected");
+ checked: boolCheck(UM.Preferences.getValue("view/center_on_select"))
+ onClicked: UM.Preferences.setValue("view/center_on_select", checked)
+ }
+ }
+
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
@@ -134,15 +192,52 @@ UM.PreferencesPage
}
}
+ UM.TooltipArea {
+ width: childrenRect.width;
+ height: childrenRect.height;
+ text: catalog.i18nc("@info:tooltip","Display 5 top layers in layer view or only the top-most layer. Rendering 5 layers takes longer, but may show more information.")
+
+ CheckBox
+ {
+ id: topLayerCountCheckbox
+ text: catalog.i18nc("@action:button","Display five top layers in layer view");
+ checked: UM.Preferences.getValue("view/top_layer_count") == 5
+ onClicked:
+ {
+ if(UM.Preferences.getValue("view/top_layer_count") == 5)
+ {
+ UM.Preferences.setValue("view/top_layer_count", 1)
+ }
+ else
+ {
+ UM.Preferences.setValue("view/top_layer_count", 5)
+ }
+ }
+ }
+ }
+
+ Item
+ {
+ //: Spacer
+ height: UM.Theme.getSize("default_margin").height
+ width: UM.Theme.getSize("default_margin").height
+ }
+
+ Label
+ {
+ font.bold: true
+ text: catalog.i18nc("@label","Opening files")
+ }
+
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
- text: catalog.i18nc("@info:tooltip","Should opened files be scaled to the build volume if they are too large?")
+ text: catalog.i18nc("@info:tooltip","Should objects be scaled to the build volume if they are too large?")
CheckBox
{
id: scaleToFitCheckbox
- text: catalog.i18nc("@option:check","Scale large files")
+ text: catalog.i18nc("@option:check","Scale large objects")
checked: boolCheck(UM.Preferences.getValue("mesh/scale_to_fit"))
onCheckedChanged: UM.Preferences.setValue("mesh/scale_to_fit", checked)
}
@@ -151,17 +246,45 @@ UM.PreferencesPage
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
- text: catalog.i18nc("@info:tooltip","Should opened files be scaled up if they are extremely small?")
+ text: catalog.i18nc("@info:tooltip","Should objects be scaled up if they are extremely small?")
CheckBox
{
id: scaleTinyCheckbox
- text: catalog.i18nc("@option:check","Scale extremely small files")
+ text: catalog.i18nc("@option:check","Scale extremely small objects")
checked: boolCheck(UM.Preferences.getValue("mesh/scale_tiny_meshes"))
onCheckedChanged: UM.Preferences.setValue("mesh/scale_tiny_meshes", checked)
}
}
+ UM.TooltipArea {
+ width: childrenRect.width
+ height: childrenRect.height
+ text: catalog.i18nc("@info:tooltip", "Should a prefix based on the printer name be added to the print job name automatically?")
+
+ CheckBox
+ {
+ id: prefixJobNameCheckbox
+ text: catalog.i18nc("@option:check", "Add machine prefix to job name")
+ checked: boolCheck(UM.Preferences.getValue("cura/jobname_prefix"))
+ onCheckedChanged: UM.Preferences.setValue("cura/jobname_prefix", checked)
+ }
+ }
+
+ Item
+ {
+ //: Spacer
+ height: UM.Theme.getSize("default_margin").height
+ width: UM.Theme.getSize("default_margin").height
+ }
+
+ Label
+ {
+ font.bold: true
+ visible: checkUpdatesCheckbox.visible || sendDataCheckbox.visible
+ text: catalog.i18nc("@label","Privacy")
+ }
+
UM.TooltipArea {
visible: plugins.model.find("id", "UpdateChecker") > -1
width: childrenRect.width
@@ -192,20 +315,7 @@ UM.PreferencesPage
}
}
- UM.TooltipArea {
- width: childrenRect.width
- height: childrenRect.height
- text: catalog.i18nc("@info:tooltip", "Should a prefix based on the printer name be added to the print job name automatically?")
-
- CheckBox
- {
- id: prefixJobNameCheckbox
- text: catalog.i18nc("@option:check", "Add machine prefix to job name")
- checked: boolCheck(UM.Preferences.getValue("cura/jobname_prefix"))
- onCheckedChanged: UM.Preferences.setValue("cura/jobname_prefix", checked)
- }
- }
-
+ //: Invisible list used to check if a plugin exists
ListView
{
id: plugins
diff --git a/resources/qml/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml
similarity index 100%
rename from resources/qml/MachinesPage.qml
rename to resources/qml/Preferences/MachinesPage.qml
diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml
index 670bf79956..1f90d7c889 100644
--- a/resources/qml/Preferences/ProfilesPage.qml
+++ b/resources/qml/Preferences/ProfilesPage.qml
@@ -39,7 +39,6 @@ UM.ManagementPage
section.property: "readOnly"
section.delegate: Rectangle
{
- width: objectListContainer.viewport.width;
height: childrenRect.height;
Label
@@ -265,7 +264,7 @@ UM.ManagementPage
id: importDialog;
title: catalog.i18nc("@title:window", "Import Profile");
selectExisting: true;
- nameFilters: base.model.getFileNameFiltersRead()
+ nameFilters: base.model.getFileNameFilters("profile_reader")
folder: base.model.getDefaultPath()
onAccepted:
{
@@ -292,7 +291,7 @@ UM.ManagementPage
id: exportDialog;
title: catalog.i18nc("@title:window", "Export Profile");
selectExisting: false;
- nameFilters: base.model.getFileNameFiltersWrite()
+ nameFilters: base.model.getFileNameFilters("profile_writer")
folder: base.model.getDefaultPath()
onAccepted:
{
diff --git a/resources/qml/Settings/SettingCategory.qml b/resources/qml/Settings/SettingCategory.qml
index 6e23158a1c..83bd7f1f82 100644
--- a/resources/qml/Settings/SettingCategory.qml
+++ b/resources/qml/Settings/SettingCategory.qml
@@ -24,9 +24,10 @@ Button {
checkable: true
checked: definition.expanded
- onClicked: definition.expanded ? settingDefinitionsModel.collapse(definition.key) : settingDefinitionsModel.expandAll(definition.key)
+ onClicked: { forceActiveFocus(); definition.expanded ? settingDefinitionsModel.collapse(definition.key) : settingDefinitionsModel.expandAll(definition.key) }
- UM.SimpleButton {
+ UM.SimpleButton
+ {
id: settingsButton
visible: base.hovered || settingsButton.hovered
@@ -60,7 +61,8 @@ Button {
height: parent.height / 2
width: height
- onClicked: {
+ onClicked:
+ {
base.showAllHiddenInheritedSettings()
}
@@ -68,11 +70,13 @@ Button {
hoverColor: UM.Theme.getColor("setting_control_button_hover")
iconSource: UM.Theme.getIcon("notice")
- onEntered: {
+ 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: {
+ onExited:
+ {
base.hideTooltip();
}
diff --git a/resources/qml/Settings/SettingCheckBox.qml b/resources/qml/Settings/SettingCheckBox.qml
index 1d66e509d0..9b50c82395 100644
--- a/resources/qml/Settings/SettingCheckBox.qml
+++ b/resources/qml/Settings/SettingCheckBox.qml
@@ -31,7 +31,7 @@ SettingItem
}
}
- onClicked: propertyProvider.setPropertyValue("value", !checked)
+ onClicked: { forceActiveFocus(); propertyProvider.setPropertyValue("value", !checked) }
Rectangle
{
diff --git a/resources/qml/Settings/SettingComboBox.qml b/resources/qml/Settings/SettingComboBox.qml
index c9f3cb727b..5308e45f5a 100644
--- a/resources/qml/Settings/SettingComboBox.qml
+++ b/resources/qml/Settings/SettingComboBox.qml
@@ -86,7 +86,7 @@ SettingItem
}
}
- onActivated: provider.setPropertyValue("value", definition.options[index].key)
+ onActivated: { forceActiveFocus(); provider.setPropertyValue("value", definition.options[index].key) }
onModelChanged: updateCurrentIndex();
Connections
diff --git a/resources/qml/Settings/SettingExtruder.qml b/resources/qml/Settings/SettingExtruder.qml
index 0160dab7fa..72c5299b15 100644
--- a/resources/qml/Settings/SettingExtruder.qml
+++ b/resources/qml/Settings/SettingExtruder.qml
@@ -47,7 +47,7 @@ SettingItem
}
else
{
- return extruders_model.getItem(index).colour;
+ return UM.Theme.getColor("setting_control");
}
}
border.width: UM.Theme.getSize("default_lining").width
@@ -55,9 +55,22 @@ SettingItem
}
label: Item
{
+ Rectangle
+ {
+ id: swatch
+ height: UM.Theme.getSize("setting_control").height / 2
+ width: height
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("default_lining").width
+ anchors.verticalCenter: parent.verticalCenter
+
+ color: extruders_model.getItem(control.currentIndex).colour
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : UM.Theme.getColor("setting_control_border")
+ }
Label
{
- anchors.left: parent.left
+ anchors.left: swatch.right
anchors.leftMargin: UM.Theme.getSize("default_lining").width
anchors.right: downArrow.left
anchors.rightMargin: UM.Theme.getSize("default_lining").width
@@ -65,7 +78,7 @@ SettingItem
text: control.currentText
font: UM.Theme.getFont("default")
- color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : extruders_model.getItem(index).colour
+ color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
@@ -89,7 +102,11 @@ SettingItem
}
}
- onActivated: provider.setPropertyValue("value", extruders_model.getItem(index).index);
+ onActivated:
+ {
+ forceActiveFocus();
+ provider.setPropertyValue("value", extruders_model.getItem(index).index)
+ }
onModelChanged: updateCurrentIndex();
Connections
diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml
index 4fd84759fd..e98c06329d 100644
--- a/resources/qml/Settings/SettingItem.qml
+++ b/resources/qml/Settings/SettingItem.qml
@@ -7,6 +7,7 @@ import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.1 as UM
+import Cura 1.0 as Cura
import "."
@@ -245,5 +246,14 @@ Item {
}
}
+ Connections
+ {
+ target: Cura.MachineManager
+ onBlurSettings:
+ {
+ revertButton.focus = true
+ }
+ }
+
UM.I18nCatalog { id: catalog; name: "cura" }
}
diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml
index b7ff0743de..4c8d671a7f 100644
--- a/resources/qml/Sidebar.qml
+++ b/resources/qml/Sidebar.qml
@@ -7,6 +7,7 @@ import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import UM 1.1 as UM
+import Cura 1.0 as Cura
Rectangle
{
@@ -14,6 +15,9 @@ Rectangle
property int currentModeIndex;
+ // Is there an output device for this printer?
+ property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
+
color: UM.Theme.getColor("sidebar");
UM.I18nCatalog { id: catalog; name:"cura"}
diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml
index 282068ee2c..55a104a21f 100644
--- a/resources/qml/SidebarHeader.qml
+++ b/resources/qml/SidebarHeader.qml
@@ -141,15 +141,35 @@ Column
control.hovered ? UM.Theme.getColor("toggle_hovered") : UM.Theme.getColor("toggle_unchecked")
Behavior on color { ColorAnimation { duration: 50; } }
+ Rectangle
+ {
+ id: swatch
+ height: UM.Theme.getSize("setting_control").height / 2
+ width: height
+ anchors.left: parent.left
+ anchors.leftMargin: (parent.height - height) / 2
+ anchors.verticalCenter: parent.verticalCenter
+
+ color: model.colour
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: UM.Theme.getColor("toggle_checked")
+ }
+
Label
{
- anchors.centerIn: parent
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: swatch.right
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width / 2
+ anchors.right: parent.right
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width / 2
+
color: control.checked ? UM.Theme.getColor("toggle_checked_text") :
control.pressed ? UM.Theme.getColor("toggle_active_text") :
control.hovered ? UM.Theme.getColor("toggle_hovered_text") : UM.Theme.getColor("toggle_unchecked_text")
font: UM.Theme.getFont("default")
- text: control.text;
+ text: control.text
+ elide: Text.ElideRight
}
}
label: Item { }
diff --git a/resources/qml/ViewPage.qml b/resources/qml/ViewPage.qml
deleted file mode 100644
index baf56db116..0000000000
--- a/resources/qml/ViewPage.qml
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2015 Ultimaker B.V.
-// Cura is released under the terms of the AGPLv3 or higher.
-
-import QtQuick 2.1
-import QtQuick.Controls 1.1
-import QtQuick.Layouts 1.1
-import QtQuick.Controls.Styles 1.1
-
-import UM 1.1 as UM
-
-UM.PreferencesPage
-{
- id: preferencesPage
-
- //: View configuration page title
- title: catalog.i18nc("@title:window","View");
-
- function reset()
- {
- UM.Preferences.resetPreference("view/show_overhang");
- UM.Preferences.resetPreference("view/center_on_select");
- UM.Preferences.resetPreference("view/top_layer_count");
- }
-
- Column
- {
- UM.I18nCatalog { id: catalog; name:"cura"}
-
- UM.TooltipArea
- {
- width: childrenRect.width;
- height: childrenRect.height;
-
- text: catalog.i18nc("@info:tooltip","Highlight unsupported areas of the model in red. Without support these areas will not print properly.")
-
- CheckBox
- {
- id: overhangCheckbox
-
- checked: boolCheck(UM.Preferences.getValue("view/show_overhang"))
- onClicked: UM.Preferences.setValue("view/show_overhang", checked)
-
- text: catalog.i18nc("@option:check","Display overhang");
- }
- }
-
- UM.TooltipArea {
- width: childrenRect.width;
- height: childrenRect.height;
- text: catalog.i18nc("@info:tooltip","Moves the camera so the object is in the center of the view when an object is selected")
-
- CheckBox
- {
- id: centerCheckbox
- text: catalog.i18nc("@action:button","Center camera when item is selected");
- checked: boolCheck(UM.Preferences.getValue("view/center_on_select"))
- onClicked: UM.Preferences.setValue("view/center_on_select", checked)
- }
- }
-
- UM.TooltipArea {
- width: childrenRect.width;
- height: childrenRect.height;
- text: catalog.i18nc("@info:tooltip","Display 5 top layers in layer view or only the top-most layer. Rendering 5 layers takes longer, but may show more information.")
-
- CheckBox
- {
- id: topLayerCheckbox
- text: catalog.i18nc("@action:button","Display five top layers in layer view.");
- checked: UM.Preferences.getValue("view/top_layer_count") == 5
- onClicked:
- {
- if(UM.Preferences.getValue("view/top_layer_count") == 5)
- {
- UM.Preferences.setValue("view/top_layer_count", 1)
- }
- else
- {
- UM.Preferences.setValue("view/top_layer_count", 5)
- }
- }
- }
- }
-
- Connections {
- target: UM.Preferences
- onPreferenceChanged:
- {
- overhangCheckbox.checked = boolCheck(UM.Preferences.getValue("view/show_overhang"))
- centerCheckbox.checked = boolCheck(UM.Preferences.getValue("view/center_on_select"))
- topLayerCheckbox = UM.Preferences.getValue("view/top_layer_count") == 5
-
- }
- }
- }
-}
diff --git a/resources/shaders/overhang.shader b/resources/shaders/overhang.shader
index 4ae2821c7d..99cbdf913d 100644
--- a/resources/shaders/overhang.shader
+++ b/resources/shaders/overhang.shader
@@ -74,6 +74,7 @@ u_viewProjectionMatrix = view_projection_matrix
u_normalMatrix = normal_matrix
u_viewPosition = view_position
u_lightPosition = light_0_position
+u_diffuseColor = diffuse_color
[attributes]
a_vertex = vertex