From 24e165b964b55d0dccd66227cbb4a746dd5f9bfc Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 14 Jun 2016 10:41:06 +0200 Subject: [PATCH 1/5] Remove error on ProfilesPage CURA-1585 --- resources/qml/Preferences/ProfilesPage.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index 670bf79956..c2ad84b581 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -39,7 +39,6 @@ UM.ManagementPage section.property: "readOnly" section.delegate: Rectangle { - width: objectListContainer.viewport.width; height: childrenRect.height; Label From 4ece9b0997ded3b9698ec749ef9548c4f934744e Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 14 Jun 2016 10:42:33 +0200 Subject: [PATCH 2/5] Add swatch to extruder tabs CURA-340 --- resources/qml/SidebarHeader.qml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 282068ee2c..166c1fbe6d 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -141,6 +141,20 @@ Column control.hovered ? UM.Theme.getColor("toggle_hovered") : UM.Theme.getColor("toggle_unchecked") Behavior on color { ColorAnimation { duration: 50; } } + Rectangle + { + id: swatch + height: UM.Theme.getSize("setting_control").height / 2 + width: height + anchors.left: parent.left + anchors.leftMargin: (parent.height - height) / 2 + anchors.verticalCenter: parent.verticalCenter + + color: model.colour + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("toggle_checked") + } + Label { anchors.centerIn: parent From 7faae685e5730becc613fc64a2524fb95ff56243 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 14 Jun 2016 10:58:26 +0200 Subject: [PATCH 3/5] Use different default colors for each extruder CURA-340 --- cura/ExtrudersModel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cura/ExtrudersModel.py b/cura/ExtrudersModel.py index 960c05bd5e..3ba6c5a99a 100644 --- a/cura/ExtrudersModel.py +++ b/cura/ExtrudersModel.py @@ -28,9 +28,9 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): # containers. IndexRole = Qt.UserRole + 4 - ## Colour to display if there is no material or the material has no known + ## List of colours to display if there is no material or the material has no known # colour. - defaultColour = "#FFFF00" + defaultColours = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"] ## Initialises the extruders model, defining the roles and listening for # changes in the data. @@ -75,7 +75,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): if self._add_global: material = global_container_stack.findContainer({ "type": "material" }) - colour = material.getMetaDataEntry("color_code", default = self.defaultColour) if material else self.defaultColour + colour = material.getMetaDataEntry("color_code", default = self.defaultColours[0]) if material else self.defaultColours[0] item = { "id": global_container_stack.getId(), "name": "Global", @@ -86,12 +86,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): for extruder in manager.getMachineExtruders(global_container_stack.getBottom().getId()): material = extruder.findContainer({ "type": "material" }) - colour = material.getMetaDataEntry("color_code", default = self.defaultColour) if material else self.defaultColour position = extruder.getBottom().getMetaDataEntry("position", default = "0") #Position in the definition. try: position = int(position) except ValueError: #Not a proper int. position = -1 + default_colour = self.defaultColours[position] if position >= 0 and position < len(self.defaultColours) else defaultColours[0] + colour = material.getMetaDataEntry("color_code", default = default_colour) if material else default_colour item = { #Construct an item with only the relevant information. "id": extruder.getId(), "name": extruder.getName(), From 2498e7110d45faee570cb763a09d38e107bfb614 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 14 Jun 2016 11:05:50 +0200 Subject: [PATCH 4/5] Allow setting the diffuse_color with a uniform binding CURA-345 --- resources/shaders/overhang.shader | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/shaders/overhang.shader b/resources/shaders/overhang.shader index 4ae2821c7d..99cbdf913d 100644 --- a/resources/shaders/overhang.shader +++ b/resources/shaders/overhang.shader @@ -74,6 +74,7 @@ u_viewProjectionMatrix = view_projection_matrix u_normalMatrix = normal_matrix u_viewPosition = view_position u_lightPosition = light_0_position +u_diffuseColor = diffuse_color [attributes] a_vertex = vertex From 175b5429b45457818e3e3faed15a50977ce4101c Mon Sep 17 00:00:00 2001 From: Simon Edwards Date: Tue, 14 Jun 2016 11:44:11 +0200 Subject: [PATCH 5/5] Refactor the profile and Cura specific import/export code, put the cura stuff in Cura itself. Contributes to CURA-1667 Profile import/export --- cura/CuraContainerRegistry.py | 167 +++++++++++++++++++++ cura_app.py | 4 + resources/qml/Preferences/ProfilesPage.qml | 4 +- 3 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 cura/CuraContainerRegistry.py diff --git a/cura/CuraContainerRegistry.py b/cura/CuraContainerRegistry.py new file mode 100644 index 0000000000..66ef5c3695 --- /dev/null +++ b/cura/CuraContainerRegistry.py @@ -0,0 +1,167 @@ +# Copyright (c) 2016 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +import os +from PyQt5.QtWidgets import QMessageBox + +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Application import Application +from UM.Logger import Logger +from UM.Message import Message +from UM.Platform import Platform +from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with. +from UM.Util import parseBool + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("uranium") + +class CuraContainerRegistry(ContainerRegistry): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + ## Exports an profile to a file + # + # \param instance_id \type{str} the ID of the profile to export. + # \param file_name \type{str} the full path and filename to export to. + # \param file_type \type{str} the file type with the format " (*.)" + def exportProfile(self, instance_id, file_name, file_type): + Logger.log('d', 'exportProfile instance_id: '+str(instance_id)) + + # Parse the fileType to deduce what plugin can save the file format. + # fileType has the format " (*.)" + split = file_type.rfind(" (*.") # Find where the description ends and the extension starts. + if split < 0: # Not found. Invalid format. + Logger.log("e", "Invalid file format identifier %s", file_type) + return + description = file_type[:split] + extension = file_type[split + 4:-1] # Leave out the " (*." and ")". + if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any. + file_name += "." + extension + + # On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself. + if not Platform.isWindows(): + if os.path.exists(file_name): + result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"), + catalog.i18nc("@label", "The file {0} already exists. Are you sure you want to overwrite it?").format(file_name)) + if result == QMessageBox.No: + return + + containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id) + if not containers: + return + container = containers[0] + + profile_writer = self._findProfileWriter(extension, description) + + try: + success = profile_writer.write(file_name, container) + except Exception as e: + Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e)) + m = Message(catalog.i18nc("@info:status", "Failed to export profile to {0}: {1}", file_name, str(e)), lifetime = 0) + m.show() + return + if not success: + Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name) + m = Message(catalog.i18nc("@info:status", "Failed to export profile to {0}: Writer plugin reported failure.", file_name), lifetime = 0) + m.show() + return + m = Message(catalog.i18nc("@info:status", "Exported profile to {0}", file_name)) + m.show() + + ## Gets the plugin object matching the criteria + # \param extension + # \param description + # \return The plugin object matching the given extension and description. + def _findProfileWriter(self, extension, description): + pr = PluginRegistry.getInstance() + for plugin_id, meta_data in self._getIOPlugins("profile_writer"): + for supported_type in meta_data["profile_writer"]: # All file types this plugin can supposedly write. + supported_extension = supported_type.get("extension", None) + if supported_extension == extension: # This plugin supports a file type with the same extension. + supported_description = supported_type.get("description", None) + if supported_description == description: # The description is also identical. Assume it's the same file type. + return pr.getPluginObject(plugin_id) + return None + + ## Imports a profile from a file + # + # \param file_name \type{str} the full path and filename of the profile to import + # \return \type{Dict} dict with a 'status' key containing the string 'ok' or 'error', and a 'message' key + # containing a message for the user + def importProfile(self, file_name): + if not file_name: + return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from {0}: {1}", file_name, "Invalid path")} + + pr = PluginRegistry.getInstance() + for plugin_id, meta_data in self._getIOPlugins("profile_reader"): + profile_reader = pr.getPluginObject(plugin_id) + try: + profile = profile_reader.read(file_name) #Try to open the file with the profile reader. + except Exception as e: + #Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None. + Logger.log("e", "Failed to import profile from %s: %s", file_name, str(e)) + return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from {0}: {1}", file_name, str(e))} + if profile: #Success! + profile.setReadOnly(False) + + if self._machineHasOwnQualities(): + profile.setDefinition(self._activeDefinition()) + if self._machineHasOwnMaterials(): + profile.addMetaDataEntry("material", self._activeMaterialId()) + else: + profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0]) + ContainerRegistry.getInstance().addContainer(profile) + + return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) } + + #If it hasn't returned by now, none of the plugins loaded the profile successfully. + return { "status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type.", file_name)} + + ## Gets a list of profile writer plugins + # \return List of tuples of (plugin_id, meta_data). + def _getIOPlugins(self, io_type): + pr = PluginRegistry.getInstance() + active_plugin_ids = pr.getActivePlugins() + + result = [] + for plugin_id in active_plugin_ids: + meta_data = pr.getMetaData(plugin_id) + if io_type in meta_data: + result.append( (plugin_id, meta_data) ) + return result + + ## Gets the active definition + # \return the active definition object or None if there is no definition + def _activeDefinition(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + definition = global_container_stack.getBottom() + if definition: + return definition + return None + + ## Returns true if the current machine requires its own materials + # \return True if the current machine requires its own materials + def _machineHasOwnMaterials(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + return global_container_stack.getMetaDataEntry("has_materials", False) + return False + + ## Gets the ID of the active material + # \return the ID of the active material or the empty string + def _activeMaterialId(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + material = global_container_stack.findContainer({"type": "material"}) + if material: + return material.getId() + return "" + + ## Returns true if the current machien requires its own quality profiles + # \return true if the current machien requires its own quality profiles + def _machineHasOwnQualities(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False)) + return False diff --git a/cura_app.py b/cura_app.py index 3bcce18fb5..15bdf10ad6 100755 --- a/cura_app.py +++ b/cura_app.py @@ -34,6 +34,7 @@ sys.excepthook = exceptHook # tries to create PyQt objects on a non-main thread. import Arcus #@UnusedImport import cura.CuraApplication +import cura.CuraContainerRegistry if sys.platform == "win32" and hasattr(sys, "frozen"): dirpath = os.path.expanduser("~/AppData/Local/cura/") @@ -41,5 +42,8 @@ if sys.platform == "win32" and hasattr(sys, "frozen"): sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w") sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w") +# Force an instance of CuraContainerRegistry to be created and reused later. +cura.CuraContainerRegistry.CuraContainerRegistry.getInstance() + app = cura.CuraApplication.CuraApplication.getInstance() app.run() diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index c2ad84b581..1f90d7c889 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -264,7 +264,7 @@ UM.ManagementPage id: importDialog; title: catalog.i18nc("@title:window", "Import Profile"); selectExisting: true; - nameFilters: base.model.getFileNameFiltersRead() + nameFilters: base.model.getFileNameFilters("profile_reader") folder: base.model.getDefaultPath() onAccepted: { @@ -291,7 +291,7 @@ UM.ManagementPage id: exportDialog; title: catalog.i18nc("@title:window", "Export Profile"); selectExisting: false; - nameFilters: base.model.getFileNameFiltersWrite() + nameFilters: base.model.getFileNameFilters("profile_writer") folder: base.model.getDefaultPath() onAccepted: {