From 3cc31fd9c2e97e6c64e9c8198e7337013c8a77dd Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 22 Jun 2016 17:39:50 +0200 Subject: [PATCH 01/52] When saving containers, use the mime type's preferred suffix instead of a hardcoded value Contributes to CURA-342 --- cura/CuraApplication.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 5f5880e3d5..9ab580eaf2 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -251,7 +251,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": @@ -279,7 +280,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": From a3ea042d4b1374a99d9870147b6c39e3ccd449c5 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 22 Jun 2016 17:40:17 +0200 Subject: [PATCH 02/52] Implement serialization of XmlMaterialProfile Contributes to CURA-342 --- .../XmlMaterialProfile/XmlMaterialProfile.py | 151 +++++++++++++++++- 1 file changed, 148 insertions(+), 3 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 89c7d76e9f..a1a992334e 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -3,10 +3,13 @@ import math import copy +import io import xml.etree.ElementTree as ET from UM.Logger import Logger +import UM.Dictionary + import UM.Settings # The namespace is prepended to the tag name but between {}. @@ -20,7 +23,131 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): super().__init__(container_id, *args, **kwargs) def serialize(self): - raise NotImplementedError("Writing material profiles has not yet been implemented") + if self.getDefinition().id != "fdmprinter": + # 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", "") + + ## Begin Name Block + builder.start("name") + + builder.start("brand") + builder.data(metadata.pop("brand", "")) + builder.end("brand") + + builder.start("material") + builder.data(self.getName()) + 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") + + for instance in self.findInstances(): + builder.start("setting", { "key": UM.Dictionary.findKey(self.__material_property_setting_map, instance.definition.key) }) + builder.data(str(instance.value)) + builder.end("setting") + + # Find all machine sub-profiles corresponding to this material and add them to this profile. + machines = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = self.getId() + "_*") + for machine in machines: + if machine.getMetaDataEntry("variant"): + # Since the list includes variant-specific containers but we do not yet want to add those, we just skip them. + continue + + builder.start("machine") + + definition = machine.getDefinition() + builder.start("machine_identifier", { "manufacturer": definition.getMetaDataEntry("manufacturer", ""), "product": UM.Dictionary.findKey(self.__product_id_map, definition.id) }) + builder.end("machine_identifier") + + for instance in machine.findInstances(): + if 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 + + builder.start("setting", { "key": UM.Dictionary.findKey(self.__material_property_setting_map, instance.definition.key) }) + builder.data(str(instance.value)) + builder.end("setting") + + # Find all hotend sub-profiles corresponding to this material and machine and add them to this profile. + hotends = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = machine.getId() + "_*") + for hotend in hotends: + variant_containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = hotend.getMetaDataEntry("variant")) + if variant_containers: + builder.start("hotend", { "id": variant_containers[0].getName() }) + + for instance in hotend.findInstances(): + if 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 + + if machine.getInstance(instance.definition.key) and machine.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 + + builder.start("setting", { "key": UM.Dictionary.findKey(self.__material_property_setting_map, instance.definition.key) }) + builder.data(str(instance.value)) + builder.end("setting") + + 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() def deserialize(self, serialized): data = ET.fromstring(serialized) @@ -28,7 +155,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): 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 +166,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) @@ -83,6 +210,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 = {} @@ -187,3 +316,19 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): __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 From f71ddc4b9ff3e300284d43af18eda5113b9122f0 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 23 Jun 2016 17:31:50 +0200 Subject: [PATCH 03/52] Start implementing view and edit support in the materials page Contributes to CURA-342 --- resources/qml/Preferences/MaterialView.qml | 183 ++++++++++++++++++++ resources/qml/Preferences/MaterialsPage.qml | 165 +++++------------- 2 files changed, 222 insertions(+), 126 deletions(-) create mode 100644 resources/qml/Preferences/MaterialView.qml diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml new file mode 100644 index 0000000000..ac3e4e1e42 --- /dev/null +++ b/resources/qml/Preferences/MaterialView.qml @@ -0,0 +1,183 @@ +// 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 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +TabView +{ + id: base + + property QtObject properties; + + property bool editingEnabled; + + property string currency: UM.Preferences.getValue("general/currency") ? UM.Preferences.getValue("general/currency") : "€" + + Tab + { + title: "Information" + + ScrollView + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + + Flow + { + id: containerGrid + + width: base.width - UM.Theme.getSize("default_margin").width * 4; + + property real firstColumnWidth: width * 0.5 + property real secondColumnWidth: width * 0.4 + + property real rowHeight: textField.height; + + Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Brand") } + TextField { id: textField; width: parent.secondColumnWidth; text: properties.supplier; readOnly: !base.editingEnabled; } + + Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") } + TextField { width: parent.secondColumnWidth; text: properties.material_type; readOnly: !base.editingEnabled; } + + Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") } + + Row + { + width: parent.secondColumnWidth; + height: parent.rowHeight; + spacing: UM.Theme.getSize("default_margin").width/2 + + Rectangle + { + id: colorSelector + color: properties.color_code + 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 } + } + TextField { id: colorLabel; text: properties.color_name; readOnly: !base.editingEnabled } + + 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: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Density") } + ReadOnlySpinBox + { + width: parent.secondColumnWidth; + value: properties.density; + decimals: 2 + suffix: "g/cm" + stepSize: 0.01 + readOnly: !base.editingEnabled; + } + + Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Diameter") } + ReadOnlySpinBox + { + width: parent.secondColumnWidth; + value: properties.diameter; + decimals: 2 + suffix: "mm³" + stepSize: 0.01 + readOnly: !base.editingEnabled; + } + + Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament Cost") } + ReadOnlySpinBox + { + width: parent.secondColumnWidth; + value: properties.spool_cost; + prefix: base.currency + readOnly: !base.editingEnabled; + } + + Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") } + ReadOnlySpinBox + { + width: parent.secondColumnWidth; + value: properties.spool_weight; + suffix: "g"; + stepSize: 10 + readOnly: !base.editingEnabled; + } + + Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") } + ReadOnlySpinBox + { + width: parent.secondColumnWidth; + value: parseFloat(properties.spool_length); + suffix: "m"; + readOnly: !base.editingEnabled; + } + + Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter (Approx.)") } + ReadOnlySpinBox + { + width: parent.secondColumnWidth; + value: parseFloat(properties.cost_per_meter); + suffix: catalog.i18nc("@label", "%1/m".arg(base.currency)); + readOnly: !base.editingEnabled; + } + + 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") } + + TextArea + { + text: properties.description; + width: parent.firstColumnWidth + parent.secondColumnWidth + wrapMode: Text.WordWrap + + readOnly: !base.editingEnabled; + } + + Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Adhesion Information") } + + TextArea + { + text: properties.adhesion_info; + width: parent.firstColumnWidth + parent.secondColumnWidth + wrapMode: Text.WordWrap + + readOnly: !base.editingEnabled; + } + } + } + } + + Tab + { + title: catalog.i18nc("@label", "Print settings") + anchors.margins: UM.Theme.getSize("default_margin").height + + ScrollView + { + anchors.fill: parent; + + ListView + { + model: UM.SettingDefinitionsModel + { + containerId: Cura.MachineManager.activeDefinitionId + visibilityHandler: UM.SettingPreferenceVisibilityHandler { } + } + + delegate: Cura.SettingDelegate { } + } + } + } +} diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index af0f0c1bd2..36451e1717 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -52,134 +52,45 @@ UM.ManagementPage scrollviewCaption: " " detailsVisible: true - property string currency: UM.Preferences.getValue("general/currency") - Item { UM.I18nCatalog { id: catalog; name: "cura"; } 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 - - 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: editButton.checked; + + properties: materialProperties } QtObject @@ -194,13 +105,15 @@ 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; + onDensityChanged: console.log(density); + property real diameter: 0.0; + onDiameterChanged: console.log(diameter); - 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: ""; @@ -228,13 +141,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; } } } From 1cb836aea5f55ac1778e005609a94c456180ef73 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 23 Jun 2016 17:33:55 +0200 Subject: [PATCH 04/52] Add a ReadOnlySpinBox control that provides a spinBox with a readOnly property Contributes to CURA-342 --- resources/qml/Preferences/ReadOnlySpinBox.qml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 resources/qml/Preferences/ReadOnlySpinBox.qml diff --git a/resources/qml/Preferences/ReadOnlySpinBox.qml b/resources/qml/Preferences/ReadOnlySpinBox.qml new file mode 100644 index 0000000000..5c1a3cbe19 --- /dev/null +++ b/resources/qml/Preferences/ReadOnlySpinBox.qml @@ -0,0 +1,23 @@ +// 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 + +// Provides a SpinBox with the same readOnly property as a TextField +SpinBox +{ + id: base + property bool readOnly: false + + Keys.enabled: !readOnly + MouseArea + { + acceptedButtons: Qt.AllButtons; + anchors.fill: parent; + enabled: parent.readOnly; + onWheel: wheel.accepted = true; + cursorShape: enabled ? Qt.ArrowCursor : Qt.IBeamCursor; + } +} From b2ef607cb6c251e9f1cead83e31e8e00581bbc6f Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 23 Jun 2016 17:34:35 +0200 Subject: [PATCH 05/52] Fix the material setting list to use a simple placeholder Contributes to CURA-342 --- resources/qml/Preferences/MaterialView.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index ac3e4e1e42..41afae9eac 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -174,9 +174,10 @@ TabView { containerId: Cura.MachineManager.activeDefinitionId visibilityHandler: UM.SettingPreferenceVisibilityHandler { } + expanded: ["*"] } - delegate: Cura.SettingDelegate { } + delegate: Label { text: model.label } } } } From 68a8bcb009562434be867ef2c473ebd131e5a18d Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 27 Jun 2016 13:33:38 +0200 Subject: [PATCH 06/52] Fix width of materials properties tab --- resources/qml/Preferences/MaterialView.qml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index 41afae9eac..c662895584 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -2,7 +2,7 @@ // Uranium is released under the terms of the AGPLv3 or higher. import QtQuick 2.1 -import QtQuick.Controls 1.1 +import QtQuick.Controls 1.3 import QtQuick.Dialogs 1.2 import UM 1.2 as UM @@ -27,13 +27,15 @@ TabView anchors.fill: parent anchors.margins: UM.Theme.getSize("default_margin").width + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + Flow { id: containerGrid - width: base.width - UM.Theme.getSize("default_margin").width * 4; + width: base.width; - property real firstColumnWidth: width * 0.5 + property real firstColumnWidth: width * 0.45 property real secondColumnWidth: width * 0.4 property real rowHeight: textField.height; From a746710e26a57be7ae4a641d46efe648381f28e2 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 28 Jun 2016 17:30:46 +0200 Subject: [PATCH 07/52] Introduce a MaterialSettingsVisibilityHandler that will only show those settings relevant for materials Contriubtes to CURA-342 --- cura/CuraApplication.py | 3 +++ .../MaterialSettingsVisibilityHandler.py | 19 +++++++++++++++++++ cura/Settings/__init__.py | 2 ++ 3 files changed, 24 insertions(+) create mode 100644 cura/Settings/MaterialSettingsVisibilityHandler.py create mode 100644 cura/Settings/__init__.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c12ce3b8e5..9970f0efce 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -46,6 +46,8 @@ from . import MachineManagerModel from . import ContainerSettingsModel 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 @@ -424,6 +426,7 @@ class CuraApplication(QtApplication): qmlRegisterType(ExtrudersModel.ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(ContainerSettingsModel.ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel") + qmlRegisterType(cura.Settings.MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") qmlRegisterSingletonType(QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")), "Cura", 1, 0, "Actions") 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/Settings/__init__.py b/cura/Settings/__init__.py new file mode 100644 index 0000000000..d6fe389478 --- /dev/null +++ b/cura/Settings/__init__.py @@ -0,0 +1,2 @@ + +from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler From 842015ea10aeba8c60bd08613b6ba6562e362950 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 28 Jun 2016 17:32:50 +0200 Subject: [PATCH 08/52] Move all the menus to individual QML files to make it easier to reuse them Contributes to CURA-342 --- resources/qml/Cura.qml | 32 +--- resources/qml/Menus/MaterialMenu.qml | 57 +++++++ resources/qml/Menus/NozzleMenu.qml | 37 +++++ resources/qml/Menus/PrinterMenu.qml | 38 +++++ resources/qml/Menus/ProfileMenu.qml | 86 +++++++++++ resources/qml/Menus/RecentFilesMenu.qml | 37 +++++ resources/qml/Menus/ViewMenu.qml | 29 ++++ resources/qml/SidebarHeader.qml | 190 +----------------------- 8 files changed, 293 insertions(+), 213 deletions(-) create mode 100644 resources/qml/Menus/MaterialMenu.qml create mode 100644 resources/qml/Menus/NozzleMenu.qml create mode 100644 resources/qml/Menus/PrinterMenu.qml create mode 100644 resources/qml/Menus/ProfileMenu.qml create mode 100644 resources/qml/Menus/RecentFilesMenu.qml create mode 100644 resources/qml/Menus/ViewMenu.qml diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index a27c232e5e..c41d80461a 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 @@ -417,14 +419,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; @@ -434,7 +431,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 @@ -442,9 +438,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; } @@ -484,27 +478,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 diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml new file mode 100644 index 0000000000..4af8246930 --- /dev/null +++ b/resources/qml/Menus/MaterialMenu.qml @@ -0,0 +1,57 @@ +// 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: model.name; + 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/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index a2304d1d3e..4aad341ee0 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 { } } } @@ -235,37 +210,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 { @@ -280,49 +225,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 { } } } } @@ -359,88 +262,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 { From 1a56da464c73e3b6cd48433619a79ed110b5c1a1 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 28 Jun 2016 17:42:04 +0200 Subject: [PATCH 09/52] Introduce a "Manage Materials" action Contributes to CURA-342 --- resources/qml/Actions.qml | 9 +++++++++ resources/qml/Cura.qml | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 7e03bd7102..45ba20c973 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; @@ -107,6 +109,13 @@ Item iconName: "configure"; } + Action + { + id: manageMaterialsAction + text: catalog.i18nc("@action:inmenu", "Manage Materials...") + iconName: "configure" + } + Action { id: updateProfileAction; diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index c41d80461a..b754d3a1d7 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -582,6 +582,16 @@ UM.MainWindow } } + Connections + { + target: Cura.Actions.manageMaterials + onTriggered: + { + preferences.visible = true; + preferences.setPage(3) + } + } + Connections { target: Cura.Actions.configureSettingVisibility From 0270aecdc342c170dcd8f1000c831bb52322bab2 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 28 Jun 2016 17:43:28 +0200 Subject: [PATCH 10/52] Replace separate machine/profile menus in menubar with a single "Settings" menu Contributes to CURA-342 --- resources/qml/Actions.qml | 3 +- resources/qml/Cura.qml | 218 ++++++-------------------------------- 2 files changed, 37 insertions(+), 184 deletions(-) diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 45ba20c973..67bc5fe149 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -92,7 +92,7 @@ Item Action { id: preferencesAction; - text: catalog.i18nc("@action:inmenu menubar:settings","&Preferences..."); + text: catalog.i18nc("@action:inmenu","Configure Cura..."); iconName: "configure"; } @@ -282,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 b754d3a1d7..5ec6a37ea5 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -52,41 +52,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 { } @@ -127,7 +99,6 @@ UM.MainWindow Menu { - //: Edit menu title: catalog.i18nc("@title:menu menubar:toplevel","&Edit"); MenuItem { action: Cura.Actions.undo; } @@ -143,173 +114,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: Cura.MachineManager.activeQualityId == model_data.id - 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 @@ -343,8 +186,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; } } @@ -362,6 +204,16 @@ UM.MainWindow } } + UM.SettingPropertyProvider + { + id: machineExtruderCount + + containerStackId: Cura.MachineManager.activeMachineId + key: "machine_extruder_count" + watchedProperties: [ "value" ] + storeIndex: 0 + } + Item { id: contentItem; From 47079dc2dd41c810bf1532643cc58c2f9f53870d Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 28 Jun 2016 17:43:52 +0200 Subject: [PATCH 11/52] Start enabling some material management things --- resources/qml/Preferences/MaterialsPage.qml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index 36451e1717..cf721aa749 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -13,6 +13,7 @@ UM.ManagementPage id: base; title: catalog.i18nc("@title:tab", "Materials"); + addText: catalog.i18nc("@action:button", "Duplicate") model: UM.InstanceContainersModel { @@ -33,6 +34,8 @@ UM.ManagementPage } return result } + + sectionProperty: "brand" } activeId: Cura.MachineManager.activeMaterialId @@ -45,13 +48,19 @@ UM.ManagementPage return -1; } - addEnabled: false - removeEnabled: false - renameEnabled: false + onActivateObject: Cura.MachineManager.setActiveMaterial(currentItem.id) - scrollviewCaption: " " + activateEnabled: currentItem != null ? currentItem.id != Cura.MachineManager.activeMaterialId : false; + addEnabled: currentItem != null; + removeEnabled: currentItem != null ? !currentItem.readOnly : false; + renameEnabled: currentItem != null ? !currentItem.readOnly : false; + + scrollviewCaption: "Printer: %1, Nozzle: %2".arg(Cura.MachineManager.activeMachineName).arg(Cura.MachineManager.activeVariantName) detailsVisible: true + section.property: "section" + section.delegate: Label { text: section } + Item { UM.I18nCatalog { id: catalog; name: "cura"; } @@ -91,6 +100,7 @@ UM.ManagementPage editingEnabled: editButton.checked; properties: materialProperties + containerId: base.currentItem.id } QtObject @@ -106,9 +116,7 @@ UM.ManagementPage property color color_code: "yellow"; property real density: 0.0; - onDensityChanged: console.log(density); property real diameter: 0.0; - onDiameterChanged: console.log(diameter); property real spool_cost: 0.0; property real spool_weight: 0.0; From f7e4b91569ad31e8ed4ec4228e71a1fff9eea9d2 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 28 Jun 2016 17:44:19 +0200 Subject: [PATCH 12/52] Update styling of Material management page Contributes to CURA-342 --- resources/qml/Preferences/MaterialView.qml | 102 +++++++++++++++------ 1 file changed, 72 insertions(+), 30 deletions(-) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index c662895584..cf6e6e38e6 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -14,19 +14,26 @@ TabView property QtObject properties; - property bool editingEnabled; - + 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 - anchors.margins: UM.Theme.getSize("default_margin").width - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff Flow @@ -35,22 +42,19 @@ TabView width: base.width; - property real firstColumnWidth: width * 0.45 - property real secondColumnWidth: width * 0.4 - property real rowHeight: textField.height; - Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Brand") } - TextField { id: textField; width: parent.secondColumnWidth; text: properties.supplier; readOnly: !base.editingEnabled; } + Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Brand") } + TextField { id: textField; width: base.secondColumnWidth; text: properties.supplier; readOnly: !base.editingEnabled; } - Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") } - TextField { width: parent.secondColumnWidth; text: properties.material_type; readOnly: !base.editingEnabled; } + Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") } + TextField { width: base.secondColumnWidth; text: properties.material_type; readOnly: !base.editingEnabled; } - Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") } + Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") } Row { - width: parent.secondColumnWidth; + width: base.secondColumnWidth; height: parent.rowHeight; spacing: UM.Theme.getSize("default_margin").width/2 @@ -75,10 +79,10 @@ TabView Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: "" + catalog.i18nc("@label", "Properties") + "" } - Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Density") } + Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Density") } ReadOnlySpinBox { - width: parent.secondColumnWidth; + width: base.secondColumnWidth; value: properties.density; decimals: 2 suffix: "g/cm" @@ -86,10 +90,10 @@ TabView readOnly: !base.editingEnabled; } - Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Diameter") } + Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Diameter") } ReadOnlySpinBox { - width: parent.secondColumnWidth; + width: base.secondColumnWidth; value: properties.diameter; decimals: 2 suffix: "mm³" @@ -97,38 +101,38 @@ TabView readOnly: !base.editingEnabled; } - Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament Cost") } + Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament Cost") } ReadOnlySpinBox { - width: parent.secondColumnWidth; + width: base.secondColumnWidth; value: properties.spool_cost; prefix: base.currency readOnly: !base.editingEnabled; } - Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") } + Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") } ReadOnlySpinBox { - width: parent.secondColumnWidth; + width: base.secondColumnWidth; value: properties.spool_weight; suffix: "g"; stepSize: 10 readOnly: !base.editingEnabled; } - Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") } + Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") } ReadOnlySpinBox { - width: parent.secondColumnWidth; + width: base.secondColumnWidth; value: parseFloat(properties.spool_length); suffix: "m"; readOnly: !base.editingEnabled; } - Label { width: parent.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter (Approx.)") } + Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter (Approx.)") } ReadOnlySpinBox { - width: parent.secondColumnWidth; + width: base.secondColumnWidth; value: parseFloat(properties.cost_per_meter); suffix: catalog.i18nc("@label", "%1/m".arg(base.currency)); readOnly: !base.editingEnabled; @@ -141,7 +145,7 @@ TabView TextArea { text: properties.description; - width: parent.firstColumnWidth + parent.secondColumnWidth + width: base.firstColumnWidth + base.secondColumnWidth wrapMode: Text.WordWrap readOnly: !base.editingEnabled; @@ -152,7 +156,7 @@ TabView TextArea { text: properties.adhesion_info; - width: parent.firstColumnWidth + parent.secondColumnWidth + width: base.firstColumnWidth + base.secondColumnWidth wrapMode: Text.WordWrap readOnly: !base.editingEnabled; @@ -164,7 +168,13 @@ TabView Tab { title: catalog.i18nc("@label", "Print settings") - anchors.margins: UM.Theme.getSize("default_margin").height + 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 { @@ -175,11 +185,43 @@ TabView model: UM.SettingDefinitionsModel { containerId: Cura.MachineManager.activeDefinitionId - visibilityHandler: UM.SettingPreferenceVisibilityHandler { } + visibilityHandler: Cura.MaterialSettingsVisibilityHandler { } expanded: ["*"] } - delegate: Label { text: model.label } + 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 + } + + UM.SettingPropertyProvider + { + id: provider + + containerStackId: Cura.MachineManager.activeMachineId + key: model.key + watchedProperties: [ "value" ] + } + } } } } From cd8bc3b60d79125de0afc3592a2b5351bfc4ff59 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 01:53:48 +0200 Subject: [PATCH 13/52] Introduce a ContainerManager class to handle container actions like duplicate Because MachineManager is getting rather large Contributes to CURA-342 --- cura/ContainerManager.py | 34 ++++++++++++++++++++++++++++++++++ cura/CuraApplication.py | 3 +++ 2 files changed, 37 insertions(+) create mode 100644 cura/ContainerManager.py diff --git a/cura/ContainerManager.py b/cura/ContainerManager.py new file mode 100644 index 0000000000..9639dd1064 --- /dev/null +++ b/cura/ContainerManager.py @@ -0,0 +1,34 @@ +# Copyright (c) 2016 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal + +import UM.Settings + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + +class ContainerManager(QObject): + def __init__(self, parent = None): + super().__init__(parent) + + @pyqtSlot(str) + def duplicateContainer(self, container_id): + containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id) + if not containers: + return + + new_name = UM.Settings.ContainerRegistry.getInstance().uniqueName(containers[0].getName()) + new_material = containers[0].duplicate(new_name) + UM.Settings.ContainerRegistry.getInstance().addContainer(new_material) + + @pyqtSlot(str, str) + def renameContainer(self, container_id, new_name): + pass + + @pyqtSlot(str) + def removeContainer(self, container_id): + pass + +def createContainerManager(engine, js_engine): + return ContainerManager() diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 9970f0efce..420ae276e9 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -45,6 +45,7 @@ from . import CuraSplashScreen from . import MachineManagerModel from . import ContainerSettingsModel from . import MachineActionManager +from . import ContainerManager import cura.Settings @@ -428,6 +429,8 @@ class CuraApplication(QtApplication): qmlRegisterType(ContainerSettingsModel.ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel") qmlRegisterType(cura.Settings.MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") + qmlRegisterSingletonType(ContainerManager.ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) + qmlRegisterSingletonType(QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")), "Cura", 1, 0, "Actions") engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.ExtruderManager.getInstance()) From 3798a6e26a7904b95e0d1a43b8d8951bdff7ae21 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 01:54:25 +0200 Subject: [PATCH 14/52] Update MaterialsPage with the new way of handling buttons Contributes to CURA-342 --- resources/qml/Preferences/MaterialsPage.qml | 39 ++++++++++++++++----- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index cf721aa749..797b698cd1 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -13,7 +13,6 @@ UM.ManagementPage id: base; title: catalog.i18nc("@title:tab", "Materials"); - addText: catalog.i18nc("@action:button", "Duplicate") model: UM.InstanceContainersModel { @@ -48,19 +47,43 @@ UM.ManagementPage return -1; } - onActivateObject: Cura.MachineManager.setActiveMaterial(currentItem.id) - - activateEnabled: currentItem != null ? currentItem.id != Cura.MachineManager.activeMaterialId : false; - addEnabled: currentItem != null; - removeEnabled: currentItem != null ? !currentItem.readOnly : false; - renameEnabled: currentItem != null ? !currentItem.readOnly : false; - scrollviewCaption: "Printer: %1, Nozzle: %2".arg(Cura.MachineManager.activeMachineName).arg(Cura.MachineManager.activeVariantName) detailsVisible: true 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 + onClicked: Cura.ContainerManager.duplicateContainer(base.currentItem.id) + }, + Button + { + text: catalog.i18nc("@action:button", "Remove"); + iconName: "list-remove"; + enabled: base.currentItem && !base.currentItem.readOnly +// onClicked: Cura.ContainerManager.removeContainer() + }, + Button + { + text: catalog.i18nc("@action:button", "Rename"); + iconName: "edit-rename"; + enabled: base.currentItem && !base.currentItem.readOnly +// onClicked: Cura.ContainerManager.renameContainer() + } + ] + Item { UM.I18nCatalog { id: catalog; name: "cura"; } From 419326132fd81e168d4e9affb827cd9ac9fcc9d1 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 01:54:58 +0200 Subject: [PATCH 15/52] Use ContainerPropertyProvider to provide properties for the materials page Contributes to CURA-342 --- resources/qml/Preferences/MaterialView.qml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index cf6e6e38e6..94aa2ca5d6 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -211,16 +211,11 @@ TabView suffix: model.unit maximumValue: 99999 decimals: model.unit == "mm" ? 2 : 0 + + onEditingFinished: provider.setPropertyValue("value", value) } - UM.SettingPropertyProvider - { - id: provider - - containerStackId: Cura.MachineManager.activeMachineId - key: model.key - watchedProperties: [ "value" ] - } + UM.ContainerPropertyProvider { id: provider; containerId: base.containerId; watchedProperties: [ "value" ]; key: model.key } } } } From ee0160075e3d392f5414a584fb9305cd2fb4730c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 01:55:53 +0200 Subject: [PATCH 16/52] Override InstanceContainer::duplicate in XmlMAterialProfile So that we can set a new GUID for the material on duplicate Contributes to CURA-342 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index a1a992334e..353c02e2ac 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -5,6 +5,7 @@ import math import copy import io import xml.etree.ElementTree as ET +import uuid from UM.Logger import Logger @@ -22,6 +23,11 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): def __init__(self, container_id, *args, **kwargs): super().__init__(container_id, *args, **kwargs) + def duplicate(self, new_id, new_name = None): + result = super().duplicate(self.getMetaDataEntry("brand") + "_" + new_id, new_name) + result.setMetaDataEntry("GUID", str(uuid.uuid4())) + return result + def serialize(self): if self.getDefinition().id != "fdmprinter": # Since we create an instance of XmlMaterialProfile for each machine and nozzle in the profile, From 26f7ba0a74647658520f4fcb63be713e59606a9b Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 01:57:58 +0200 Subject: [PATCH 17/52] Fix up XmlMaterialProfile::serialize so we can properly serialize duplicated materials We now use GUID to look up all containers belonging to a material. In addition, we handle the multiple containers better Contributes to CURA-342 --- .../XmlMaterialProfile/XmlMaterialProfile.py | 103 ++++++++++++------ 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 353c02e2ac..53659f14bb 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -28,8 +28,25 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): result.setMetaDataEntry("GUID", str(uuid.uuid4())) return result + def setProperty(self, key, property_name, property_value, container = None): + super().setProperty(key, property_name, property_value) + + for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(GUID = self.getMetaDataEntry("GUID")): + container._dirty = True + def serialize(self): - if self.getDefinition().id != "fdmprinter": + registry = UM.Settings.ContainerRegistry.getInstance() + + all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID")) + most_generic = all_containers[0] + for container in all_containers: + # Find the "most generic" version of this material + # This is a bit of a nasty implementation because of the naive assumption that anything with a shorter + # id is more generic. It holds for the current implementation though. + if len(most_generic.id) > len(container.id): + most_generic = container + + if most_generic and self.id != most_generic.id: # 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. @@ -91,54 +108,63 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): ## Begin Settings Block builder.start("settings") - for instance in self.findInstances(): - builder.start("setting", { "key": UM.Dictionary.findKey(self.__material_property_setting_map, instance.definition.key) }) - builder.data(str(instance.value)) - builder.end("setting") + if self.getDefinition().id == "fdmprinter": + for instance in self.findInstances(): + self._addSettingElement(builder, instance) - # Find all machine sub-profiles corresponding to this material and add them to this profile. - machines = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = self.getId() + "_*") - for machine in machines: - if machine.getMetaDataEntry("variant"): - # Since the list includes variant-specific containers but we do not yet want to add those, we just skip them. + machine_container_map = {} + machine_nozzle_map = {} + + 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 + elif len(container.id) < len(machine_container_map[definition_id].id): + machine_container_map[definition_id] = container + + variant = container.getMetaDataEntry("variant") + if variant: + if variant not in machine_nozzle_map: + machine_nozzle_map[definition_id] = [] + machine_nozzle_map[definition_id].append(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") - - definition = machine.getDefinition() - builder.start("machine_identifier", { "manufacturer": definition.getMetaDataEntry("manufacturer", ""), "product": UM.Dictionary.findKey(self.__product_id_map, definition.id) }) + builder.start("machine_identifier", { "manufacturer": definition.getMetaDataEntry("manufacturer", ""), "product": product}) builder.end("machine_identifier") - for instance in machine.findInstances(): - if self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value: + 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 - builder.start("setting", { "key": UM.Dictionary.findKey(self.__material_property_setting_map, instance.definition.key) }) - builder.data(str(instance.value)) - builder.end("setting") + self._addSettingElement(builder, instance) # Find all hotend sub-profiles corresponding to this material and machine and add them to this profile. - hotends = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = machine.getId() + "_*") - for hotend in hotends: - variant_containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = hotend.getMetaDataEntry("variant")) - if variant_containers: - builder.start("hotend", { "id": variant_containers[0].getName() }) + for hotend in machine_nozzle_map[definition_id]: + variant_containers = registry.findInstanceContainers(id = hotend.getMetaDataEntry("variant")) + if not variant_containers: + continue - for instance in hotend.findInstances(): - if 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 + builder.start("hotend", { "id": variant_containers[0].getName() }) - if machine.getInstance(instance.definition.key) and machine.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 + 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 - builder.start("setting", { "key": UM.Dictionary.findKey(self.__material_property_setting_map, instance.definition.key) }) - builder.data(str(instance.value)) - builder.end("setting") + self._addSettingElement(builder, instance) - builder.end("hotend") + builder.end("hotend") builder.end("machine") @@ -297,6 +323,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 = { From 343280103f130bd72e48b9b1348844c8eb4f9d11 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 15:07:17 +0200 Subject: [PATCH 18/52] Fix GUID of materials Contributes to CURA-342 --- resources/materials/generic_abs.xml.fdm_material | 2 +- resources/materials/generic_cpe.xml.fdm_material | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/materials/generic_abs.xml.fdm_material b/resources/materials/generic_abs.xml.fdm_material index 654b06d221..82b2f1f963 100644 --- a/resources/materials/generic_abs.xml.fdm_material +++ b/resources/materials/generic_abs.xml.fdm_material @@ -9,7 +9,7 @@ Generic PLA profile. Serves as an example file, data in this file is not correct ABS Generic - 506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9 + 60636bb4-518f-42e7-8237-fe77b194ebe0 0 #FF0000 diff --git a/resources/materials/generic_cpe.xml.fdm_material b/resources/materials/generic_cpe.xml.fdm_material index bbe6e328d2..8ac4dd8c71 100644 --- a/resources/materials/generic_cpe.xml.fdm_material +++ b/resources/materials/generic_cpe.xml.fdm_material @@ -9,7 +9,7 @@ Generic PLA profile. Serves as an example file, data in this file is not correct CPE Generic - 506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9 + 12f41353-1a33-415e-8b4f-a775a6c70cc6 0 #0000FF From 8badb061da85a96def826e902e3af8d23043711d Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 15:08:00 +0200 Subject: [PATCH 19/52] Check for definition_id, not variant name Since the dict is supposed to be per definition Contributes to CURA-342 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 53659f14bb..b1b065db70 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -127,7 +127,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): variant = container.getMetaDataEntry("variant") if variant: - if variant not in machine_nozzle_map: + if definition_id not in machine_nozzle_map: machine_nozzle_map[definition_id] = [] machine_nozzle_map[definition_id].append(container) From 92d3bea2290a6b76c33195c0c5a39186621812c4 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 17:31:19 +0200 Subject: [PATCH 20/52] Add a setContainerMetaDataEntry to ContainerManager So that we can set metadata from QML Contributes to CURA-342 --- cura/ContainerManager.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/cura/ContainerManager.py b/cura/ContainerManager.py index 9639dd1064..b2b8e36770 100644 --- a/cura/ContainerManager.py +++ b/cura/ContainerManager.py @@ -12,15 +12,17 @@ class ContainerManager(QObject): def __init__(self, parent = None): super().__init__(parent) + self._registry = UM.Settings.ContainerRegistry.getInstance() + @pyqtSlot(str) def duplicateContainer(self, container_id): - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id) + containers = self._registry.findInstanceContainers(id = container_id) if not containers: return - new_name = UM.Settings.ContainerRegistry.getInstance().uniqueName(containers[0].getName()) + new_name = self_registry.uniqueName(containers[0].getName()) new_material = containers[0].duplicate(new_name) - UM.Settings.ContainerRegistry.getInstance().addContainer(new_material) + self._registry.addContainer(new_material) @pyqtSlot(str, str) def renameContainer(self, container_id, new_name): @@ -30,5 +32,31 @@ class ContainerManager(QObject): def removeContainer(self, container_id): pass + @pyqtSlot(str, str, str) + def setContainerMetaDataEntry(self, container_id, entry_name, entry_value): + containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id) + if not containers: + return + + container = containers[0] + + 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 + + containers[0].setMetaDataEntry(entry_name, entry_value) + def createContainerManager(engine, js_engine): return ContainerManager() From ded4c89cb5b193d11bb0bb6ee25dba31ea0356fc Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 17:32:10 +0200 Subject: [PATCH 21/52] Ensure metadata changes are propagated to the related containers Now we properly serialize the new metadata Contributes to CURA-342 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index b1b065db70..ec7673f7f8 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -28,6 +28,12 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): result.setMetaDataEntry("GUID", str(uuid.uuid4())) return result + def setMetaDataEntry(self, key, value): + super().setMetaDataEntry(key, value) + + for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(GUID = self.getMetaDataEntry("GUID")): + container.setMetaData(copy.deepcopy(self._metadata)) + def setProperty(self, key, property_name, property_value, container = None): super().setProperty(key, property_name, property_value) @@ -75,8 +81,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): builder.end("brand") builder.start("material") - builder.data(self.getName()) - metadata.pop("material", "") + builder.data(metadata.pop("material", "")) builder.end("material") builder.start("color") @@ -128,8 +133,8 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): variant = container.getMetaDataEntry("variant") if variant: if definition_id not in machine_nozzle_map: - machine_nozzle_map[definition_id] = [] - machine_nozzle_map[definition_id].append(container) + machine_nozzle_map[definition_id] = {} + machine_nozzle_map[definition_id][variant] = container for definition_id, container in machine_container_map.items(): definition = container.getDefinition() @@ -150,7 +155,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): self._addSettingElement(builder, instance) # Find all hotend sub-profiles corresponding to this material and machine and add them to this profile. - for hotend in machine_nozzle_map[definition_id]: + for hotend_id, hotend in machine_nozzle_map[definition_id].items(): variant_containers = registry.findInstanceContainers(id = hotend.getMetaDataEntry("variant")) if not variant_containers: continue From eb48a4e7687e8850964d35e4b33e7de2d5a36012 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 30 Jun 2016 17:32:43 +0200 Subject: [PATCH 22/52] Set the new metadata when editing a material Contributes to CURA-342 --- resources/qml/Preferences/MaterialView.qml | 31 +++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index 94aa2ca5d6..b187fcb469 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -45,10 +45,23 @@ TabView property real rowHeight: textField.height; Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Brand") } - TextField { id: textField; width: base.secondColumnWidth; text: properties.supplier; readOnly: !base.editingEnabled; } + TextField + { + 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") } - TextField { width: base.secondColumnWidth; text: properties.material_type; readOnly: !base.editingEnabled; } + TextField + { + 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") } @@ -62,6 +75,8 @@ TabView { 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 @@ -70,7 +85,13 @@ TabView MouseArea { anchors.fill: parent; onClicked: colorDialog.open(); enabled: base.editingEnabled } } - TextField { id: colorLabel; text: properties.color_name; readOnly: !base.editingEnabled } + TextField + { + 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 } } @@ -88,6 +109,8 @@ TabView 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") } @@ -99,6 +122,8 @@ TabView 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") } From c9b1c36cc3d32c375c1b9fcf69ec9ce2fa61d9a2 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 4 Jul 2016 11:00:54 +0200 Subject: [PATCH 23/52] Add some documentation to XmlMaterialProfile Contributes to CURA-342 --- .../XmlMaterialProfile/XmlMaterialProfile.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index ec7673f7f8..b996be602f 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -13,33 +13,32 @@ 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) + ## Overridden from InstanceContainer def duplicate(self, new_id, new_name = None): result = super().duplicate(self.getMetaDataEntry("brand") + "_" + new_id, new_name) result.setMetaDataEntry("GUID", str(uuid.uuid4())) return result + ## Overridden from InstanceContainer def setMetaDataEntry(self, key, value): 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): 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() @@ -186,6 +185,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): return stream.getvalue() + ## Overridden from InstanceContainer def deserialize(self, serialized): data = ET.fromstring(serialized) @@ -359,6 +359,7 @@ 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" } @@ -378,3 +379,10 @@ def _indent(elem, level = 0): 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:] From df3275a10a7b64f4622abd1e76e95cd9a537a05b Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 4 Jul 2016 11:43:27 +0200 Subject: [PATCH 24/52] Update Machine and Profile pages with the changes made to ManagementPage Since all the individual properties are removed and we now just need to handle the buttons ourself. Contributes to CURA-342 --- resources/qml/Preferences/MachinesPage.qml | 37 +++++++-- resources/qml/Preferences/ProfilesPage.qml | 94 +++++++++++++--------- 2 files changed, 84 insertions(+), 47 deletions(-) diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml index dd9a7b42a7..87b4a9e540 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/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"; } From 0e28b331fb3f0949f01834c924bc9d439f96aa57 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 4 Jul 2016 12:07:29 +0200 Subject: [PATCH 25/52] Move Cura setting related classes to cura/Settings Contributes to CURA-342 --- cura/{ => Settings}/ContainerManager.py | 0 cura/{ => Settings}/ContainerSettingsModel.py | 0 cura/{ => Settings}/CuraContainerRegistry.py | 0 cura/{ => Settings}/ExtruderManager.py | 0 cura/{ => Settings}/ExtrudersModel.py | 0 cura/{ => Settings}/SettingOverrideDecorator.py | 0 cura/Settings/__init__.py | 9 +++++++++ 7 files changed, 9 insertions(+) rename cura/{ => Settings}/ContainerManager.py (100%) rename cura/{ => Settings}/ContainerSettingsModel.py (100%) rename cura/{ => Settings}/CuraContainerRegistry.py (100%) rename cura/{ => Settings}/ExtruderManager.py (100%) rename cura/{ => Settings}/ExtrudersModel.py (100%) rename cura/{ => Settings}/SettingOverrideDecorator.py (100%) diff --git a/cura/ContainerManager.py b/cura/Settings/ContainerManager.py similarity index 100% rename from cura/ContainerManager.py rename to cura/Settings/ContainerManager.py diff --git a/cura/ContainerSettingsModel.py b/cura/Settings/ContainerSettingsModel.py similarity index 100% rename from cura/ContainerSettingsModel.py rename to cura/Settings/ContainerSettingsModel.py 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 100% rename from cura/ExtrudersModel.py rename to cura/Settings/ExtrudersModel.py 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 index d6fe389478..c3d45dc81e 100644 --- a/cura/Settings/__init__.py +++ b/cura/Settings/__init__.py @@ -1,2 +1,11 @@ +# 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 .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler +from .SettingOverrideDecorator import SettingOverrideDecorator From a882c43b2c399899f01c8e475b54ec75f998f3d1 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 4 Jul 2016 12:08:08 +0200 Subject: [PATCH 26/52] Move MachineManager to cura/Settings and rename it to MachineManager Contributes to CURA-342 --- .../MachineManager.py} | 34 +++++++++---------- cura/Settings/__init__.py | 1 + 2 files changed, 18 insertions(+), 17 deletions(-) rename cura/{MachineManagerModel.py => Settings/MachineManager.py} (95%) 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 76786efe61..6ab19509ba 100644 --- a/cura/MachineManagerModel.py +++ b/cura/Settings/MachineManager.py @@ -2,21 +2,20 @@ # 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 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) @@ -27,7 +26,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() @@ -35,13 +34,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] @@ -80,7 +79,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: @@ -109,7 +108,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) @@ -161,7 +160,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) @@ -185,7 +184,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 @@ -502,6 +501,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 @@ -587,6 +590,3 @@ class MachineManagerModel(QObject): return containers[0] return self._empty_quality_container - -def createMachineManagerModel(engine, script_engine): - return MachineManagerModel() diff --git a/cura/Settings/__init__.py b/cura/Settings/__init__.py index c3d45dc81e..da8f36c040 100644 --- a/cura/Settings/__init__.py +++ b/cura/Settings/__init__.py @@ -7,5 +7,6 @@ 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 From ce3a5f7e483a831c0f0536aaee6a15861805baaf Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 4 Jul 2016 12:08:38 +0200 Subject: [PATCH 27/52] Update references to moved classes so things work again Contributes to CURA-342 --- cura/CuraApplication.py | 19 +++++++------------ cura/Settings/ContainerManager.py | 5 +++-- cura/Settings/ContainerSettingsModel.py | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3664563590..4883194803 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,8 @@ from . import CuraActions from . import MultiMaterialDecorator from . import ZOffsetDecorator from . import CuraSplashScreen -from . import MachineManagerModel -from . import ContainerSettingsModel from . import CameraImageProvider from . import MachineActionManager -from . import ContainerManager import cura.Settings @@ -373,9 +368,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")) @@ -426,16 +421,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(ContainerManager.ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) + 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 index b2b8e36770..c3ee7e1645 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -58,5 +58,6 @@ class ContainerManager(QObject): containers[0].setMetaDataEntry(entry_name, entry_value) -def createContainerManager(engine, js_engine): - return ContainerManager() + @staticmethod + def createContainerManager(engine, js_engine): + return ContainerManager() diff --git a/cura/Settings/ContainerSettingsModel.py b/cura/Settings/ContainerSettingsModel.py index 2ff1a5f401..9ec19ed7fb 100644 --- a/cura/Settings/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 From 6fbe354839eaeae4bd67b91c848016bae02448d6 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 4 Jul 2016 13:03:10 +0200 Subject: [PATCH 28/52] Fix ExtrudersModel to use the right location for ExtruderManager Contributes to CURA-342 --- cura/Settings/ExtrudersModel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index c8c5a21274..0f2511452a 100644 --- a/cura/Settings/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. From 0e098e38ebd9b96ca5f87512d89a4d7896f0f7ab Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 4 Jul 2016 13:10:29 +0200 Subject: [PATCH 29/52] More fixes for changed setting object location --- cura_app.py | 4 ++-- plugins/CuraEngineBackend/CuraEngineBackend.py | 2 +- plugins/CuraEngineBackend/StartSliceJob.py | 2 +- plugins/SolidView/SolidView.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) 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 c91e414a13..d222e37bd9 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 +from cura.Settings.ExtruderManager import ExtruderManager from cura.OneAtATimeIterator import OneAtATimeIterator from . import ProcessSlicedLayersJob diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 3d2eb0ed4a..6ed103d0b5 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -15,7 +15,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Settings.Validator import ValidatorState from cura.OneAtATimeIterator import OneAtATimeIterator -from cura.ExtruderManager import ExtruderManager +from cura.Settings.ExtruderManager import ExtruderManager class StartJobResult(IntEnum): Finished = 1 diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index ddbc06d119..2af4108f23 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 +from cura.Settings.ExtrudersModel import ExtrudersModel import math From 6f3fa19890b7708eb40f2b6039de7c6ba95761e0 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 4 Jul 2016 13:13:44 +0200 Subject: [PATCH 30/52] Fix up plugins that use ExtruderManager Since it has been moved to cura.Settings --- plugins/CuraEngineBackend/CuraEngineBackend.py | 8 ++++---- plugins/CuraEngineBackend/StartSliceJob.py | 7 ++++--- plugins/SolidView/SolidView.py | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index d222e37bd9..a70683bd21 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.Settings.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. @@ -379,8 +379,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) - self._onChanged() \ No newline at end of file + self._onChanged() diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 6ed103d0b5..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.Settings.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/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 2af4108f23..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.Settings.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() From ff9e4c9bb7e6f706f11ab0c75d7db679c7f2e6bf Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 5 Jul 2016 16:44:13 +0200 Subject: [PATCH 31/52] Add some more documentation to ContainerManager Contributes to CURA-341 --- cura/Settings/ContainerManager.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index c3ee7e1645..0853d8534b 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -8,13 +8,26 @@ import UM.Settings 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() - @pyqtSlot(str) + ## 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.findInstanceContainers(id = container_id) if not containers: @@ -32,6 +45,16 @@ class ContainerManager(QObject): def removeContainer(self, container_id): pass + ## 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. @pyqtSlot(str, str, str) def setContainerMetaDataEntry(self, container_id, entry_name, entry_value): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id) @@ -58,6 +81,7 @@ class ContainerManager(QObject): containers[0].setMetaDataEntry(entry_name, entry_value) + # Factory function, used by QML @staticmethod def createContainerManager(engine, js_engine): return ContainerManager() From ce065d110d8598e8ce8118035a8b4cd5f13bc145 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 5 Jul 2016 16:45:23 +0200 Subject: [PATCH 32/52] Finish implementation of ContainerManager Contributes to CURA-341 --- cura/Settings/ContainerManager.py | 116 +++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 11 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 0853d8534b..fae5eb0b9d 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -29,21 +29,115 @@ class ContainerManager(QObject): # \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.findInstanceContainers(id = container_id) + containers = self._registry.findContainers(None, id = container_id) if not containers: - return + return "" - new_name = self_registry.uniqueName(containers[0].getName()) - new_material = containers[0].duplicate(new_name) - self._registry.addContainer(new_material) + container = containers[0] - @pyqtSlot(str, str) - def renameContainer(self, container_id, new_name): - pass + new_container = None + new_name = self._registry.uniqueName(container.getName()) + 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) - @pyqtSlot(str) + 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: + 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): - pass + containers = self._registry.findContainers(None, id = container_id) + if not containers: + return False + + self._registry.removeContainer(containers[0]) + + 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 = container_id) + if not containers: + return False + + merge_into = containers[0] + + containers = self._registry.findContainers(None, id = container_id) + if not containers: + return False + + merge = containers[0] + + if type(merge) != type(merge_into): + 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: + return False + + containers[0].clear() + + return True ## Set a metadata entry of the specified container. # @@ -57,7 +151,7 @@ class ContainerManager(QObject): # \param entry_value The new value of the entry. @pyqtSlot(str, str, str) def setContainerMetaDataEntry(self, container_id, entry_name, entry_value): - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id) + containers = UM.Settings.ContainerRegistry.getInstance().findContainers(None, id = container_id) if not containers: return From e80a999740ba333046fdbd0b55d74b4bbe1b4888 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 5 Jul 2016 16:46:52 +0200 Subject: [PATCH 33/52] Fix up readOnly property of XmlMaterialProfile Contributes to CURA-341 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index b996be602f..0fd3ded228 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -24,6 +24,13 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): result.setMetaDataEntry("GUID", str(uuid.uuid4())) 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): super().setMetaDataEntry(key, value) From 827c7b84fd15fd026475e6db444fab659d2926b8 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 5 Jul 2016 16:47:26 +0200 Subject: [PATCH 34/52] Do not try to set a property of a read-only material Contributes to CURA-341 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 0fd3ded228..d827f506e3 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -40,6 +40,9 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): ## 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")): From aba60b0105b1e5a74f647d2280c3766ec2e724a8 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 5 Jul 2016 16:48:43 +0200 Subject: [PATCH 35/52] Remove "Rename" button from Materials page and make the other ones work Since the name is generated from the metadata Contributes to CURA-341 --- resources/qml/Preferences/MaterialsPage.qml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index 797b698cd1..a40d4c1e3b 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -65,22 +65,15 @@ UM.ManagementPage { text: catalog.i18nc("@action:button", "Duplicate"); iconName: "list-add"; - enabled: base.currentItem + enabled: base.currentItem != null onClicked: Cura.ContainerManager.duplicateContainer(base.currentItem.id) }, Button { text: catalog.i18nc("@action:button", "Remove"); iconName: "list-remove"; - enabled: base.currentItem && !base.currentItem.readOnly -// onClicked: Cura.ContainerManager.removeContainer() - }, - Button - { - text: catalog.i18nc("@action:button", "Rename"); - iconName: "edit-rename"; - enabled: base.currentItem && !base.currentItem.readOnly -// onClicked: Cura.ContainerManager.renameContainer() + enabled: base.currentItem != null && !base.currentItem.readOnly + onClicked: confirmDialog.open() } ] @@ -149,6 +142,13 @@ UM.ManagementPage property string description: ""; property string adhesion_info: ""; } + + UM.ConfirmRemoveDialog + { + id: confirmDialog + object: base.currentItem != null ? base.currentItem.name : "" + onYes: Cura.ContainerManager.removeContainer(base.currentItem.id) + } } onCurrentItemChanged: From 2bc3a1077654e255433612f69f4d83d23110728a Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 5 Jul 2016 16:51:02 +0200 Subject: [PATCH 36/52] Properly handle machines that have no variants Contributes to CURA-341 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index d827f506e3..b0aa93afe6 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -139,10 +139,11 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): elif len(container.id) < len(machine_container_map[definition_id].id): 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: - if definition_id not in machine_nozzle_map: - machine_nozzle_map[definition_id] = {} machine_nozzle_map[definition_id][variant] = container for definition_id, container in machine_container_map.items(): From 750ecfc2e185c9e281af1aa95e877bdc89ef473b Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 5 Jul 2016 16:51:28 +0200 Subject: [PATCH 37/52] Remove some now-useless code relating to spool weight Contributes to CURA-341 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index b0aa93afe6..8e3298597c 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -233,17 +233,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]) @@ -360,6 +349,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", From 8476090ed237ee2e2c0dbc97b1dd727b787598f7 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 01:36:52 +0200 Subject: [PATCH 38/52] Also expose the main Cura application object as CuraApplication to QML This way we can start cleaning things up and moving away from the "Printer" name Contributes to CURA-341 --- cura/CuraApplication.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 4883194803..eb9c5e6d3b 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -414,6 +414,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) From 6e153414a4f89158844478d5a1563a43f352cace Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 01:37:54 +0200 Subject: [PATCH 39/52] Do not try to write to read-only containers Contributes to CURA-341 --- cura/Settings/ContainerManager.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index fae5eb0b9d..050e61a023 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -18,6 +18,7 @@ class ContainerManager(QObject): super().__init__(parent) self._registry = UM.Settings.ContainerRegistry.getInstance() + self._container_name_filters = {} ## Create a duplicate of the specified container # @@ -89,7 +90,7 @@ class ContainerManager(QObject): if not containers: return False - self._registry.removeContainer(containers[0]) + self._registry.removeContainer(containers[0].getId()) return True @@ -135,6 +136,9 @@ class ContainerManager(QObject): if not containers: return False + if containers[0].isReadOnly(): + return False + containers[0].clear() return True @@ -149,14 +153,19 @@ class ContainerManager(QObject): # \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. - @pyqtSlot(str, str, str) + # + # \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: - return + return False container = containers[0] + if container.isReadOnly(): + return False + entries = entry_name.split("/") entry_name = entries.pop() From 1f6e99f641181a5b18a20de90ce33759622c40cb Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 01:39:38 +0200 Subject: [PATCH 40/52] Add methods to get name filters, import and export containers to ContainerManager We should start replacing the other ways of import/export with this since this uses the containerregistry provided contaienr types instead of needing a different plugin type. Contributes to CURA-341 --- cura/Settings/ContainerManager.py | 184 +++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 2 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 050e61a023..6f272c65d8 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -1,9 +1,19 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal +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 + +from UM.MimeTypeDatabase import MimeTypeNotFoundError from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -182,7 +192,177 @@ class ContainerManager(QObject): entry_name = root_name entry_value = root - containers[0].setMetaDataEntry(entry_name, entry_value) + 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 From f21079b763b06ed32438a2f1987844ab5c2b1344 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 01:41:00 +0200 Subject: [PATCH 41/52] Store the base material file as a metadata property of XmlMaterialProfile And use the base_file property to check if we should serialize or not. Contributes to CURA-342 --- .../XmlMaterialProfile/XmlMaterialProfile.py | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 8e3298597c..6c70930bae 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -52,16 +52,8 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): def serialize(self): registry = UM.Settings.ContainerRegistry.getInstance() - all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID")) - most_generic = all_containers[0] - for container in all_containers: - # Find the "most generic" version of this material - # This is a bit of a nasty implementation because of the naive assumption that anything with a shorter - # id is more generic. It holds for the current implementation though. - if len(most_generic.id) > len(container.id): - most_generic = container - - if most_generic and self.id != most_generic.id: + 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. @@ -81,6 +73,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): metadata.pop("status", "") metadata.pop("variant", "") metadata.pop("type", "") + metadata.pop("base_file", "") ## Begin Name Block builder.start("name") @@ -129,6 +122,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): 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": @@ -136,8 +130,6 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): if definition_id not in machine_container_map: machine_container_map[definition_id] = container - elif len(container.id) < len(machine_container_map[definition_id].id): - machine_container_map[definition_id] = container if definition_id not in machine_nozzle_map: machine_nozzle_map[definition_id] = {} @@ -145,6 +137,9 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): 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() @@ -278,6 +273,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) @@ -308,6 +304,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) From fdab77ff6ac2f9336879134cbc0371c804bac47f Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 01:42:05 +0200 Subject: [PATCH 42/52] Also duplicate the base file when duplicating a material profile This way serialization will always be correctly performed and we can generate a machine/variant specifc ID for the duplicate. Contributes to CURA-341 --- .../XmlMaterialProfile/XmlMaterialProfile.py | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 6c70930bae..709eb8c276 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -20,8 +20,27 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): ## Overridden from InstanceContainer def duplicate(self, new_id, new_name = None): - result = super().duplicate(self.getMetaDataEntry("brand") + "_" + new_id, new_name) - result.setMetaDataEntry("GUID", str(uuid.uuid4())) + 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 @@ -33,6 +52,9 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): ## 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")): From 19fd2795e1cd909bb969a4c4e514d8cb1fd884f5 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 01:42:39 +0200 Subject: [PATCH 43/52] Mark XmlMaterialProfile as type "material" so the import/export code can find it Contributes to CURA-341 --- plugins/XmlMaterialProfile/__init__.py | 1 + 1 file changed, 1 insertion(+) 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" } } From 0da07b55ce7c91286f5ea667bd69a2e5229907cb Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 01:43:18 +0200 Subject: [PATCH 44/52] Display brand and color name in the material menu if they are different than "generic" Contributes to CURA-341 --- resources/qml/Menus/MaterialMenu.qml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml index 4af8246930..be2ef4a551 100644 --- a/resources/qml/Menus/MaterialMenu.qml +++ b/resources/qml/Menus/MaterialMenu.qml @@ -36,7 +36,22 @@ Menu } MenuItem { - text: model.name; + 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; From 17ff92bdc9fb9afb62f748b89ca778cc7c97c677 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 01:43:44 +0200 Subject: [PATCH 45/52] Add support for import/export to the Materials page Contributes to CURA-341 --- resources/qml/Preferences/MaterialsPage.qml | 113 +++++++++++++++++++- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index a40d4c1e3b..35fa7256ea 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -66,7 +66,23 @@ UM.ManagementPage text: catalog.i18nc("@action:button", "Duplicate"); iconName: "list-add"; enabled: base.currentItem != null - onClicked: Cura.ContainerManager.duplicateContainer(base.currentItem.id) + 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 { @@ -74,6 +90,19 @@ UM.ManagementPage 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 } ] @@ -98,6 +127,8 @@ UM.ManagementPage text: catalog.i18nc("@action:button", "Edit"); iconName: "document-edit"; + enabled: base.currentItem != null && !base.currentItem.readOnly + checkable: true } } @@ -113,7 +144,7 @@ UM.ManagementPage bottom: parent.bottom } - editingEnabled: editButton.checked; + editingEnabled: base.currentItem != null && !base.currentItem.readOnly && editButton.checked; properties: materialProperties containerId: base.currentItem.id @@ -147,7 +178,83 @@ UM.ManagementPage { id: confirmDialog object: base.currentItem != null ? base.currentItem.name : "" - onYes: Cura.ContainerManager.removeContainer(base.currentItem.id) + 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 } } From 8e63016ef3d506b069a9d9a8f7c0553e53adf659 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 12:07:16 +0200 Subject: [PATCH 46/52] Add logging to explain why ContainerManager's methods do not complete successfully Contributes to CURA-341 --- cura/Settings/ContainerManager.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 6f272c65d8..eb9dab0ad7 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -12,6 +12,7 @@ import UM.Settings import UM.SaveFile import UM.Platform import UM.MimeTypeDatabase +import UM.Logger from UM.MimeTypeDatabase import MimeTypeNotFoundError @@ -42,12 +43,15 @@ class ContainerManager(QObject): 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: @@ -71,6 +75,7 @@ class ContainerManager(QObject): 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] @@ -98,6 +103,7 @@ class ContainerManager(QObject): 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()) @@ -115,19 +121,22 @@ class ContainerManager(QObject): # \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 = container_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 = container_id) + 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(): @@ -144,9 +153,11 @@ class ContainerManager(QObject): 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() @@ -169,11 +180,13 @@ class ContainerManager(QObject): 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("/") From 6374eee7b45e4065b1a95c26674b8f27dd954eeb Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Thu, 7 Jul 2016 16:55:56 +0200 Subject: [PATCH 47/52] Fix LayerView cursor issue. CURA-1381 --- plugins/LayerView/LayerView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"); From 0477955974cbdbca249438e1dc16ab536a88cd6f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 7 Jul 2016 17:02:29 +0200 Subject: [PATCH 48/52] Sidebar stuff is now properly done in a loader (and in seperate files) CURA-1036 --- resources/qml/PrintMonitor.qml | 118 ++++++++++++++++++++++++++++++ resources/qml/Sidebar.qml | 112 +--------------------------- resources/qml/SidebarContents.qml | 43 +++++++++++ 3 files changed, 164 insertions(+), 109 deletions(-) create mode 100644 resources/qml/PrintMonitor.qml create mode 100644 resources/qml/SidebarContents.qml 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 From 571523e0efa64d5906d869bbd04a0d392f43706c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 17:30:39 +0200 Subject: [PATCH 49/52] Ensure an XML material always has a description and adhesion_info metadata entry Contributes to CURA-342 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 709eb8c276..ec75b7253c 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -241,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: From 975106b90e6c14fc101482c194820d8fc6bc30de Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 17:31:12 +0200 Subject: [PATCH 50/52] Also update description and adhesion info when they change Contributes to CURA-342 --- resources/qml/Preferences/MaterialView.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index b187fcb469..c79b8867c1 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -174,6 +174,8 @@ TabView 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") } @@ -185,6 +187,7 @@ TabView wrapMode: Text.WordWrap readOnly: !base.editingEnabled; + onEditingFinished: Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, "adhesion_info", text) } } } From 7df5008d3c02827ce1b48aa37a0c930c45ac3dce Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 17:32:11 +0200 Subject: [PATCH 51/52] Disable spool weight etc. fields since they are not yet functional Contributes to CURA-342 --- resources/qml/Preferences/MaterialView.qml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index c79b8867c1..6d5f218c06 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -127,40 +127,40 @@ TabView } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament Cost") } - ReadOnlySpinBox + SpinBox { width: base.secondColumnWidth; value: properties.spool_cost; prefix: base.currency - readOnly: !base.editingEnabled; + enabled: false } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") } - ReadOnlySpinBox + SpinBox { width: base.secondColumnWidth; value: properties.spool_weight; suffix: "g"; stepSize: 10 - readOnly: !base.editingEnabled; + enabled: false } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") } - ReadOnlySpinBox + SpinBox { width: base.secondColumnWidth; value: parseFloat(properties.spool_length); suffix: "m"; - readOnly: !base.editingEnabled; + enabled: false } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter (Approx.)") } - ReadOnlySpinBox + SpinBox { width: base.secondColumnWidth; value: parseFloat(properties.cost_per_meter); suffix: catalog.i18nc("@label", "%1/m".arg(base.currency)); - readOnly: !base.editingEnabled; + enabled: false } Item { width: parent.width; height: UM.Theme.getSize("default_margin").height } From 6b9689c2c79a108ed3715e23aaeddde9d25953af Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 7 Jul 2016 17:32:49 +0200 Subject: [PATCH 52/52] Use clearer styling of disabled entry fields on the materials page Contributes to CURA-342 --- resources/qml/Preferences/MaterialView.qml | 11 +++-- resources/qml/Preferences/ReadOnlySpinBox.qml | 47 +++++++++++++++---- .../qml/Preferences/ReadOnlyTextArea.qml | 46 ++++++++++++++++++ .../qml/Preferences/ReadOnlyTextField.qml | 46 ++++++++++++++++++ 4 files changed, 136 insertions(+), 14 deletions(-) create mode 100644 resources/qml/Preferences/ReadOnlyTextArea.qml create mode 100644 resources/qml/Preferences/ReadOnlyTextField.qml diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index 6d5f218c06..67a149f446 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -45,7 +45,7 @@ TabView property real rowHeight: textField.height; Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Brand") } - TextField + ReadOnlyTextField { id: textField; width: base.secondColumnWidth; @@ -55,7 +55,7 @@ TabView } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") } - TextField + ReadOnlyTextField { width: base.secondColumnWidth; text: properties.material_type; @@ -85,7 +85,7 @@ TabView MouseArea { anchors.fill: parent; onClicked: colorDialog.open(); enabled: base.editingEnabled } } - TextField + ReadOnlyTextField { id: colorLabel; text: properties.color_name; @@ -167,7 +167,7 @@ TabView Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Description") } - TextArea + ReadOnlyTextArea { text: properties.description; width: base.firstColumnWidth + base.secondColumnWidth @@ -180,13 +180,14 @@ TabView Label { width: parent.width; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Adhesion Information") } - TextArea + 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) } } diff --git a/resources/qml/Preferences/ReadOnlySpinBox.qml b/resources/qml/Preferences/ReadOnlySpinBox.qml index 5c1a3cbe19..8692f55708 100644 --- a/resources/qml/Preferences/ReadOnlySpinBox.qml +++ b/resources/qml/Preferences/ReadOnlySpinBox.qml @@ -5,19 +5,48 @@ import QtQuick 2.1 import QtQuick.Controls 1.1 import QtQuick.Dialogs 1.2 -// Provides a SpinBox with the same readOnly property as a TextField -SpinBox +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 - Keys.enabled: !readOnly - MouseArea + width: spinBox.width + height: spinBox.height + + SpinBox { - acceptedButtons: Qt.AllButtons; - anchors.fill: parent; - enabled: parent.readOnly; - onWheel: wheel.accepted = true; - cursorShape: enabled ? Qt.ArrowCursor : Qt.IBeamCursor; + 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 } +}