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 {