diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py
index 88ced6f68e..e69393edf0 100644
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -32,8 +32,6 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
-from . import ExtruderManager
-from . import ExtrudersModel
from . import PlatformPhysics
from . import BuildVolume
from . import CameraAnimation
@@ -42,11 +40,11 @@ from . import CuraActions
from . import MultiMaterialDecorator
from . import ZOffsetDecorator
from . import CuraSplashScreen
-from . import MachineManagerModel
-from . import ContainerSettingsModel
from . import CameraImageProvider
from . import MachineActionManager
+import cura.Settings
+
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
@@ -273,7 +271,8 @@ class CuraApplication(QtApplication):
Logger.logException("e", "An exception occurred when serializing container %s", instance.getId())
continue
- file_name = urllib.parse.quote_plus(instance.getId()) + ".inst.cfg"
+ mime_type = ContainerRegistry.getMimeTypeForContainer(type(instance))
+ file_name = urllib.parse.quote_plus(instance.getId()) + "." + mime_type.preferredSuffix
instance_type = instance.getMetaDataEntry("type")
path = None
if instance_type == "material":
@@ -301,7 +300,8 @@ class CuraApplication(QtApplication):
Logger.logException("e", "An exception occurred when serializing container %s", instance.getId())
continue
- file_name = urllib.parse.quote_plus(stack.getId()) + ".stack.cfg"
+ mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
+ file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix
stack_type = stack.getMetaDataEntry("type", None)
path = None
if not stack_type or stack_type == "machine":
@@ -378,9 +378,9 @@ class CuraApplication(QtApplication):
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
# Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
- ExtruderManager.ExtruderManager.getInstance()
- qmlRegisterSingletonType(MachineManagerModel.MachineManagerModel, "Cura", 1, 0, "MachineManager",
- MachineManagerModel.createMachineManagerModel)
+ cura.Settings.ExtruderManager.getInstance()
+ qmlRegisterSingletonType(cura.Settings.MachineManager, "Cura", 1, 0, "MachineManager",
+ cura.Settings.MachineManager.createMachineManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
@@ -424,6 +424,7 @@ class CuraApplication(QtApplication):
# \param engine The QML engine.
def registerObjects(self, engine):
engine.rootContext().setContextProperty("Printer", self)
+ engine.rootContext().setContextProperty("CuraApplication", self)
self._print_information = PrintInformation.PrintInformation()
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
self._cura_actions = CuraActions.CuraActions(self)
@@ -431,13 +432,16 @@ class CuraApplication(QtApplication):
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
- qmlRegisterType(ExtrudersModel.ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
+ qmlRegisterType(cura.Settings.ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
- qmlRegisterType(ContainerSettingsModel.ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
+ qmlRegisterType(cura.Settings.ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
+ qmlRegisterType(cura.Settings.MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
+
+ qmlRegisterSingletonType(cura.Settings.ContainerManager, "Cura", 1, 0, "ContainerManager", cura.Settings.ContainerManager.createContainerManager)
qmlRegisterSingletonType(QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")), "Cura", 1, 0, "Actions")
- engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.ExtruderManager.getInstance())
+ engine.rootContext().setContextProperty("ExtruderManager", cura.Settings.ExtruderManager.getInstance())
for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles):
type_name = os.path.splitext(os.path.basename(path))[0]
diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py
new file mode 100644
index 0000000000..eb9dab0ad7
--- /dev/null
+++ b/cura/Settings/ContainerManager.py
@@ -0,0 +1,383 @@
+# Copyright (c) 2016 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+import os.path
+import urllib
+
+from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal, QUrl
+from PyQt5.QtWidgets import QMessageBox
+
+import UM.PluginRegistry
+import UM.Settings
+import UM.SaveFile
+import UM.Platform
+import UM.MimeTypeDatabase
+import UM.Logger
+
+from UM.MimeTypeDatabase import MimeTypeNotFoundError
+
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+
+## Manager class that contains common actions to deal with containers in Cura.
+#
+# This is primarily intended as a class to be able to perform certain actions
+# from within QML. We want to be able to trigger things like removing a container
+# when a certain action happens. This can be done through this class.
+class ContainerManager(QObject):
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self._registry = UM.Settings.ContainerRegistry.getInstance()
+ self._container_name_filters = {}
+
+ ## Create a duplicate of the specified container
+ #
+ # This will create and add a duplicate of the container corresponding
+ # to the container ID.
+ #
+ # \param container_id \type{str} The ID of the container to duplicate.
+ #
+ # \return The ID of the new container, or an empty string if duplication failed.
+ @pyqtSlot(str, result = str)
+ def duplicateContainer(self, container_id):
+ containers = self._registry.findContainers(None, id = container_id)
+ if not containers:
+ UM.Logger.log("w", "Could duplicate container %s because it was not found.", container_id)
+ return ""
+
+ container = containers[0]
+
+ new_container = None
+ new_name = self._registry.uniqueName(container.getName())
+ # Only InstanceContainer has a duplicate method at the moment.
+ # So fall back to serialize/deserialize when no duplicate method exists.
+ if hasattr(container, "duplicate"):
+ new_container = container.duplicate(new_name)
+ else:
+ new_container = container.__class__(new_name)
+ new_container.deserialize(container.serialize())
+ new_container.setName(new_name)
+
+ if new_container:
+ self._registry.addContainer(new_container)
+
+ return new_container.getId()
+
+ ## Change the name of a specified container to a new name.
+ #
+ # \param container_id \type{str} The ID of the container to change the name of.
+ # \param new_id \type{str} The new ID of the container.
+ # \param new_name \type{str} The new name of the specified container.
+ #
+ # \return True if successful, False if not.
+ @pyqtSlot(str, str, str, result = bool)
+ def renameContainer(self, container_id, new_id, new_name):
+ containers = self._registry.findContainers(None, id = container_id)
+ if not containers:
+ UM.Logger.log("w", "Could rename container %s because it was not found.", container_id)
+ return False
+
+ container = containers[0]
+ # First, remove the container from the registry. This will clean up any files related to the container.
+ self._registry.removeContainer(container)
+
+ # Ensure we have a unique name for the container
+ new_name = self._registry.uniqueName(new_name)
+
+ # Then, update the name and ID of the container
+ container.setName(new_name)
+ container._id = new_id # TODO: Find a nicer way to set a new, unique ID
+
+ # Finally, re-add the container so it will be properly serialized again.
+ self._registry.addContainer(container)
+
+ return True
+
+ ## Remove the specified container.
+ #
+ # \param container_id \type{str} The ID of the container to remove.
+ #
+ # \return True if the container was successfully removed, False if not.
+ @pyqtSlot(str, result = bool)
+ def removeContainer(self, container_id):
+ containers = self._registry.findContainers(None, id = container_id)
+ if not containers:
+ UM.Logger.log("w", "Could remove container %s because it was not found.", container_id)
+ return False
+
+ self._registry.removeContainer(containers[0].getId())
+
+ return True
+
+ ## Merge a container with another.
+ #
+ # This will try to merge one container into the other, by going through the container
+ # and setting the right properties on the other container.
+ #
+ # \param merge_into_id \type{str} The ID of the container to merge into.
+ # \param merge_id \type{str} The ID of the container to merge.
+ #
+ # \return True if successfully merged, False if not.
+ @pyqtSlot(str, result = bool)
+ def mergeContainers(self, merge_into_id, merge_id):
+ containers = self._registry.findContainers(None, id = merge_into_id)
+ if not containers:
+ UM.Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id)
+ return False
+
+ merge_into = containers[0]
+
+ containers = self._registry.findContainers(None, id = merge_id)
+ if not containers:
+ UM.Logger.log("w", "Could not merge container %s because it was not found", merge_id)
+ return False
+
+ merge = containers[0]
+
+ if type(merge) != type(merge_into):
+ UM.Logger.log("w", "Cannot merge two containers of different types")
+ return False
+
+ for key in merge.getAllKeys():
+ merge_into.setProperty(key, "value", merge.getProperty(key, "value"))
+
+ return True
+
+ ## Clear the contents of a container.
+ #
+ # \param container_id \type{str} The ID of the container to clear.
+ #
+ # \return True if successful, False if not.
+ @pyqtSlot(str, result = bool)
+ def clearContainer(self, container_id):
+ containers = self._registry.findContainers(None, id = container_id)
+ if not containers:
+ UM.Logger.log("w", "Could clear container %s because it was not found.", container_id)
+ return False
+
+ if containers[0].isReadOnly():
+ UM.Logger.log("w", "Cannot clear read-only container %s", container_id)
+ return False
+
+ containers[0].clear()
+
+ return True
+
+ ## Set a metadata entry of the specified container.
+ #
+ # This will set the specified entry of the container's metadata to the specified
+ # value. Note that entries containing dictionaries can have their entries changed
+ # by using "/" as a separator. For example, to change an entry "foo" in a
+ # dictionary entry "bar", you can specify "bar/foo" as entry name.
+ #
+ # \param container_id \type{str} The ID of the container to change.
+ # \param entry_name \type{str} The name of the metadata entry to change.
+ # \param entry_value The new value of the entry.
+ #
+ # \return True if successful, False if not.
+ @pyqtSlot(str, str, str, result = bool)
+ def setContainerMetaDataEntry(self, container_id, entry_name, entry_value):
+ containers = UM.Settings.ContainerRegistry.getInstance().findContainers(None, id = container_id)
+ if not containers:
+ UM.Logger.log("w", "Could set metadata of container %s because it was not found.", container_id)
+ return False
+
+ container = containers[0]
+
+ if container.isReadOnly():
+ UM.Logger.log("w", "Cannot set metadata of read-only container %s.", container_id)
+ return False
+
+ entries = entry_name.split("/")
+ entry_name = entries.pop()
+
+ if entries:
+ root_name = entries.pop(0)
+ root = container.getMetaDataEntry(root_name)
+
+ item = root
+ for entry in entries:
+ item = item.get(entries.pop(0), { })
+
+ item[entry_name] = entry_value
+
+ entry_name = root_name
+ entry_value = root
+
+ container.setMetaDataEntry(entry_name, entry_value)
+
+ return True
+
+ ## Find instance containers matching certain criteria.
+ #
+ # This effectively forwards to ContainerRegistry::findInstanceContainers.
+ #
+ # \param criteria A dict of key - value pairs to search for.
+ #
+ # \return A list of container IDs that match the given criteria.
+ @pyqtSlot("QVariantMap", result = "QVariantList")
+ def findInstanceContainers(self, criteria):
+ result = []
+ for entry in self._registry.findInstanceContainers(**criteria):
+ result.append(entry.getId())
+
+ return result
+
+ ## Get a list of string that can be used as name filters for a Qt File Dialog
+ #
+ # This will go through the list of available container types and generate a list of strings
+ # out of that. The strings are formatted as "description (*.extension)" and can be directly
+ # passed to a nameFilters property of a Qt File Dialog.
+ #
+ # \param type_name Which types of containers to list. These types correspond to the "type"
+ # key of the plugin metadata.
+ #
+ # \return A string list with name filters.
+ @pyqtSlot(str, result = "QStringList")
+ def getContainerNameFilters(self, type_name):
+ if not self._container_name_filters:
+ self._updateContainerNameFilters()
+
+ filters = []
+ for filter_string, entry in self._container_name_filters.items():
+ if not type_name or entry["type"] == type_name:
+ filters.append(filter_string)
+
+ return filters
+
+ ## Export a container to a file
+ #
+ # \param container_id The ID of the container to export
+ # \param file_type The type of file to save as. Should be in the form of "description (*.extension, *.ext)"
+ # \param file_url The URL where to save the file.
+ #
+ # \return A dictionary containing a key "status" with a status code and a key "message" with a message
+ # explaining the status.
+ # The status code can be one of "error", "cancelled", "success"
+ @pyqtSlot(str, str, QUrl, result = "QVariantMap")
+ def exportContainer(self, container_id, file_type, file_url):
+ if not container_id or not file_type or not file_url:
+ return { "status": "error", "message": "Invalid arguments"}
+
+ if isinstance(file_url, QUrl):
+ file_url = file_url.toLocalFile()
+
+ if not file_url:
+ return { "status": "error", "message": "Invalid path"}
+
+ mime_type = None
+ if not file_type in self._container_name_filters:
+ try:
+ mime_type = UM.MimeTypeDatabase.getMimeTypeForFile(file_url)
+ except MimeTypeNotFoundError:
+ return { "status": "error", "message": "Unknown File Type" }
+ else:
+ mime_type = self._container_name_filters[file_type]["mime"]
+
+ containers = UM.Settings.ContainerRegistry.getInstance().findContainers(None, id = container_id)
+ if not containers:
+ return { "status": "error", "message": "Container not found"}
+ container = containers[0]
+
+ for suffix in mime_type.suffixes:
+ if file_url.endswith(suffix):
+ break
+ else:
+ file_url += "." + mime_type.preferredSuffix
+
+ if not UM.Platform.isWindows():
+ if os.path.exists(file_url):
+ 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_url))
+ if result == QMessageBox.No:
+ return { "status": "cancelled", "message": "User cancelled"}
+
+ try:
+ contents = container.serialize()
+ except NotImplementedError:
+ return { "status": "error", "message": "Unable to serialize container"}
+
+ with UM.SaveFile(file_url, "w") as f:
+ f.write(contents)
+
+ return { "status": "success", "message": "Succesfully exported container"}
+
+ ## Imports a profile from a file
+ #
+ # \param file_url A URL that points to the file to import.
+ #
+ # \return \type{Dict} dict with a 'status' key containing the string 'success' or 'error', and a 'message' key
+ # containing a message for the user
+ @pyqtSlot(QUrl, result = "QVariantMap")
+ def importContainer(self, file_url):
+ if not file_url:
+ return { "status": "error", "message": "Invalid path"}
+
+ if isinstance(file_url, QUrl):
+ file_url = file_url.toLocalFile()
+
+ if not file_url or not os.path.exists(file_url):
+ return { "status": "error", "message": "Invalid path" }
+
+ try:
+ mime_type = UM.MimeTypeDatabase.getMimeTypeForFile(file_url)
+ except MimeTypeNotFoundError:
+ return { "status": "error", "message": "Could not determine mime type of file" }
+
+ container_type = UM.Settings.ContainerRegistry.getContainerForMimeType(mime_type)
+ if not container_type:
+ return { "status": "error", "message": "Could not find a container to handle the specified file."}
+
+ container_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_url)))
+ container_id = UM.Settings.ContainerRegistry.getInstance().uniqueName(container_id)
+
+ container = container_type(container_id)
+
+ try:
+ with open(file_url, "rt") as f:
+ container.deserialize(f.read())
+ except PermissionError:
+ return { "status": "error", "message": "Permission denied when trying to read the file"}
+
+ container.setName(container_id)
+
+ UM.Settings.ContainerRegistry.getInstance().addContainer(container)
+
+ return { "status": "success", "message": "Successfully imported container {0}".format(container.getName()) }
+
+ def _updateContainerNameFilters(self):
+ self._container_name_filters = {}
+ for plugin_id, container_type in UM.Settings.ContainerRegistry.getContainerTypes():
+ serialize_type = ""
+ try:
+ plugin_metadata = UM.PluginRegistry.getInstance().getMetaData(plugin_id)
+ if plugin_metadata:
+ serialize_type = plugin_metadata["settings_container"]["type"]
+ else:
+ continue
+ except KeyError as e:
+ continue
+
+ mime_type = UM.Settings.ContainerRegistry.getMimeTypeForContainer(container_type)
+
+ entry = {
+ "type": serialize_type,
+ "mime": mime_type,
+ "container": container_type
+ }
+
+ suffix_list = "*." + mime_type.preferredSuffix
+ for suffix in mime_type.suffixes:
+ if suffix == mime_type.preferredSuffix:
+ continue
+
+ suffix_list += ", *." + suffix
+
+ name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
+ self._container_name_filters[name_filter] = entry
+
+ # Factory function, used by QML
+ @staticmethod
+ def createContainerManager(engine, js_engine):
+ return ContainerManager()
diff --git a/cura/ContainerSettingsModel.py b/cura/Settings/ContainerSettingsModel.py
similarity index 98%
rename from cura/ContainerSettingsModel.py
rename to cura/Settings/ContainerSettingsModel.py
index 2ff1a5f401..9ec19ed7fb 100644
--- a/cura/ContainerSettingsModel.py
+++ b/cura/Settings/ContainerSettingsModel.py
@@ -90,4 +90,4 @@ class ContainerSettingsModel(ListModel):
containersChanged = pyqtSignal()
@pyqtProperty("QVariantList", fset = setContainers, notify = containersChanged)
def containers(self):
- return self.container_ids
\ No newline at end of file
+ return self.container_ids
diff --git a/cura/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py
similarity index 100%
rename from cura/CuraContainerRegistry.py
rename to cura/Settings/CuraContainerRegistry.py
diff --git a/cura/ExtruderManager.py b/cura/Settings/ExtruderManager.py
similarity index 100%
rename from cura/ExtruderManager.py
rename to cura/Settings/ExtruderManager.py
diff --git a/cura/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py
similarity index 95%
rename from cura/ExtrudersModel.py
rename to cura/Settings/ExtrudersModel.py
index c8c5a21274..0f2511452a 100644
--- a/cura/ExtrudersModel.py
+++ b/cura/Settings/ExtrudersModel.py
@@ -3,9 +3,10 @@
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
-import cura.ExtruderManager
import UM.Qt.ListModel
+from . import ExtruderManager
+
## Model that holds extruders.
#
# This model is designed for use by any list of extruders, but specifically
@@ -49,7 +50,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
self._active_extruder_stack = None
#Listen to changes.
- manager = cura.ExtruderManager.ExtruderManager.getInstance()
+ manager = ExtruderManager.getInstance()
manager.extrudersChanged.connect(self._updateExtruders) #When the list of extruders changes in general.
UM.Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders) #When the current machine changes.
self._updateExtruders()
@@ -69,7 +70,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
return self._add_global
def _onActiveExtruderChanged(self):
- manager = cura.ExtruderManager.ExtruderManager.getInstance()
+ manager = ExtruderManager.getInstance()
active_extruder_stack = manager.getActiveExtruderStack()
if self._active_extruder_stack != active_extruder_stack:
if self._active_extruder_stack:
@@ -93,7 +94,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
# This should be called whenever the list of extruders changes.
def _updateExtruders(self):
self.clear()
- manager = cura.ExtruderManager.ExtruderManager.getInstance()
+ manager = ExtruderManager.getInstance()
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
if not global_container_stack:
return #There is no machine to get the extruders of.
diff --git a/cura/MachineManagerModel.py b/cura/Settings/MachineManager.py
similarity index 95%
rename from cura/MachineManagerModel.py
rename to cura/Settings/MachineManager.py
index e5c2989362..c217c7087a 100644
--- a/cura/MachineManagerModel.py
+++ b/cura/Settings/MachineManager.py
@@ -2,22 +2,21 @@
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
+
from UM.Application import Application
from UM.Preferences import Preferences
from UM.Logger import Logger
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
catalog = i18nCatalog("cura")
-
-class MachineManagerModel(QObject):
+class MachineManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
@@ -28,7 +27,7 @@ class MachineManagerModel(QObject):
self._global_stack_valid = None
self._onGlobalContainerChanged()
- ExtruderManager.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
+ ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
self.globalContainerChanged.connect(self._onActiveExtruderStackChanged)
self._onActiveExtruderStackChanged()
@@ -36,13 +35,13 @@ class MachineManagerModel(QObject):
self.globalContainerChanged.connect(self.activeMaterialChanged)
self.globalContainerChanged.connect(self.activeVariantChanged)
self.globalContainerChanged.connect(self.activeQualityChanged)
- ExtruderManager.ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeMaterialChanged)
- ExtruderManager.ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeVariantChanged)
- ExtruderManager.ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeQualityChanged)
+ ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeMaterialChanged)
+ ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeVariantChanged)
+ ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeQualityChanged)
self.globalContainerChanged.connect(self.activeStackChanged)
self.globalValueChanged.connect(self.activeStackChanged)
- ExtruderManager.ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
+ ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
self._empty_variant_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_variant")[0]
self._empty_material_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_material")[0]
@@ -126,7 +125,7 @@ class MachineManagerModel(QObject):
if property_name == "validationState":
if self._global_stack_valid:
changed_validation_state = self._active_container_stack.getProperty(key, property_name)
- if changed_validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
+ if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
self._global_stack_valid = False
self.globalValidationChanged.emit()
else:
@@ -155,7 +154,7 @@ class MachineManagerModel(QObject):
self._active_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
self._active_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
- self._active_container_stack = ExtruderManager.ExtruderManager.getInstance().getActiveExtruderStack()
+ self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
if self._active_container_stack:
self._active_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
@@ -207,7 +206,7 @@ class MachineManagerModel(QObject):
new_global_stack.addContainer(quality_instance_container)
new_global_stack.addContainer(current_settings_instance_container)
- ExtruderManager.ExtruderManager.getInstance().addMachineExtruders(definition)
+ ExtruderManager.getInstance().addMachineExtruders(definition)
Application.getInstance().setGlobalContainerStack(new_global_stack)
@@ -228,7 +227,7 @@ class MachineManagerModel(QObject):
for key in stack.getAllKeys():
validation_state = stack.getProperty(key, "validationState")
- if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
+ if validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
return True
return False
@@ -552,6 +551,10 @@ class MachineManagerModel(QObject):
if containers:
return containers[0].getBottom().getId()
+ @staticmethod
+ def createMachineManager(engine, script_engine):
+ return MachineManager()
+
def _updateVariantContainer(self, definition):
if not definition.getMetaDataEntry("has_variants"):
return self._empty_variant_container
@@ -637,6 +640,3 @@ class MachineManagerModel(QObject):
return containers[0]
return self._empty_quality_container
-
-def createMachineManagerModel(engine, script_engine):
- return MachineManagerModel()
diff --git a/cura/Settings/MaterialSettingsVisibilityHandler.py b/cura/Settings/MaterialSettingsVisibilityHandler.py
new file mode 100644
index 0000000000..7286f509bf
--- /dev/null
+++ b/cura/Settings/MaterialSettingsVisibilityHandler.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2016 Ultimaker B.V.
+# Uranium is released under the terms of the AGPLv3 or higher.
+
+import UM.Settings.Models
+
+class MaterialSettingsVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler):
+ def __init__(self, parent = None, *args, **kwargs):
+ super().__init__(parent = parent, *args, **kwargs)
+
+ material_settings = set([
+ "material_print_temperature",
+ "material_bed_temperature",
+ "material_standby_temperature",
+ "cool_fan_speed",
+ "retraction_amount",
+ "retraction_speed",
+ ])
+
+ self.setVisible(material_settings)
diff --git a/cura/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py
similarity index 100%
rename from cura/SettingOverrideDecorator.py
rename to cura/Settings/SettingOverrideDecorator.py
diff --git a/cura/Settings/__init__.py b/cura/Settings/__init__.py
new file mode 100644
index 0000000000..da8f36c040
--- /dev/null
+++ b/cura/Settings/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2016 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
+from .ContainerManager import ContainerManager
+from .ContainerSettingsModel import ContainerSettingsModel
+from .CuraContainerRegistry import CuraContainerRegistry
+from .ExtruderManager import ExtruderManager
+from .ExtrudersModel import ExtrudersModel
+from .MachineManager import MachineManager
+from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
+from .SettingOverrideDecorator import SettingOverrideDecorator
diff --git a/cura_app.py b/cura_app.py
index 3548acedb6..4df40344b9 100755
--- a/cura_app.py
+++ b/cura_app.py
@@ -35,7 +35,7 @@ sys.excepthook = exceptHook
import Arcus #@UnusedImport
from UM.Platform import Platform
import cura.CuraApplication
-import cura.CuraContainerRegistry
+import cura.Settings.CuraContainerRegistry
if Platform.isWindows() and hasattr(sys, "frozen"):
dirpath = os.path.expanduser("~/AppData/Local/cura/")
@@ -44,7 +44,7 @@ if Platform.isWindows() and hasattr(sys, "frozen"):
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()
+cura.Settings.CuraContainerRegistry.getInstance()
app = cura.CuraApplication.CuraApplication.getInstance()
app.run()
diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py
index 3bcc56a79c..e777414459 100644
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -13,7 +13,7 @@ from UM.Resources import Resources
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
from UM.Platform import Platform
-from cura.ExtruderManager import ExtruderManager
+import cura.Settings
from cura.OneAtATimeIterator import OneAtATimeIterator
from . import ProcessSlicedLayersJob
@@ -63,7 +63,7 @@ class CuraEngineBackend(Backend):
self._onGlobalStackChanged()
self._active_extruder_stack = None
- ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
+ cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
self._onActiveExtruderChanged()
#When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
@@ -386,7 +386,8 @@ class CuraEngineBackend(Backend):
self._active_extruder_stack.propertyChanged.disconnect(self._onSettingChanged)
self._active_extruder_stack.containersChanged.disconnect(self._onChanged)
- self._active_extruder_stack = ExtruderManager.getInstance().getActiveExtruderStack()
+ self._active_extruder_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack()
if self._active_extruder_stack:
self._active_extruder_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
- self._active_extruder_stack.containersChanged.connect(self._onChanged)
\ No newline at end of file
+ self._active_extruder_stack.containersChanged.connect(self._onChanged)
+
diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py
index 3d2eb0ed4a..cdfcecb6b9 100644
--- a/plugins/CuraEngineBackend/StartSliceJob.py
+++ b/plugins/CuraEngineBackend/StartSliceJob.py
@@ -15,7 +15,8 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Settings.Validator import ValidatorState
from cura.OneAtATimeIterator import OneAtATimeIterator
-from cura.ExtruderManager import ExtruderManager
+
+import cura.Settings
class StartJobResult(IntEnum):
Finished = 1
@@ -128,7 +129,7 @@ class StartSliceJob(Job):
self._buildGlobalSettingsMessage(stack)
- for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getBottom().getId()):
+ for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getBottom().getId()):
self._buildExtruderMessage(extruder_stack)
for group in object_groups:
@@ -208,4 +209,4 @@ class StartSliceJob(Job):
setting = message.addRepeatedMessage("settings")
setting.name = key
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
- Job.yieldThread()
\ No newline at end of file
+ Job.yieldThread()
diff --git a/plugins/LayerView/LayerView.qml b/plugins/LayerView/LayerView.qml
index 4779d74720..a7e9a7c67d 100644
--- a/plugins/LayerView/LayerView.qml
+++ b/plugins/LayerView/LayerView.qml
@@ -69,7 +69,7 @@ Item
// Ensure that the cursor is at the first position. On some systems the text isnt fully visible
// Seems to have to do something with different dpi densities that QML doesn't quite handle.
// Another option would be to increase the size even further, but that gives pretty ugly results.
- onTextChanged: cursorPosition = 0
+ onEditingFinished: cursorPosition = 0
style: TextFieldStyle
{
textColor: UM.Theme.getColor("setting_control_text");
diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py
index ddbc06d119..15cf25e65e 100644
--- a/plugins/SolidView/SolidView.py
+++ b/plugins/SolidView/SolidView.py
@@ -10,7 +10,7 @@ from UM.View.Renderer import Renderer
from UM.View.GL.OpenGL import OpenGL
-from cura.ExtrudersModel import ExtrudersModel
+import cura.Settings
import math
@@ -24,7 +24,7 @@ class SolidView(View):
self._enabled_shader = None
self._disabled_shader = None
- self._extruders_model = ExtrudersModel()
+ self._extruders_model = cura.Settings.ExtrudersModel()
def beginRendering(self):
scene = self.getController().getScene()
diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py
index 89c7d76e9f..ec75b7253c 100644
--- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py
+++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py
@@ -3,32 +3,224 @@
import math
import copy
+import io
import xml.etree.ElementTree as ET
+import uuid
from UM.Logger import Logger
+import UM.Dictionary
+
import UM.Settings
-# The namespace is prepended to the tag name but between {}.
-# We are only interested in the actual tag name, so discard everything
-# before the last }
-def _tag_without_namespace(element):
- return element.tag[element.tag.rfind("}") + 1:]
-
+## Handles serializing and deserializing material containers from an XML file
class XmlMaterialProfile(UM.Settings.InstanceContainer):
def __init__(self, container_id, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
- def serialize(self):
- raise NotImplementedError("Writing material profiles has not yet been implemented")
+ ## Overridden from InstanceContainer
+ def duplicate(self, new_id, new_name = None):
+ base_file = self.getMetaDataEntry("base_file", "")
+ new_uuid = str(uuid.uuid4())
+ if base_file:
+ containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = base_file)
+ if containers:
+ new_basefile = containers[0].duplicate(self.getMetaDataEntry("brand") + "_" + new_id, new_name)
+ new_basefile.setMetaDataEntry("GUID", new_uuid)
+ base_file = new_basefile.id
+ UM.Settings.ContainerRegistry.getInstance().addContainer(new_basefile)
+
+ new_id = self.getMetaDataEntry("brand") + "_" + new_id + "_" + self.getDefinition().getId()
+ variant = self.getMetaDataEntry("variant")
+ if variant:
+ variant_containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = variant)
+ if variant_containers:
+ new_id += "_" + variant_containers[0].getName().replace(" ", "_")
+
+ result = super().duplicate(new_id, new_name)
+ result.setMetaDataEntry("GUID", new_uuid)
+ result.setMetaDataEntry("base_file", base_file)
+ return result
+
+ ## Overridden from InstanceContainer
+ def setReadOnly(self, read_only):
+ super().setReadOnly(read_only)
+
+ for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(GUID = self.getMetaDataEntry("GUID")):
+ container._read_only = read_only
+
+ ## Overridden from InstanceContainer
+ def setMetaDataEntry(self, key, value):
+ if self.isReadOnly():
+ return
+
+ super().setMetaDataEntry(key, value)
+
+ for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(GUID = self.getMetaDataEntry("GUID")):
+ container.setMetaData(copy.deepcopy(self._metadata))
+
+ ## Overridden from InstanceContainer
+ def setProperty(self, key, property_name, property_value, container = None):
+ if self.isReadOnly():
+ return
+
+ super().setProperty(key, property_name, property_value)
+
+ for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(GUID = self.getMetaDataEntry("GUID")):
+ container._dirty = True
+
+ ## Overridden from InstanceContainer
+ def serialize(self):
+ registry = UM.Settings.ContainerRegistry.getInstance()
+
+ base_file = self.getMetaDataEntry("base_file", "")
+ if base_file and self.id != base_file:
+ # Since we create an instance of XmlMaterialProfile for each machine and nozzle in the profile,
+ # we should only serialize the "base" material definition, since that can then take care of
+ # serializing the machine/nozzle specific profiles.
+ raise NotImplementedError("Cannot serialize non-root XML materials")
+
+ builder = ET.TreeBuilder()
+
+ root = builder.start("fdmmaterial", { "xmlns": "http://www.ultimaker.com/material"})
+
+ ## Begin Metadata Block
+ builder.start("metadata")
+
+ metadata = copy.deepcopy(self.getMetaData())
+ properties = metadata.pop("properties", {})
+
+ # Metadata properties that should not be serialized.
+ metadata.pop("status", "")
+ metadata.pop("variant", "")
+ metadata.pop("type", "")
+ metadata.pop("base_file", "")
+
+ ## Begin Name Block
+ builder.start("name")
+
+ builder.start("brand")
+ builder.data(metadata.pop("brand", ""))
+ builder.end("brand")
+
+ builder.start("material")
+ builder.data(metadata.pop("material", ""))
+ builder.end("material")
+
+ builder.start("color")
+ builder.data(metadata.pop("color_name", ""))
+ builder.end("color")
+
+ builder.end("name")
+ ## End Name Block
+
+ for key, value in metadata.items():
+ builder.start(key)
+ builder.data(value)
+ builder.end(key)
+
+ builder.end("metadata")
+ ## End Metadata Block
+
+ ## Begin Properties Block
+ builder.start("properties")
+
+ for key, value in properties.items():
+ builder.start(key)
+ builder.data(value)
+ builder.end(key)
+
+ builder.end("properties")
+ ## End Properties Block
+
+ ## Begin Settings Block
+ builder.start("settings")
+
+ if self.getDefinition().id == "fdmprinter":
+ for instance in self.findInstances():
+ self._addSettingElement(builder, instance)
+
+ machine_container_map = {}
+ machine_nozzle_map = {}
+
+ all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"))
+ for container in all_containers:
+ definition_id = container.getDefinition().id
+ if definition_id == "fdmprinter":
+ continue
+
+ if definition_id not in machine_container_map:
+ machine_container_map[definition_id] = container
+
+ if definition_id not in machine_nozzle_map:
+ machine_nozzle_map[definition_id] = {}
+
+ variant = container.getMetaDataEntry("variant")
+ if variant:
+ machine_nozzle_map[definition_id][variant] = container
+ continue
+
+ machine_container_map[definition_id] = container
+
+ for definition_id, container in machine_container_map.items():
+ definition = container.getDefinition()
+ try:
+ product = UM.Dictionary.findKey(self.__product_id_map, definition_id)
+ except ValueError:
+ continue
+
+ builder.start("machine")
+ builder.start("machine_identifier", { "manufacturer": definition.getMetaDataEntry("manufacturer", ""), "product": product})
+ builder.end("machine_identifier")
+
+ for instance in container.findInstances():
+ if self.getDefinition().id == "fdmprinter" and self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value:
+ # If the settings match that of the base profile, just skip since we inherit the base profile.
+ continue
+
+ self._addSettingElement(builder, instance)
+
+ # Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
+ for hotend_id, hotend in machine_nozzle_map[definition_id].items():
+ variant_containers = registry.findInstanceContainers(id = hotend.getMetaDataEntry("variant"))
+ if not variant_containers:
+ continue
+
+ builder.start("hotend", { "id": variant_containers[0].getName() })
+
+ for instance in hotend.findInstances():
+ if container.getInstance(instance.definition.key) and container.getProperty(instance.definition.key, "value") == instance.value:
+ # If the settings match that of the machine profile, just skip since we inherit the machine profile.
+ continue
+
+ self._addSettingElement(builder, instance)
+
+ builder.end("hotend")
+
+ builder.end("machine")
+
+ builder.end("settings")
+ ## End Settings Block
+
+ builder.end("fdmmaterial")
+
+ root = builder.close()
+ _indent(root)
+ stream = io.StringIO()
+ tree = ET.ElementTree(root)
+ tree.write(stream, "unicode", True)
+
+ return stream.getvalue()
+
+ ## Overridden from InstanceContainer
def deserialize(self, serialized):
data = ET.fromstring(serialized)
self.addMetaDataEntry("type", "material")
# TODO: Add material verfication
- self.addMetaDataEntry("status", "Unknown")
+ self.addMetaDataEntry("status", "unknown")
metadata = data.iterfind("./um:metadata/*", self.__namespaces)
for entry in metadata:
@@ -39,7 +231,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
material = entry.find("./um:material", self.__namespaces)
color = entry.find("./um:color", self.__namespaces)
- self.setName("{0} {1} ({2})".format(brand.text, material.text, color.text))
+ self.setName(material.text)
self.addMetaDataEntry("brand", brand.text)
self.addMetaDataEntry("material", material.text)
@@ -49,6 +241,12 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
self.addMetaDataEntry(tag_name, entry.text)
+ if not "description" in self.getMetaData():
+ self.addMetaDataEntry("description", "")
+
+ if not "adhesion_info" in self.getMetaData():
+ self.addMetaDataEntry("adhesion_info", "")
+
property_values = {}
properties = data.iterfind("./um:properties/*", self.__namespaces)
for entry in properties:
@@ -58,17 +256,6 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
diameter = float(property_values.get("diameter", 2.85)) # In mm
density = float(property_values.get("density", 1.3)) # In g/cm3
- weight_per_cm = (math.pi * (diameter / 20) ** 2 * 0.1) * density
-
- spool_weight = property_values.get("spool_weight")
- spool_length = property_values.get("spool_length")
- if spool_weight:
- length = float(spool_weight) / weight_per_cm
- property_values["spool_length"] = str(length / 100)
- elif spool_length:
- weight = (float(spool_length) * 100) * weight_per_cm
- property_values["spool_weight"] = str(weight)
-
self.addMetaDataEntry("properties", property_values)
self.setDefinition(UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0])
@@ -83,6 +270,8 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
else:
Logger.log("d", "Unsupported material setting %s", key)
+ self._dirty = False
+
machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
for machine in machines:
machine_setting_values = {}
@@ -112,6 +301,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
new_material.setName(self.getName())
new_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_material.setDefinition(definition)
+ new_material.addMetaDataEntry("base_file", self.id)
for key, value in global_setting_values.items():
new_material.setProperty(key, "value", value, definition)
@@ -142,6 +332,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
new_hotend_material.setName(self.getName())
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_hotend_material.setDefinition(definition)
+ new_hotend_material.addMetaDataEntry("base_file", self.id)
new_hotend_material.addMetaDataEntry("variant", variant_containers[0].id)
@@ -162,6 +353,15 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
new_hotend_material._dirty = False
UM.Settings.ContainerRegistry.getInstance().addContainer(new_hotend_material)
+ def _addSettingElement(self, builder, instance):
+ try:
+ key = UM.Dictionary.findKey(self.__material_property_setting_map, instance.definition.key)
+ except ValueError:
+ return
+
+ builder.start("setting", { "key": key })
+ builder.data(str(instance.value))
+ builder.end("setting")
# Map XML file setting names to internal names
__material_property_setting_map = {
@@ -174,6 +374,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
}
# Map XML file product names to internal ids
+ # TODO: Move this to definition's metadata
__product_id_map = {
"Ultimaker2": "ultimaker2",
"Ultimaker2+": "ultimaker2_plus",
@@ -184,6 +385,30 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
"Ultimaker Original+": "ultimaker_original_plus"
}
+ # Map of recognised namespaces with a proper prefix.
__namespaces = {
"um": "http://www.ultimaker.com/material"
}
+
+## Helper function for pretty-printing XML because ETree is stupid
+def _indent(elem, level = 0):
+ i = "\n" + level * " "
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + " "
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ _indent(elem, level + 1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
+
+# The namespace is prepended to the tag name but between {}.
+# We are only interested in the actual tag name, so discard everything
+# before the last }
+def _tag_without_namespace(element):
+ return element.tag[element.tag.rfind("}") + 1:]
diff --git a/plugins/XmlMaterialProfile/__init__.py b/plugins/XmlMaterialProfile/__init__.py
index 041a3f6346..213b9a358a 100644
--- a/plugins/XmlMaterialProfile/__init__.py
+++ b/plugins/XmlMaterialProfile/__init__.py
@@ -17,6 +17,7 @@ def getMetaData():
"api": 3
},
"settings_container": {
+ "type": "material",
"mimetype": "application/x-ultimaker-material-profile"
}
}
diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml
index 7e03bd7102..67bc5fe149 100644
--- a/resources/qml/Actions.qml
+++ b/resources/qml/Actions.qml
@@ -39,6 +39,8 @@ Item
property alias resetProfile: resetProfileAction;
property alias manageProfiles: manageProfilesAction;
+ property alias manageMaterials: manageMaterialsAction;
+
property alias preferences: preferencesAction;
property alias showEngineLog: showEngineLogAction;
@@ -90,7 +92,7 @@ Item
Action
{
id: preferencesAction;
- text: catalog.i18nc("@action:inmenu menubar:settings","&Preferences...");
+ text: catalog.i18nc("@action:inmenu","Configure Cura...");
iconName: "configure";
}
@@ -107,6 +109,13 @@ Item
iconName: "configure";
}
+ Action
+ {
+ id: manageMaterialsAction
+ text: catalog.i18nc("@action:inmenu", "Manage Materials...")
+ iconName: "configure"
+ }
+
Action
{
id: updateProfileAction;
@@ -273,5 +282,6 @@ Item
{
id: configureSettingVisibilityAction
text: catalog.i18nc("@action:menu", "Configure setting visiblity...");
+ iconName: "configure"
}
}
diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml
index e475007a5d..e805991df4 100644
--- a/resources/qml/Cura.qml
+++ b/resources/qml/Cura.qml
@@ -10,6 +10,8 @@ import QtQuick.Dialogs 1.1
import UM 1.2 as UM
import Cura 1.0 as Cura
+import "Menus"
+
UM.MainWindow
{
id: base
@@ -55,41 +57,13 @@ UM.MainWindow
Menu
{
id: fileMenu
- //: File menu
title: catalog.i18nc("@title:menu menubar:toplevel","&File");
MenuItem {
action: Cura.Actions.open;
}
- Menu
- {
- id: recentFilesMenu;
- title: catalog.i18nc("@title:menu menubar:file", "Open &Recent")
- iconName: "document-open-recent";
-
- enabled: Printer.recentFiles.length > 0;
-
- Instantiator
- {
- model: Printer.recentFiles
- MenuItem
- {
- text:
- {
- var path = modelData.toString()
- return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1);
- }
- onTriggered: {
- UM.MeshFileHandler.readLocalFile(modelData);
- var meshName = backgroundItem.getMeshName(modelData.toString())
- backgroundItem.hasMesh(decodeURIComponent(meshName))
- }
- }
- onObjectAdded: recentFilesMenu.insertItem(index, object)
- onObjectRemoved: recentFilesMenu.removeItem(object)
- }
- }
+ RecentFilesMenu { }
MenuSeparator { }
@@ -130,7 +104,6 @@ UM.MainWindow
Menu
{
- //: Edit menu
title: catalog.i18nc("@title:menu menubar:toplevel","&Edit");
MenuItem { action: Cura.Actions.undo; }
@@ -146,173 +119,45 @@ UM.MainWindow
MenuItem { action: Cura.Actions.unGroupObjects;}
}
+ ViewMenu { title: catalog.i18nc("@title:menu", "&View") }
+
Menu
{
- title: catalog.i18nc("@title:menu menubar:toplevel","&View");
- id: top_view_menu
- Instantiator
- {
- model: UM.ViewModel { }
- MenuItem
- {
- text: model.name;
- checkable: true;
- checked: model.active;
- exclusiveGroup: view_menu_top_group;
- onTriggered: UM.Controller.setActiveView(model.id);
- }
- onObjectAdded: top_view_menu.insertItem(index, object)
- onObjectRemoved: top_view_menu.removeItem(object)
- }
- ExclusiveGroup { id: view_menu_top_group; }
- }
- Menu
- {
- id: machineMenu;
- //: Machine menu
- title: catalog.i18nc("@title:menu menubar:toplevel","&Printer");
+ id: settingsMenu
+ title: catalog.i18nc("@title:menu", "&Settings")
+
+ PrinterMenu { title: catalog.i18nc("@title:menu menubar:toplevel", "&Printer") }
Instantiator
{
- model: UM.ContainerStacksModel
- {
- filter: {"type": "machine"}
+ model: Cura.ExtrudersModel { }
+ Menu {
+ title: model.name
+
+ NozzleMenu { title: catalog.i18nc("@title:menu", "&Nozzle"); visible: Cura.MachineManager.hasVariants }
+ MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: Cura.MachineManager.hasMaterials }
+ ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); }
+
+ MenuSeparator { }
+
+ MenuItem { text: "Set as Active Extruder" }
}
- MenuItem
- {
- text: model.name;
- checkable: true;
- checked: Cura.MachineManager.activeMachineId == model.id
- exclusiveGroup: machineMenuGroup;
- onTriggered: Cura.MachineManager.setActiveMachine(model.id);
- }
- onObjectAdded: machineMenu.insertItem(index, object)
- onObjectRemoved: machineMenu.removeItem(object)
+ onObjectAdded: settingsMenu.insertItem(index, object)
+ onObjectRemoved: settingsMenu.removeItem(object)
}
- ExclusiveGroup { id: machineMenuGroup; }
+ NozzleMenu { title: catalog.i18nc("@title:menu", "&Nozzle"); visible: machineExtruderCount.properties.value <= 1 && Cura.MachineManager.hasVariants }
+ MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: machineExtruderCount.properties.value <= 1 && Cura.MachineManager.hasMaterials }
+ ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); visible: machineExtruderCount.properties.value <= 1 }
MenuSeparator { }
- Instantiator
- {
- model: UM.InstanceContainersModel
- {
- filter:
- {
- "type": "variant",
- "definition": Cura.MachineManager.activeDefinitionId //Only show variants of this machine
- }
- }
- MenuItem {
- text: model.name;
- checkable: true;
- checked: model.id == Cura.MachineManager.activeVariantId;
- exclusiveGroup: machineVariantsGroup;
- onTriggered: Cura.MachineManager.setActiveVariant(model.id)
- }
- onObjectAdded: machineMenu.insertItem(index, object)
- onObjectRemoved: machineMenu.removeItem(object)
- }
-
- ExclusiveGroup { id: machineVariantsGroup; }
-
- MenuSeparator { visible: Cura.MachineManager.hasVariants; }
-
- MenuItem { action: Cura.Actions.addMachine; }
- MenuItem { action: Cura.Actions.configureMachines; }
- }
-
- Menu
- {
- id: profileMenu
- title: catalog.i18nc("@title:menu menubar:toplevel", "P&rofile")
-
- Instantiator
- {
- id: profileMenuInstantiator
- model: UM.InstanceContainersModel
- {
- filter:
- {
- var result = { "type": "quality" };
- if(Cura.MachineManager.filterQualityByMachine)
- {
- result.definition = Cura.MachineManager.activeDefinitionId;
- if(Cura.MachineManager.hasMaterials)
- {
- result.material = Cura.MachineManager.activeMaterialId;
- }
- }
- else
- {
- result.definition = "fdmprinter"
- }
- return result
- }
- }
- property int separatorIndex: -1
-
- Loader {
- property QtObject model_data: model
- property int model_index: index
- sourceComponent: profileMenuItemDelegate
- }
-
- onObjectAdded:
- {
- //Insert a separator between readonly and custom profiles
- if(separatorIndex < 0 && index > 0) {
- if(model.getItem(index-1).readOnly != model.getItem(index).readOnly) {
- profileMenu.insertSeparator(index);
- separatorIndex = index;
- }
- }
- //Because of the separator, custom profiles move one index lower
- profileMenu.insertItem((model.getItem(index).readOnly) ? index : index + 1, object.item);
- }
- onObjectRemoved:
- {
- //When adding a profile, the menu is rebuild by removing all items.
- //If a separator was added, we need to remove that too.
- if(separatorIndex >= 0)
- {
- profileMenu.removeItem(profileMenu.items[separatorIndex])
- separatorIndex = -1;
- }
- profileMenu.removeItem(object.item);
- }
- }
-
- ExclusiveGroup { id: profileMenuGroup; }
-
- Component
- {
- id: profileMenuItemDelegate
- MenuItem
- {
- id: item
- text: model_data ? model_data.name : ""
- checkable: true
- checked: model_data != null ? Cura.MachineManager.activeQualityId == model_data.id : false
- exclusiveGroup: profileMenuGroup
- onTriggered: Cura.MachineManager.setActiveQuality(model_data.id)
- }
- }
-
- MenuSeparator { id: profileMenuSeparator }
-
- MenuItem { action: Cura.Actions.addProfile }
- MenuItem { action: Cura.Actions.updateProfile }
- MenuItem { action: Cura.Actions.resetProfile }
- MenuSeparator { }
- MenuItem { action: Cura.Actions.manageProfiles }
+ MenuItem { action: Cura.Actions.configureSettingVisibility }
}
Menu
{
id: extension_menu
- //: Extensions menu
title: catalog.i18nc("@title:menu menubar:toplevel","E&xtensions");
Instantiator
@@ -346,8 +191,7 @@ UM.MainWindow
Menu
{
- //: Settings menu
- title: catalog.i18nc("@title:menu menubar:toplevel","&Settings");
+ title: catalog.i18nc("@title:menu menubar:toplevel","P&references");
MenuItem { action: Cura.Actions.preferences; }
}
@@ -365,6 +209,16 @@ UM.MainWindow
}
}
+ UM.SettingPropertyProvider
+ {
+ id: machineExtruderCount
+
+ containerStackId: Cura.MachineManager.activeMachineId
+ key: "machine_extruder_count"
+ watchedProperties: [ "value" ]
+ storeIndex: 0
+ }
+
Item
{
id: contentItem;
@@ -411,14 +265,9 @@ UM.MainWindow
{
id: view_panel
- //anchors.left: parent.left;
- //anchors.right: parent.right;
- //anchors.bottom: parent.bottom
anchors.top: viewModeButton.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height;
anchors.left: viewModeButton.left;
- //anchors.bottom: buttons.top;
- //anchors.bottomMargin: UM.Theme.getSize("default_margin").height;
height: childrenRect.height;
@@ -428,7 +277,6 @@ UM.MainWindow
Button
{
id: openFileButton;
- //style: UM.Backend.progress < 0 ? UM.Theme.styles.open_file_button : UM.Theme.styles.tool_button;
text: catalog.i18nc("@action:button","Open File");
iconSource: UM.Theme.getIcon("load")
style: UM.Theme.styles.tool_button
@@ -436,9 +284,7 @@ UM.MainWindow
anchors
{
top: parent.top;
- //topMargin: UM.Theme.getSize("loadfile_margin").height
left: parent.left;
- //leftMargin: UM.Theme.getSize("loadfile_margin").width
}
action: Cura.Actions.open;
}
@@ -478,27 +324,7 @@ UM.MainWindow
style: UM.Theme.styles.tool_button;
tooltip: '';
- menu: Menu
- {
- id: viewMenu;
- Instantiator
- {
- id: viewMenuInstantiator
- model: UM.ViewModel { }
- MenuItem
- {
- text: model.name
- checkable: true;
- checked: model.active
- exclusiveGroup: viewMenuGroup;
- onTriggered: UM.Controller.setActiveView(model.id);
- }
- onObjectAdded: viewMenu.insertItem(index, object)
- onObjectRemoved: viewMenu.removeItem(object)
- }
-
- ExclusiveGroup { id: viewMenuGroup; }
- }
+ menu: ViewMenu { }
}
Toolbar
@@ -650,6 +476,16 @@ UM.MainWindow
}
}
+ Connections
+ {
+ target: Cura.Actions.manageMaterials
+ onTriggered:
+ {
+ preferences.visible = true;
+ preferences.setPage(3)
+ }
+ }
+
Connections
{
target: Cura.Actions.configureSettingVisibility
diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml
new file mode 100644
index 0000000000..be2ef4a551
--- /dev/null
+++ b/resources/qml/Menus/MaterialMenu.qml
@@ -0,0 +1,72 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Menu
+{
+ id: menu
+ title: "Material"
+
+ Instantiator
+ {
+ model: UM.InstanceContainersModel
+ {
+ filter:
+ {
+ var result = { "type": "material" }
+ if(Cura.MachineManager.filterMaterialsByMachine)
+ {
+ result.definition = Cura.MachineManager.activeDefinitionId
+ if(Cura.MachineManager.hasVariants)
+ {
+ result.variant = Cura.MachineManager.activeVariantId
+ }
+ }
+ else
+ {
+ result.definition = "fdmprinter"
+ }
+ return result
+ }
+ }
+ MenuItem
+ {
+ text:
+ {
+ var result = model.name
+
+ if(model.metadata.brand != undefined && model.metadata.brand != "Generic")
+ {
+ result = model.metadata.brand + " " + result
+ }
+
+ if(model.metadata.color_name != undefined && model.metadata.color_name != "Generic")
+ {
+ result = result + " (%1)".arg(model.metadata.color_name)
+ }
+
+ return result
+ }
+ checkable: true;
+ checked: model.id == Cura.MachineManager.activeMaterialId;
+ exclusiveGroup: group;
+ onTriggered:
+ {
+ Cura.MachineManager.setActiveMaterial(model.id);
+ }
+ }
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem(object)
+ }
+
+ ExclusiveGroup { id: group }
+
+ MenuSeparator { }
+
+ MenuItem { action: Cura.Actions.manageMaterials }
+}
diff --git a/resources/qml/Menus/NozzleMenu.qml b/resources/qml/Menus/NozzleMenu.qml
new file mode 100644
index 0000000000..506a9a2a01
--- /dev/null
+++ b/resources/qml/Menus/NozzleMenu.qml
@@ -0,0 +1,37 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Menu
+{
+ id: menu
+ title: "Nozzle"
+
+ Instantiator
+ {
+ model: UM.InstanceContainersModel
+ {
+ filter:
+ {
+ "type": "variant",
+ "definition": Cura.MachineManager.activeDefinitionId //Only show variants of this machine
+ }
+ }
+ MenuItem {
+ text: model.name;
+ checkable: true;
+ checked: model.id == Cura.MachineManager.activeVariantId;
+ exclusiveGroup: group
+ onTriggered: Cura.MachineManager.setActiveVariant(model.id)
+ }
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem(object)
+ }
+
+ ExclusiveGroup { id: group }
+}
diff --git a/resources/qml/Menus/PrinterMenu.qml b/resources/qml/Menus/PrinterMenu.qml
new file mode 100644
index 0000000000..408e8bd585
--- /dev/null
+++ b/resources/qml/Menus/PrinterMenu.qml
@@ -0,0 +1,38 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Menu
+{
+ id: menu;
+
+ Instantiator
+ {
+ model: UM.ContainerStacksModel
+ {
+ filter: {"type": "machine"}
+ }
+ MenuItem
+ {
+ text: model.name;
+ checkable: true;
+ checked: Cura.MachineManager.activeMachineId == model.id
+ exclusiveGroup: group;
+ onTriggered: Cura.MachineManager.setActiveMachine(model.id);
+ }
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem(object)
+ }
+
+ ExclusiveGroup { id: group; }
+
+ MenuSeparator { }
+
+ MenuItem { action: Cura.Actions.addMachine; }
+ MenuItem { action: Cura.Actions.configureMachines; }
+}
diff --git a/resources/qml/Menus/ProfileMenu.qml b/resources/qml/Menus/ProfileMenu.qml
new file mode 100644
index 0000000000..213f8c2629
--- /dev/null
+++ b/resources/qml/Menus/ProfileMenu.qml
@@ -0,0 +1,86 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+ Menu
+{
+ id: menu
+
+ Instantiator
+ {
+ model: UM.InstanceContainersModel { filter: menu.getFilter({ "read_only": true }); }
+
+ MenuItem
+ {
+ text: model.name
+ checkable: true
+ checked: Cura.MachineManager.activeQualityId == model.id
+ exclusiveGroup: group
+ onTriggered: Cura.MachineManager.setActiveQuality(model.id)
+ }
+
+ onObjectAdded: menu.insertItem(index, object);
+ onObjectRemoved: menu.removeItem(object);
+ }
+
+ MenuSeparator { id: customSeparator }
+
+ Instantiator
+ {
+ model: UM.InstanceContainersModel
+ {
+ id: customProfilesModel;
+ filter: menu.getFilter({ "read_only": false });
+ onRowsInserted: customSeparator.visible = rowCount() > 1
+ onRowsRemoved: customSeparator.visible = rowCount() > 1
+ onModelReset: customSeparator.visible = rowCount() > 1
+ }
+
+ MenuItem
+ {
+ text: model.name
+ checkable: true
+ checked: Cura.MachineManager.activeQualityId == model.id
+ exclusiveGroup: group
+ onTriggered: Cura.MachineManager.setActiveQuality(model.id)
+ }
+
+ onObjectAdded: menu.insertItem(index, object);
+ onObjectRemoved: menu.removeItem(object);
+ }
+
+ ExclusiveGroup { id: group; }
+
+ MenuSeparator { id: profileMenuSeparator }
+
+ MenuItem { action: Cura.Actions.addProfile }
+ MenuItem { action: Cura.Actions.updateProfile }
+ MenuItem { action: Cura.Actions.resetProfile }
+ MenuSeparator { }
+ MenuItem { action: Cura.Actions.manageProfiles }
+
+ function getFilter(initial_conditions)
+ {
+ var result = initial_conditions;
+ result.type = "quality"
+
+ if(Cura.MachineManager.filterQualityByMachine)
+ {
+ result.definition = Cura.MachineManager.activeDefinitionId;
+ if(Cura.MachineManager.hasMaterials)
+ {
+ result.material = Cura.MachineManager.activeMaterialId;
+ }
+ }
+ else
+ {
+ result.definition = "fdmprinter"
+ }
+ return result
+ }
+}
diff --git a/resources/qml/Menus/RecentFilesMenu.qml b/resources/qml/Menus/RecentFilesMenu.qml
new file mode 100644
index 0000000000..c47fc5715b
--- /dev/null
+++ b/resources/qml/Menus/RecentFilesMenu.qml
@@ -0,0 +1,37 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Menu
+{
+ id: menu
+ title: catalog.i18nc("@title:menu menubar:file", "Open &Recent")
+ iconName: "document-open-recent";
+
+ enabled: Printer.recentFiles.length > 0;
+
+ Instantiator
+ {
+ model: Printer.recentFiles
+ MenuItem
+ {
+ text:
+ {
+ var path = modelData.toString()
+ return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1);
+ }
+ onTriggered: {
+ UM.MeshFileHandler.readLocalFile(modelData);
+ var meshName = backgroundItem.getMeshName(modelData.toString())
+ backgroundItem.hasMesh(decodeURIComponent(meshName))
+ }
+ }
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem(object)
+ }
+}
diff --git a/resources/qml/Menus/ViewMenu.qml b/resources/qml/Menus/ViewMenu.qml
new file mode 100644
index 0000000000..74579932e0
--- /dev/null
+++ b/resources/qml/Menus/ViewMenu.qml
@@ -0,0 +1,29 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Menu
+{
+ title: catalog.i18nc("@title:menu menubar:toplevel", "&View");
+ id: menu
+ Instantiator
+ {
+ model: UM.ViewModel { }
+ MenuItem
+ {
+ text: model.name;
+ checkable: true;
+ checked: model.active;
+ exclusiveGroup: group;
+ onTriggered: UM.Controller.setActiveView(model.id);
+ }
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem(object)
+ }
+ ExclusiveGroup { id: group; }
+}
diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml
index 74089ec9e6..d4f63d78fc 100644
--- a/resources/qml/Preferences/MachinesPage.qml
+++ b/resources/qml/Preferences/MachinesPage.qml
@@ -27,14 +27,35 @@ UM.ManagementPage
return -1;
}
- onAddObject: Printer.requestAddPrinter()
- onRemoveObject: confirmDialog.open();
- onRenameObject: renameDialog.open();
- onActivateObject: Cura.MachineManager.setActiveMachine(base.currentItem.id)
-
- removeEnabled: base.currentItem != null && model.rowCount() > 1
- renameEnabled: base.currentItem != null
- activateEnabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMachineId
+ buttons: [
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Activate");
+ iconName: "list-activate";
+ enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId
+ onClicked: Cura.MachineManager.setActiveMachine(base.currentItem.id)
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Add");
+ iconName: "list-add";
+ onClicked: Printer.requestAddPrinter()
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Remove");
+ iconName: "list-remove";
+ enabled: base.currentItem != null && model.rowCount() > 1
+ onClicked: confirmDialog.open();
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Rename");
+ iconName: "edit-rename";
+ enabled: base.currentItem != null
+ onClicked: renameDialog.open();
+ }
+ ]
Item
{
diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml
new file mode 100644
index 0000000000..67a149f446
--- /dev/null
+++ b/resources/qml/Preferences/MaterialView.qml
@@ -0,0 +1,252 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Uranium is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.1
+import QtQuick.Controls 1.3
+import QtQuick.Dialogs 1.2
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+TabView
+{
+ id: base
+
+ property QtObject properties;
+
+ property bool editingEnabled: false;
+ property string currency: UM.Preferences.getValue("general/currency") ? UM.Preferences.getValue("general/currency") : "€"
+ property real firstColumnWidth: width * 0.45
+ property real secondColumnWidth: width * 0.45
+ property string containerId: ""
+
+ Tab
+ {
+ title: "Information"
+ anchors
+ {
+ leftMargin: UM.Theme.getSize("default_margin").width
+ topMargin: UM.Theme.getSize("default_margin").height
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ rightMargin: 0
+ }
+
+ ScrollView
+ {
+ anchors.fill: parent
+ horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
+
+ Flow
+ {
+ id: containerGrid
+
+ width: base.width;
+
+ property real rowHeight: textField.height;
+
+ Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Brand") }
+ ReadOnlyTextField
+ {
+ id: textField;
+ width: base.secondColumnWidth;
+ text: properties.supplier;
+ readOnly: !base.editingEnabled;
+ onEditingFinished: Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, "brand", text)
+ }
+
+ Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") }
+ ReadOnlyTextField
+ {
+ width: base.secondColumnWidth;
+ text: properties.material_type;
+ readOnly: !base.editingEnabled;
+ onEditingFinished: Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, "material", text)
+ }
+
+ Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") }
+
+ Row
+ {
+ width: base.secondColumnWidth;
+ height: parent.rowHeight;
+ spacing: UM.Theme.getSize("default_margin").width/2
+
+ Rectangle
+ {
+ id: colorSelector
+ color: properties.color_code
+ onColorChanged: Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, "color_code", color)
+
+ width: colorLabel.height * 0.75
+ height: colorLabel.height * 0.75
+ border.width: UM.Theme.getSize("default_lining").height
+
+ anchors.verticalCenter: parent.verticalCenter
+
+ MouseArea { anchors.fill: parent; onClicked: colorDialog.open(); enabled: base.editingEnabled }
+ }
+ ReadOnlyTextField
+ {
+ id: colorLabel;
+ text: properties.color_name;
+ readOnly: !base.editingEnabled
+ onEditingFinished: Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, "color_name", text)
+ }
+
+ ColorDialog { id: colorDialog; color: properties.color_code; onAccepted: colorSelector.color = color }
+ }
+
+ Item { width: parent.width; height: UM.Theme.getSize("default_margin").height }
+
+ Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: "" + catalog.i18nc("@label", "Properties") + "" }
+
+ Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Density") }
+ ReadOnlySpinBox
+ {
+ width: base.secondColumnWidth;
+ value: properties.density;
+ decimals: 2
+ suffix: "g/cm"
+ stepSize: 0.01
+ readOnly: !base.editingEnabled;
+
+ onEditingFinished: Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, "properties/density", value)
+ }
+
+ Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Diameter") }
+ ReadOnlySpinBox
+ {
+ width: base.secondColumnWidth;
+ value: properties.diameter;
+ decimals: 2
+ suffix: "mm³"
+ stepSize: 0.01
+ readOnly: !base.editingEnabled;
+
+ onEditingFinished: Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, "properties/diameter", value)
+ }
+
+ Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament Cost") }
+ SpinBox
+ {
+ width: base.secondColumnWidth;
+ value: properties.spool_cost;
+ prefix: base.currency
+ enabled: false
+ }
+
+ Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") }
+ SpinBox
+ {
+ width: base.secondColumnWidth;
+ value: properties.spool_weight;
+ suffix: "g";
+ stepSize: 10
+ enabled: false
+ }
+
+ Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") }
+ SpinBox
+ {
+ width: base.secondColumnWidth;
+ value: parseFloat(properties.spool_length);
+ suffix: "m";
+ enabled: false
+ }
+
+ Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter (Approx.)") }
+ SpinBox
+ {
+ width: base.secondColumnWidth;
+ value: parseFloat(properties.cost_per_meter);
+ suffix: catalog.i18nc("@label", "%1/m".arg(base.currency));
+ enabled: false
+ }
+
+ Item { width: parent.width; height: UM.Theme.getSize("default_margin").height }
+
+ Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Description") }
+
+ ReadOnlyTextArea
+ {
+ text: properties.description;
+ width: base.firstColumnWidth + base.secondColumnWidth
+ wrapMode: Text.WordWrap
+
+ readOnly: !base.editingEnabled;
+
+ onEditingFinished: Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, "description", text)
+ }
+
+ Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Adhesion Information") }
+
+ ReadOnlyTextArea
+ {
+ text: properties.adhesion_info;
+ width: base.firstColumnWidth + base.secondColumnWidth
+ wrapMode: Text.WordWrap
+
+ readOnly: !base.editingEnabled;
+
+ onEditingFinished: Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, "adhesion_info", text)
+ }
+ }
+ }
+ }
+
+ Tab
+ {
+ title: catalog.i18nc("@label", "Print settings")
+ anchors
+ {
+ leftMargin: UM.Theme.getSize("default_margin").width
+ topMargin: UM.Theme.getSize("default_margin").height
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ rightMargin: 0
+ }
+
+ ScrollView
+ {
+ anchors.fill: parent;
+
+ ListView
+ {
+ model: UM.SettingDefinitionsModel
+ {
+ containerId: Cura.MachineManager.activeDefinitionId
+ visibilityHandler: Cura.MaterialSettingsVisibilityHandler { }
+ expanded: ["*"]
+ }
+
+ delegate: UM.TooltipArea
+ {
+ width: childrenRect.width
+ height: childrenRect.height
+ text: model.description
+ Label
+ {
+ id: label
+ width: base.firstColumnWidth;
+ height: spinBox.height
+ text: model.label
+ }
+ ReadOnlySpinBox
+ {
+ id: spinBox
+ anchors.left: label.right
+ value: parseFloat(provider.properties.value);
+ width: base.secondColumnWidth;
+ readOnly: !base.editingEnabled
+ suffix: model.unit
+ maximumValue: 99999
+ decimals: model.unit == "mm" ? 2 : 0
+
+ onEditingFinished: provider.setPropertyValue("value", value)
+ }
+
+ UM.ContainerPropertyProvider { id: provider; containerId: base.containerId; watchedProperties: [ "value" ]; key: model.key }
+ }
+ }
+ }
+ }
+}
diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml
index af0f0c1bd2..35fa7256ea 100644
--- a/resources/qml/Preferences/MaterialsPage.qml
+++ b/resources/qml/Preferences/MaterialsPage.qml
@@ -33,6 +33,8 @@ UM.ManagementPage
}
return result
}
+
+ sectionProperty: "brand"
}
activeId: Cura.MachineManager.activeMaterialId
@@ -45,14 +47,64 @@ UM.ManagementPage
return -1;
}
- addEnabled: false
- removeEnabled: false
- renameEnabled: false
-
- scrollviewCaption: " "
+ scrollviewCaption: "Printer: %1, Nozzle: %2".arg(Cura.MachineManager.activeMachineName).arg(Cura.MachineManager.activeVariantName)
detailsVisible: true
- property string currency: UM.Preferences.getValue("general/currency")
+ section.property: "section"
+ section.delegate: Label { text: section }
+
+ buttons: [
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Activate");
+ iconName: "list-activate";
+ enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId
+ onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id)
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Duplicate");
+ iconName: "list-add";
+ enabled: base.currentItem != null
+ onClicked:
+ {
+ var material_id = Cura.ContainerManager.duplicateContainer(base.currentItem.id)
+ if(material_id == "")
+ {
+ return
+ }
+
+ if(Cura.MachineManager.filterQualityByMachine)
+ {
+ var quality_id = Cura.ContainerManager.duplicateContainer(Cura.MachineManager.activeQualityId)
+ Cura.ContainerManager.setContainerMetaDataEntry(quality_id, "material", material_id)
+ Cura.MachineManager.setActiveQuality(quality_id)
+ }
+
+ Cura.MachineManager.setActiveMaterial(material_id)
+ }
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Remove");
+ iconName: "list-remove";
+ enabled: base.currentItem != null && !base.currentItem.readOnly
+ onClicked: confirmDialog.open()
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Import");
+ iconName: "document-import";
+ onClicked: importDialog.open();
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Export")
+ iconName: "document-export"
+ onClicked: exportDialog.open()
+ enabled: currentItem != null
+ }
+ ]
Item {
UM.I18nCatalog { id: catalog; name: "cura"; }
@@ -60,126 +112,42 @@ UM.ManagementPage
visible: base.currentItem != null
anchors.fill: parent
- Label { id: profileName; text: materialProperties.name; font: UM.Theme.getFont("large"); width: parent.width; }
+ Item
+ {
+ id: profileName
- TabView {
- id: scrollView
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: profileName.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.bottom: parent.bottom
+ width: parent.width;
+ height: childrenRect.height
- Tab {
- title: "Information"
- anchors.margins: UM.Theme.getSize("default_margin").height
+ Label { text: materialProperties.name; font: UM.Theme.getFont("large"); }
+ Button
+ {
+ id: editButton
+ anchors.right: parent.right;
+ text: catalog.i18nc("@action:button", "Edit");
+ iconName: "document-edit";
- Flow {
- id: containerGrid
+ enabled: base.currentItem != null && !base.currentItem.readOnly
- width: scrollView.width;
- property real columnWidth: width / 2
-
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "Profile Type") }
- Label { width: parent.columnWidth; text: materialProperties.profile_type }
-
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "Supplier") }
- Label { width: parent.columnWidth; text: materialProperties.supplier }
-
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "Material Type") }
- Label { width: parent.columnWidth; text: materialProperties.material_type }
-
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "Color") }
-
- Row {
- width: parent.columnWidth;
- spacing: UM.Theme.getSize("default_margin").width/2
- Rectangle {
- color: materialProperties.color_code
- width: colorLabel.height
- height: colorLabel.height
- border.width: UM.Theme.getSize("default_lining").height
- }
- Label { id: colorLabel; text: materialProperties.color_name }
- }
-
- Item { width: parent.width; height: UM.Theme.getSize("default_margin").height }
-
- Label { width: parent.width; text: "" + catalog.i18nc("@label", "Properties") + "" }
-
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "Density") }
- Label { width: parent.columnWidth; text: materialProperties.density }
-
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "Diameter") }
- Label { width: parent.columnWidth; text: materialProperties.diameter }
-
- Label {
- text: catalog.i18nc("@label", "Filament cost")
- width: parent.columnWidth;
- height: spoolCostInput.height
- verticalAlignment: Text.AlignVCenter
- }
-
- Row {
- width: parent.columnWidth;
- Label {
- text: base.currency ? base.currency + " " : " "
- anchors.verticalCenter: parent.verticalCenter
- }
- TextField {
- id: spoolCostInput
- text: materialProperties.spool_cost
- }
- }
-
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "Filament weight") }
- Label { width: parent.columnWidth; text: materialProperties.spool_weight + " " + "g" }
-
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "Filament length") }
- Label { width: parent.columnWidth; text: materialProperties.spool_length + " " + "m" }
-
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "Cost per meter") }
- Label { width: parent.columnWidth; text: catalog.i18nc("@label", "approx. %1 %2/m").arg(materialProperties.cost_per_meter).arg(base.currency); }
-
- Item { width: parent.width; height: UM.Theme.getSize("default_margin").height }
-
- Label {
- text: materialProperties.description ? "" + catalog.i18nc("@label", "Information") + "
" + materialProperties.description : "";
- width: parent.width
- wrapMode: Text.WordWrap
- }
- Label {
- text: materialProperties.adhesion_info ? "" + catalog.i18nc("@label", "Adhesion") + "
" + materialProperties.adhesion_info : "";
- width: parent.width
- wrapMode: Text.WordWrap
- }
- }
+ checkable: true
}
- Tab {
- title: catalog.i18nc("@label", "Print settings")
- anchors.margins: UM.Theme.getSize("default_margin").height
+ }
- Grid {
- columns: 2
- spacing: UM.Theme.getSize("default_margin").width
-
- Column {
- Repeater {
- model: base.currentItem ? base.currentItem.settings : null
- Label {
- text: modelData.name.toString();
- elide: Text.ElideMiddle;
- }
- }
- }
- Column {
- Repeater {
- model: base.currentItem ? base.currentItem.settings : null
- Label { text: modelData.value.toString() + " " + modelData.unit.toString(); }
- }
- }
- }
+ MaterialView
+ {
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ top: profileName.bottom
+ topMargin: UM.Theme.getSize("default_margin").height
+ bottom: parent.bottom
}
+
+ editingEnabled: base.currentItem != null && !base.currentItem.readOnly && editButton.checked;
+
+ properties: materialProperties
+ containerId: base.currentItem.id
}
QtObject
@@ -194,17 +162,100 @@ UM.ManagementPage
property string color_name: "Yellow";
property color color_code: "yellow";
- property string density: "Unknown";
- property string diameter: "Unknown";
+ property real density: 0.0;
+ property real diameter: 0.0;
- property string spool_cost: "Unknown";
- property string spool_weight: "Unknown";
- property string spool_length: "Unknown";
- property string cost_per_meter: "Unknown";
+ property real spool_cost: 0.0;
+ property real spool_weight: 0.0;
+ property real spool_length: 0.0;
+ property real cost_per_meter: 0.0;
property string description: "";
property string adhesion_info: "";
}
+
+ UM.ConfirmRemoveDialog
+ {
+ id: confirmDialog
+ object: base.currentItem != null ? base.currentItem.name : ""
+ onYes:
+ {
+ var containers = Cura.ContainerManager.findInstanceContainers({"GUID": base.currentItem.metadata.GUID})
+ for(var i in containers)
+ {
+ Cura.ContainerManager.removeContainer(containers[i])
+ }
+ }
+ }
+
+ FileDialog
+ {
+ id: importDialog;
+ title: catalog.i18nc("@title:window", "Import Material");
+ selectExisting: true;
+ nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
+ folder: CuraApplication.getDefaultPath()
+ onAccepted:
+ {
+ var result = Cura.ContainerManager.importContainer(fileUrl)
+
+ messageDialog.title = catalog.i18nc("@title:window", "Import Material")
+ messageDialog.text = catalog.i18nc("@info:status", "Could not import material %1: %2").arg(fileUrl).arg(result.message)
+ if(result.status == "success")
+ {
+ messageDialog.icon = StandardIcon.Information
+ messageDialog.text = catalog.i18nc("@info:status", "Successfully imported material %1").arg(fileUrl)
+ }
+ else if(result.status == "duplicate")
+ {
+ messageDialog.icon = StandardIcon.Warning
+ }
+ else
+ {
+ messageDialog.icon = StandardIcon.Critical
+ }
+ messageDialog.open()
+ }
+ }
+
+ FileDialog
+ {
+ id: exportDialog;
+ title: catalog.i18nc("@title:window", "Export Material");
+ selectExisting: false;
+ nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
+ folder: CuraApplication.getDefaultPath()
+ onAccepted:
+ {
+ if(base.currentItem.metadata.base_file)
+ {
+ var result = Cura.ContainerManager.exportContainer(base.currentItem.metadata.base_file, selectedNameFilter, fileUrl)
+ }
+ else
+ {
+ var result = Cura.ContainerManager.exportContainer(base.currentItem.id, selectedNameFilter, fileUrl)
+ }
+
+ messageDialog.title = catalog.i18nc("@title:window", "Export Material")
+ if(result.status == "error")
+ {
+ messageDialog.icon = StandardIcon.Critical
+ messageDialog.text = catalog.i18nc("@info:status", "Failed to export material to %1: %2").arg(fileUrl).arg(result.message)
+ messageDialog.open()
+ }
+ else if(result.status == "success")
+ {
+ messageDialog.icon = StandardIcon.Information
+ messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to %1").arg(fileUrl)
+ messageDialog.open()
+ }
+ }
+ }
+
+ MessageDialog
+ {
+ id: messageDialog
+ }
}
onCurrentItemChanged:
@@ -228,13 +279,13 @@ UM.ManagementPage
if(currentItem.metadata.properties != undefined && currentItem.metadata.properties != null)
{
- materialProperties.density = currentItem.metadata.properties.density ? currentItem.metadata.properties.density : "Unknown";
- materialProperties.diameter = currentItem.metadata.properties.diameter ? currentItem.metadata.properties.diameter : "Unknown";
+ materialProperties.density = currentItem.metadata.properties.density ? currentItem.metadata.properties.density : 0.0;
+ materialProperties.diameter = currentItem.metadata.properties.diameter ? currentItem.metadata.properties.diameter : 0.0;
}
else
{
- materialProperties.density = "Unknown";
- materialProperties.diameter = "Unknown";
+ materialProperties.density = 0.0;
+ materialProperties.diameter = 0.0;
}
}
}
diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml
index 1f90d7c889..d6dd66e8a8 100644
--- a/resources/qml/Preferences/ProfilesPage.qml
+++ b/resources/qml/Preferences/ProfilesPage.qml
@@ -13,7 +13,6 @@ UM.ManagementPage
id: base;
title: catalog.i18nc("@title:tab", "Profiles");
- addText: base.currentItem && (base.currentItem.id == Cura.MachineManager.activeQualityId) ? catalog.i18nc("@label", "Create") : catalog.i18nc("@label", "Duplicate")
model: UM.InstanceContainersModel
{
@@ -60,27 +59,62 @@ UM.ManagementPage
return -1;
}
- onActivateObject: Cura.MachineManager.setActiveQuality(currentItem.id)
- onAddObject: {
- var selectedContainer;
- if (objectList.currentItem.id == Cura.MachineManager.activeQualityId) {
- selectedContainer = Cura.MachineManager.newQualityContainerFromQualityAndUser();
- } else {
- selectedContainer = Cura.MachineManager.duplicateContainer(base.currentItem.id);
+ buttons: [
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Activate");
+ iconName: "list-activate";
+ enabled: base.currentItem != null ? base.currentItem.id != Cura.MachineManager.activeQualityId : false;
+ onClicked: Cura.MachineManager.setActiveQuality(base.currentItem.id)
+ },
+ Button
+ {
+ text: base.currentItem && (base.currentItem.id == Cura.MachineManager.activeQualityId) ? catalog.i18nc("@label", "Create") : catalog.i18nc("@label", "Duplicate")
+ iconName: "list-add";
+
+ onClicked:
+ {
+ var selectedContainer;
+ if (objectList.currentItem.id == Cura.MachineManager.activeQualityId) {
+ selectedContainer = Cura.MachineManager.newQualityContainerFromQualityAndUser();
+ } else {
+ selectedContainer = Cura.MachineManager.duplicateContainer(base.currentItem.id);
+ }
+ base.selectContainer(selectedContainer);
+
+ renameDialog.removeWhenRejected = true;
+ renameDialog.open();
+ renameDialog.selectText();
+ }
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Remove");
+ iconName: "list-remove";
+ enabled: base.currentItem != null ? !base.currentItem.readOnly : false;
+ onClicked: confirmDialog.open();
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Rename");
+ iconName: "edit-rename";
+ enabled: base.currentItem != null ? !base.currentItem.readOnly : false;
+ onClicked: { renameDialog.removeWhenRejected = false; renameDialog.open(); renameDialog.selectText(); }
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Import");
+ iconName: "document-import";
+ onClicked: importDialog.open();
+ },
+ Button
+ {
+ text: catalog.i18nc("@action:button", "Export")
+ iconName: "document-export"
+ onClicked: exportDialog.open()
+ enabled: currentItem != null
}
- base.selectContainer(selectedContainer);
-
- renameDialog.removeWhenRejected = true;
- renameDialog.open();
- renameDialog.selectText();
- }
- onRemoveObject: confirmDialog.open();
- onRenameObject: { renameDialog.removeWhenRejected = false; renameDialog.open(); renameDialog.selectText(); }
-
- activateEnabled: currentItem != null ? currentItem.id != Cura.MachineManager.activeQualityId : false;
- addEnabled: currentItem != null;
- removeEnabled: currentItem != null ? !currentItem.readOnly : false;
- renameEnabled: currentItem != null ? !currentItem.readOnly : false;
+ ]
scrollviewCaption: catalog.i18nc("@label %1 is printer name","Printer: %1").arg(Cura.MachineManager.activeMachineName)
@@ -211,24 +245,6 @@ UM.ManagementPage
}
}
- buttons: Row {
-
- Button
- {
- text: catalog.i18nc("@action:button", "Import");
- iconName: "document-import";
- onClicked: importDialog.open();
- }
-
- Button
- {
- text: catalog.i18nc("@action:button", "Export")
- iconName: "document-export"
- onClicked: exportDialog.open()
- enabled: currentItem != null
- }
- }
-
Item
{
UM.I18nCatalog { id: catalog; name: "uranium"; }
diff --git a/resources/qml/Preferences/ReadOnlySpinBox.qml b/resources/qml/Preferences/ReadOnlySpinBox.qml
new file mode 100644
index 0000000000..8692f55708
--- /dev/null
+++ b/resources/qml/Preferences/ReadOnlySpinBox.qml
@@ -0,0 +1,52 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Uranium is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.1
+import QtQuick.Controls 1.1
+import QtQuick.Dialogs 1.2
+
+Item
+{
+ id: base
+
+ property alias value: spinBox.value
+ property alias minimumValue: spinBox.minimumValue
+ property alias maximumValue: spinBox.maximumValue
+ property alias stepSize: spinBox.stepSize
+ property alias prefix: spinBox.prefix
+ property alias suffix: spinBox.suffix
+ property alias decimals: spinBox.decimals
+
+ signal editingFinished();
+
+ property bool readOnly: false
+
+ width: spinBox.width
+ height: spinBox.height
+
+ SpinBox
+ {
+ id: spinBox
+
+ enabled: !base.readOnly
+ opacity: base.readOnly ? 0.5 : 1.0
+
+ anchors.fill: parent
+
+ onEditingFinished: base.editingFinished()
+ }
+
+ Label
+ {
+ visible: base.readOnly
+ text: base.prefix + base.value.toFixed(spinBox.decimals) + base.suffix
+
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: spinBox.__style ? spinBox.__style.padding.left : 0
+
+ color: palette.buttonText
+ }
+
+ SystemPalette { id: palette }
+}
diff --git a/resources/qml/Preferences/ReadOnlyTextArea.qml b/resources/qml/Preferences/ReadOnlyTextArea.qml
new file mode 100644
index 0000000000..cbef8fa46b
--- /dev/null
+++ b/resources/qml/Preferences/ReadOnlyTextArea.qml
@@ -0,0 +1,46 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Uranium is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.1
+import QtQuick.Controls 1.1
+import QtQuick.Dialogs 1.2
+
+Item
+{
+ id: base
+
+ property alias text: textArea.text
+ property alias wrapMode: textArea.wrapMode
+
+ signal editingFinished();
+
+ property bool readOnly: false
+
+ width: textArea.width
+ height: textArea.height
+
+ TextArea
+ {
+ id: textArea
+
+ enabled: !base.readOnly
+ opacity: base.readOnly ? 0.5 : 1.0
+
+ anchors.fill: parent
+
+ onEditingFinished: base.editingFinished()
+ }
+
+ Label
+ {
+ visible: base.readOnly
+ text: textArea.text
+
+ anchors.fill: parent
+ anchors.margins: textArea.__style ? textArea.__style.textMargin : 4
+
+ color: palette.buttonText
+ }
+
+ SystemPalette { id: palette }
+}
diff --git a/resources/qml/Preferences/ReadOnlyTextField.qml b/resources/qml/Preferences/ReadOnlyTextField.qml
new file mode 100644
index 0000000000..28c714259b
--- /dev/null
+++ b/resources/qml/Preferences/ReadOnlyTextField.qml
@@ -0,0 +1,46 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Uranium is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.1
+import QtQuick.Controls 1.1
+import QtQuick.Dialogs 1.2
+
+Item
+{
+ id: base
+
+ property alias text: textField.text
+
+ signal editingFinished();
+
+ property bool readOnly: false
+
+ width: textField.width
+ height: textField.height
+
+ TextField
+ {
+ id: textField
+
+ enabled: !base.readOnly
+ opacity: base.readOnly ? 0.5 : 1.0
+
+ anchors.fill: parent
+
+ onEditingFinished: base.editingFinished()
+ }
+
+ Label
+ {
+ visible: base.readOnly
+ text: textField.text
+
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: textField.__panel ? textField.__panel.leftMargin : 0
+
+ color: palette.buttonText
+ }
+
+ SystemPalette { id: palette }
+}
diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml
new file mode 100644
index 0000000000..c8f56a5b98
--- /dev/null
+++ b/resources/qml/PrintMonitor.qml
@@ -0,0 +1,118 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Styles 1.1
+import QtQuick.Layouts 1.1
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Column
+{
+ id: printMonitor
+
+ Loader
+ {
+ sourceComponent: monitorSection
+ property string label: catalog.i18nc("@label", "Temperatures")
+ }
+ Repeater
+ {
+ model: machineExtruderCount.properties.value
+ delegate: Loader
+ {
+ sourceComponent: monitorItem
+ property string label: machineExtruderCount.properties.value > 1 ? catalog.i18nc("@label", "Hotend Temperature %1").arg(index + 1) : catalog.i18nc("@label", "Hotend Temperature")
+ property string value: printerConnected ? Math.round(Cura.MachineManager.printerOutputDevices[0].hotendTemperatures[index]) + "°C" : ""
+ }
+ }
+ Repeater
+ {
+ model: machineHeatedBed.properties.value == "True" ? 1 : 0
+ delegate: Loader
+ {
+ sourceComponent: monitorItem
+ property string label: catalog.i18nc("@label", "Bed Temperature")
+ property string value: printerConnected ? Math.round(Cura.MachineManager.printerOutputDevices[0].bedTemperature) + "°C" : ""
+ }
+ }
+
+ Loader
+ {
+ sourceComponent: monitorSection
+ property string label: catalog.i18nc("@label", "Active print")
+ }
+ Loader
+ {
+ sourceComponent: monitorItem
+ property string label: catalog.i18nc("@label", "Job Name")
+ property string value: printerConnected ? Cura.MachineManager.printerOutputDevices[0].jobName : ""
+ }
+ Loader
+ {
+ sourceComponent: monitorItem
+ property string label: catalog.i18nc("@label", "Printing Time")
+ property string value: printerConnected ? getPrettyTime(Cura.MachineManager.printerOutputDevices[0].timeTotal) : ""
+ }
+ Loader
+ {
+ sourceComponent: monitorItem
+ property string label: catalog.i18nc("@label", "Estimated time left")
+ property string value: printerConnected ? getPrettyTime(Cura.MachineManager.printerOutputDevices[0].timeTotal - Cura.MachineManager.printerOutputDevices[0].timeElapsed) : ""
+ }
+ Loader
+ {
+ sourceComponent: monitorItem
+ property string label: catalog.i18nc("@label", "Current Layer")
+ property string value: printerConnected ? "0" : ""
+ }
+
+ Component
+ {
+ id: monitorItem
+
+ Row
+ {
+ height: UM.Theme.getSize("setting_control").height
+ Label
+ {
+ text: label
+ color: printerConnected ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text")
+ font: UM.Theme.getFont("default")
+ width: base.width * 0.4
+ elide: Text.ElideRight
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ Label
+ {
+ text: value
+ color: printerConnected ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text")
+ font: UM.Theme.getFont("default")
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+ Component
+ {
+ id: monitorSection
+
+ Rectangle
+ {
+ color: UM.Theme.getColor("setting_category")
+ width: base.width - 2 * UM.Theme.getSize("default_margin").width
+ height: UM.Theme.getSize("section").height
+
+ Label
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ text: label
+ font: UM.Theme.getFont("setting_category")
+ color: UM.Theme.getColor("setting_category_text")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml
index d9b3b56109..ebf75e7a49 100644
--- a/resources/qml/Sidebar.qml
+++ b/resources/qml/Sidebar.qml
@@ -285,122 +285,16 @@ Rectangle
}
}
- // Item that shows the print monitor properties
- Column
+ Loader
{
- id: printMonitor
-
anchors.bottom: footerSeparator.top
anchors.top: monitorLabel.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: base.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: base.right
- visible: monitoringPrint
-
- Loader
- {
- sourceComponent: monitorSection
- property string label: catalog.i18nc("@label", "Temperatures")
- }
- Repeater
- {
- model: machineExtruderCount.properties.value
- delegate: Loader
- {
- sourceComponent: monitorItem
- property string label: machineExtruderCount.properties.value > 1 ? catalog.i18nc("@label", "Hotend Temperature %1").arg(index + 1) : catalog.i18nc("@label", "Hotend Temperature")
- property string value: printerConnected ? Math.round(Cura.MachineManager.printerOutputDevices[0].hotendTemperatures[index]) + "°C" : ""
- }
- }
- Repeater
- {
- model: machineHeatedBed.properties.value == "True" ? 1 : 0
- delegate: Loader
- {
- sourceComponent: monitorItem
- property string label: catalog.i18nc("@label", "Bed Temperature")
- property string value: printerConnected ? Math.round(Cura.MachineManager.printerOutputDevices[0].bedTemperature) + "°C" : ""
- }
- }
-
- Loader
- {
- sourceComponent: monitorSection
- property string label: catalog.i18nc("@label", "Active print")
- }
- Loader
- {
- sourceComponent: monitorItem
- property string label: catalog.i18nc("@label", "Job Name")
- property string value: printerConnected ? Cura.MachineManager.printerOutputDevices[0].jobName : ""
- }
- Loader
- {
- sourceComponent: monitorItem
- property string label: catalog.i18nc("@label", "Printing Time")
- property string value: printerConnected ? getPrettyTime(Cura.MachineManager.printerOutputDevices[0].timeTotal) : ""
- }
- Loader
- {
- sourceComponent: monitorItem
- property string label: catalog.i18nc("@label", "Estimated time left")
- property string value: printerConnected ? getPrettyTime(Cura.MachineManager.printerOutputDevices[0].timeTotal - Cura.MachineManager.printerOutputDevices[0].timeElapsed) : ""
- }
- Loader
- {
- sourceComponent: monitorItem
- property string label: catalog.i18nc("@label", "Current Layer")
- property string value: printerConnected ? "0" : ""
- }
-
- Component
- {
- id: monitorItem
-
- Row
- {
- height: UM.Theme.getSize("setting_control").height
- Label
- {
- text: label
- color: printerConnected ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text")
- font: UM.Theme.getFont("default")
- width: base.width * 0.4
- elide: Text.ElideRight
- anchors.verticalCenter: parent.verticalCenter
- }
- Label
- {
- text: value
- color: printerConnected ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text")
- font: UM.Theme.getFont("default")
- anchors.verticalCenter: parent.verticalCenter
- }
- }
- }
- Component
- {
- id: monitorSection
-
- Rectangle
- {
- color: UM.Theme.getColor("setting_category")
- width: base.width - 2 * UM.Theme.getSize("default_margin").width
- height: UM.Theme.getSize("section").height
-
- Label
- {
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- text: label
- font: UM.Theme.getFont("setting_category")
- color: UM.Theme.getColor("setting_category_text")
- }
- }
- }
- }
+ source: monitoringPrint ? "PrintMonitor.qml": "SidebarContents.qml"
+ }
Rectangle
{
diff --git a/resources/qml/SidebarContents.qml b/resources/qml/SidebarContents.qml
new file mode 100644
index 0000000000..c53818a9ce
--- /dev/null
+++ b/resources/qml/SidebarContents.qml
@@ -0,0 +1,43 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Styles 1.1
+import QtQuick.Layouts 1.1
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+StackView
+{
+ id: sidebarContents
+
+ delegate: StackViewDelegate
+ {
+ function transitionFinished(properties)
+ {
+ properties.exitItem.opacity = 1
+ }
+
+ pushTransition: StackViewTransition
+ {
+ PropertyAnimation
+ {
+ target: enterItem
+ property: "opacity"
+ from: 0
+ to: 1
+ duration: 100
+ }
+ PropertyAnimation
+ {
+ target: exitItem
+ property: "opacity"
+ from: 1
+ to: 0
+ duration: 100
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml
index 82dba70b92..aa6f2c0067 100644
--- a/resources/qml/SidebarHeader.qml
+++ b/resources/qml/SidebarHeader.qml
@@ -8,6 +8,8 @@ import QtQuick.Controls.Styles 1.1
import UM 1.2 as UM
import Cura 1.0 as Cura
+import "Menus"
+
Column
{
id: base;
@@ -56,34 +58,7 @@ Column
width: parent.width * 0.55 + UM.Theme.getSize("default_margin").width
- menu: Menu
- {
- id: machineSelectionMenu
- Instantiator
- {
- model: UM.ContainerStacksModel
- {
- filter: {"type": "machine"}
- }
- MenuItem
- {
- text: model.name;
- checkable: true;
- checked: Cura.MachineManager.activeMachineId == model.id
- exclusiveGroup: machineSelectionMenuGroup;
- onTriggered: Cura.MachineManager.setActiveMachine(model.id);
- }
- onObjectAdded: machineSelectionMenu.insertItem(index, object)
- onObjectRemoved: machineSelectionMenu.removeItem(object)
- }
-
- ExclusiveGroup { id: machineSelectionMenuGroup; }
-
- MenuSeparator { }
-
- MenuItem { action: Cura.Actions.addMachine; }
- MenuItem { action: Cura.Actions.configureMachines; }
- }
+ menu: PrinterMenu { }
}
}
@@ -236,37 +211,7 @@ Column
anchors.left: parent.left
style: UM.Theme.styles.sidebar_header_button
- menu: Menu
- {
- id: variantsSelectionMenu
- Instantiator
- {
- id: variantSelectionInstantiator
- model: UM.InstanceContainersModel
- {
- filter:
- {
- "type": "variant",
- "definition": Cura.MachineManager.activeDefinitionId //Only show variants of this machine
- }
- }
- MenuItem
- {
- text: model.name;
- checkable: true;
- checked: model.id == Cura.MachineManager.activeVariantId;
- exclusiveGroup: variantSelectionMenuGroup;
- onTriggered:
- {
- Cura.MachineManager.setActiveVariant(model.id);
- }
- }
- onObjectAdded: variantsSelectionMenu.insertItem(index, object)
- onObjectRemoved: variantsSelectionMenu.removeItem(object)
- }
-
- ExclusiveGroup { id: variantSelectionMenuGroup; }
- }
+ menu: NozzleMenu { }
}
ToolButton {
@@ -281,49 +226,7 @@ Column
anchors.right: parent.right
style: UM.Theme.styles.sidebar_header_button
- menu: Menu
- {
- id: materialSelectionMenu
- Instantiator
- {
- id: materialSelectionInstantiator
- model: UM.InstanceContainersModel
- {
- filter:
- {
- var result = { "type": "material" }
- if(Cura.MachineManager.filterMaterialsByMachine)
- {
- result.definition = Cura.MachineManager.activeDefinitionId
- if(Cura.MachineManager.hasVariants)
- {
- result.variant = Cura.MachineManager.activeVariantId
- }
- }
- else
- {
- result.definition = "fdmprinter"
- }
- return result
- }
- }
- MenuItem
- {
- text: model.name;
- checkable: true;
- checked: model.id == Cura.MachineManager.activeMaterialId;
- exclusiveGroup: materialSelectionMenuGroup;
- onTriggered:
- {
- Cura.MachineManager.setActiveMaterial(model.id);
- }
- }
- onObjectAdded: materialSelectionMenu.insertItem(index, object)
- onObjectRemoved: materialSelectionMenu.removeItem(object)
- }
-
- ExclusiveGroup { id: materialSelectionMenuGroup; }
- }
+ menu: MaterialMenu { }
}
}
}
@@ -360,88 +263,7 @@ Column
tooltip: Cura.MachineManager.activeQualityName
style: UM.Theme.styles.sidebar_header_button
- menu: Menu
- {
- id: profileSelectionMenu
- Instantiator
- {
- id: profileSelectionInstantiator
- model: UM.InstanceContainersModel
- {
- filter:
- {
- var result = { "type": "quality" };
- if(Cura.MachineManager.filterQualityByMachine)
- {
- result.definition = Cura.MachineManager.activeDefinitionId;
- if(Cura.MachineManager.hasMaterials)
- {
- result.material = Cura.MachineManager.activeMaterialId;
- }
- }
- else
- {
- result.definition = "fdmprinter"
- }
- return result
- }
- }
- property int separatorIndex: -1
-
- Loader {
- property QtObject model_data: model
- property int model_index: index
- sourceComponent: menuItemDelegate
- }
- onObjectAdded:
- {
- //Insert a separator between readonly and custom profiles
- if(separatorIndex < 0 && index > 0)
- {
- if(model.getItem(index-1).readOnly != model.getItem(index).readOnly)
- {
- profileSelectionMenu.insertSeparator(index);
- separatorIndex = index;
- }
- }
- //Because of the separator, custom profiles move one index lower
- profileSelectionMenu.insertItem((model.getItem(index).readOnly) ? index : index + 1, object.item);
- }
- onObjectRemoved:
- {
- //When adding a profile, the menu is rebuilt by removing all items.
- //If a separator was added, we need to remove that too.
- if(separatorIndex >= 0)
- {
- profileSelectionMenu.removeItem(profileSelectionMenu.items[separatorIndex])
- separatorIndex = -1;
- }
- profileSelectionMenu.removeItem(object.item);
- }
- }
- ExclusiveGroup { id: profileSelectionMenuGroup; }
-
- Component
- {
- id: menuItemDelegate
- MenuItem
- {
- id: item
- text: model_data ? model_data.name : ""
- checkable: true
- checked: model_data != null ? Cura.MachineManager.activeQualityId == model_data.id : false
- exclusiveGroup: profileSelectionMenuGroup;
- onTriggered: Cura.MachineManager.setActiveQuality(model_data.id)
- }
- }
-
- MenuSeparator { }
- MenuItem { action: Cura.Actions.addProfile }
- MenuItem { action: Cura.Actions.updateProfile }
- MenuItem { action: Cura.Actions.resetProfile }
- MenuSeparator { }
- MenuItem { action: Cura.Actions.manageProfiles }
- }
+ menu: ProfileMenu { }
UM.SimpleButton
{