Browse Source

WIP: Rework Material management page

Lipu Fei 7 years ago
parent
commit
f024f45cfe

+ 28 - 13
cura/Machines/MaterialManager.py

@@ -1,4 +1,4 @@
-from collections import defaultdict
+from collections import defaultdict, OrderedDict
 from typing import Optional
 
 from PyQt5.Qt import QTimer, QObject, pyqtSignal
@@ -17,6 +17,9 @@ class MaterialGroup:
         self.root_material_node = None
         self.derived_material_node_list = []
 
+    def __str__(self) -> str:
+        return "%s[%s]" % (self.__class__.__name__, self.name)
+
 
 class MaterialNode(ContainerNode):
     __slots__ = ("material_map", "children_map")
@@ -45,6 +48,9 @@ class MaterialManager(QObject):
         self._material_diameter_map = defaultdict()  # root_material_id -> diameter -> root_material_id for that diameter
         self._diameter_material_map = dict()  # material id including diameter (generic_pla_175) -> material root id (generic_pla)
 
+        # This is used in Legacy UM3 send material function and the material management page.
+        self._guid_material_groups_map = defaultdict(list)  # GUID -> a list of material_groups
+
         # The machine definition ID for the non-machine-specific materials.
         # This is used as the last fallback option if the given machine-specific material(s) cannot be found.
         self._default_machine_definition_id = "fdmprinter"
@@ -63,7 +69,7 @@ class MaterialManager(QObject):
         # Find all materials and put them in a matrix for quick search.
         material_metadata_list = self._container_registry.findContainersMetadata(type = "material")
 
-        self._material_group_map = {}
+        self._material_group_map = OrderedDict()
         self._diameter_machine_variant_material_map = {}
 
         # Map #1
@@ -85,6 +91,14 @@ class MaterialManager(QObject):
             else:
                 new_node = MaterialNode(material_metadata)
                 group.derived_material_node_list.append(new_node)
+        self._material_group_map = OrderedDict(sorted(self._material_group_map.items(), key = lambda x: x[0]))
+
+        # Map #1.5
+        #    GUID -> material group list
+        self._guid_material_groups_map = defaultdict(list)
+        for root_material_id, material_group in self._material_group_map.items():
+            guid = material_group.root_material_node.metadata["GUID"]
+            self._guid_material_groups_map[guid].append(material_group)
 
         # Map #2
         # Lookup table for material type -> fallback material metadata
@@ -198,6 +212,9 @@ class MaterialManager(QObject):
     def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
         return self._diameter_material_map.get(root_material_id)
 
+    def getMaterialGroupListByGUID(self, guid: str) -> Optional[list]:
+        return self._guid_material_groups_map.get(guid)
+
     #
     # Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
     #
@@ -206,29 +223,27 @@ class MaterialManager(QObject):
         rounded_diameter = str(round(diameter))
         if rounded_diameter not in self._diameter_machine_variant_material_map:
             Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
-            return {}
+            return dict()
 
         # If there are variant materials, get the variant material
         machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
         machine_node = machine_variant_material_map.get(machine_definition_id)
+        default_machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
         variant_node = None
-        if machine_node is None:
-            machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
         if variant_name is not None and machine_node is not None:
             variant_node = machine_node.getChildNode(variant_name)
 
+        nodes_to_check = [variant_node, machine_node, default_machine_node]
+
         # Fallback mechanism of finding materials:
         #  1. variant-specific material
         #  2. machine-specific material
         #  3. generic material (for fdmprinter)
-        material_id_metadata_dict = {}
-        if variant_node is not None:
-            material_id_metadata_dict = {mid: node for mid, node in variant_node.material_map.items()}
-
-        # Fallback: machine-specific materials, including "fdmprinter"
-        if not material_id_metadata_dict:
-            if machine_node is not None:
-                material_id_metadata_dict = {mid: node for mid, node in machine_node.material_map.items()}
+        material_id_metadata_dict = dict()
+        for node in nodes_to_check:
+            if node is not None:
+                material_id_metadata_dict = {mid: node for mid, node in variant_node.material_map.items()}
+                break
 
         return material_id_metadata_dict
 

+ 29 - 69
cura/Settings/ContainerManager.py

@@ -764,16 +764,16 @@ class ContainerManager(QObject):
     ##  Create a duplicate of a material, which has the same GUID and base_file metadata
     #
     #   \return \type{str} the id of the newly created container.
-    @pyqtSlot(str, result = str)
-    def duplicateMaterial(self, material_id: str) -> str:
-        assert material_id
+    @pyqtSlot("QVariant")
+    def duplicateMaterial(self, material_node):
+        root_material_id = material_node.metadata["base_file"]
 
         from cura.CuraApplication import CuraApplication
         material_manager = CuraApplication.getInstance()._material_manager
 
-        material_group = material_manager.getMaterialGroup(material_id)
+        material_group = material_manager.getMaterialGroup(root_material_id)
         if not material_group:
-            Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id)
+            Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", root_material_id)
             return ""
 
         base_container = material_group.root_material_node.getContainer()
@@ -803,7 +803,7 @@ class ContainerManager(QObject):
                 if container_to_copy.getMetaDataEntry("variant_name"):
                     variant_name = container_to_copy.getMetaDataEntry("variant_name")
                     new_id += "_" + variant_name.replace(" ", "_")
-            if current_id == material_id:
+            if current_id == root_material_id:
                 clone_of_original = new_id
 
             new_container = copy.deepcopy(container_to_copy)
@@ -814,17 +814,6 @@ class ContainerManager(QObject):
         for container_to_add in new_containers:
             container_to_add.setDirty(True)
             ContainerRegistry.getInstance().addContainer(container_to_add)
-        return self._getMaterialContainerIdForActiveMachine(clone_of_original)
-
-    ##  Create a duplicate of a material or it's original entry
-    #
-    #   \return \type{str} the id of the newly created container.
-    @pyqtSlot(str, result = str)
-    def duplicateOriginalMaterial(self, material_id):
-
-        # check if the given material has a base file (i.e. was shipped by default)
-        base_file = self.getContainerMetaDataEntry(material_id, "base_file")
-        return self.duplicateMaterial(base_file)
 
     ##  Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
     #
@@ -869,72 +858,43 @@ class ContainerManager(QObject):
         duplicated_container.setName(catalog.i18nc("@label", "Custom Material"))
 
         self._container_registry.addContainer(duplicated_container)
-        return self._getMaterialContainerIdForActiveMachine(new_id)
-
-    ##  Find the id of a material container based on the new material
-    #   Utilty function that is shared between duplicateMaterial and createMaterial
-    #
-    #   \param base_file \type{str} the id of the created container.
-    def _getMaterialContainerIdForActiveMachine(self, base_file):
-        global_stack = Application.getInstance().getGlobalContainerStack()
-        if not global_stack:
-            return base_file
-
-        has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False))
-        has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False))
-        has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
-        if has_machine_materials or has_variant_materials:
-            if has_variants:
-                materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
-            else:
-                materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
-
-            if materials:
-                return materials[0]["id"]
-
-            Logger.log("w", "Unable to find a suitable container based on %s for the current machine.", base_file)
-            return "" # do not activate a new material if a container can not be found
-
-        return base_file
 
     ##  Get a list of materials that have the same GUID as the reference material
     #
     #   \param material_id \type{str} the id of the material for which to get the linked materials.
     #   \return \type{list} a list of names of materials with the same GUID
-    @pyqtSlot(str, result = "QStringList")
-    def getLinkedMaterials(self, material_id: str):
-        containers = self._container_registry.findInstanceContainersMetadata(id = material_id)
-        if not containers:
-            Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
-            return []
+    @pyqtSlot("QVariant", result = "QStringList")
+    def getLinkedMaterials(self, material_node):
+        guid = material_node.metadata["GUID"]
 
-        material_container = containers[0]
-        material_base_file = material_container.get("base_file", "")
-        material_guid = material_container.get("GUID", "")
-        if not material_guid:
-            Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
-            return []
+        from cura.CuraApplication import CuraApplication
+        material_manager = CuraApplication.getInstance()._material_manager
 
-        containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_guid)
-        linked_material_names = []
-        for container in containers:
-            if container["id"] in [material_id, material_base_file] or container.get("base_file") != container["id"]:
-                continue
+        material_group_list = material_manager.getMaterialGroupListByGUID(guid)
 
-            linked_material_names.append(container["name"])
+        linked_material_names = []
+        if material_group_list:
+            for material_group in material_group_list:
+                linked_material_names.append(material_group.root_material_node.metadata["name"])
         return linked_material_names
 
     ##  Unlink a material from all other materials by creating a new GUID
     #   \param material_id \type{str} the id of the material to create a new GUID for.
-    @pyqtSlot(str)
-    def unlinkMaterial(self, material_id: str):
-        containers = self._container_registry.findInstanceContainers(id=material_id)
-        if not containers:
-            Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id)
-            return ""
+    @pyqtSlot("QVariant")
+    def unlinkMaterial(self, material_node):
+        # Get the material group
+        from cura.CuraApplication import CuraApplication
+        material_manager = CuraApplication.getInstance()._material_manager
+        material_group = material_manager.getMaterialGroup(material_node.metadata["base_file"])
 
-        containers[0].setMetaDataEntry("GUID", str(uuid.uuid4()))
+        # Generate a new GUID
+        new_guid = str(uuid.uuid4())
 
+        # Update the GUID
+        # NOTE: We only need to set the root material container because XmlMaterialProfile.setMetaDataEntry() will
+        # take care of the derived containers too
+        container = material_group.root_material_node.getContainer()
+        container.setMetaDataEntry("GUID", new_guid)
 
     ##  Get the singleton instance for this class.
     @classmethod

+ 17 - 7
plugins/XmlMaterialProfile/XmlMaterialProfile.py

@@ -48,18 +48,28 @@ class XmlMaterialProfile(InstanceContainer):
 
     ##  Overridden from InstanceContainer
     #   set the meta data for all machine / variant combinations
-    def setMetaDataEntry(self, key, value):
+    def setMetaDataEntry(self, key, value, is_first_call = True):
         registry = ContainerRegistry.getInstance()
         if registry.isReadOnly(self.getId()):
             return
 
-        super().setMetaDataEntry(key, value)
+        # Prevent recursion
+        if is_first_call:
+            super().setMetaDataEntry(key, value)
 
-        basefile = self.getMetaDataEntry("base_file", self.getId())  #if basefile is self.getId, this is a basefile.
-        # Update all containers that share basefile
-        for container in registry.findInstanceContainers(base_file = basefile):
-            if container.getMetaDataEntry(key, None) != value: # Prevent recursion
-                container.setMetaDataEntry(key, value)
+        # Get the MaterialGroup
+        material_manager = CuraApplication.getInstance()._material_manager
+        root_material_id = self.getMetaDataEntry("base_file")  #if basefile is self.getId, this is a basefile.
+        material_group = material_manager.getMaterialGroup(root_material_id)
+
+        # Update the root material container
+        root_material_container = material_group.root_material_node.getContainer()
+        root_material_container.setMetaDataEntry(key, value, is_first_call = False)
+
+        # Update all containers derived from it
+        for node in material_group.derived_material_node_list:
+            container = node.getContainer()
+            container.setMetaDataEntry(key, value, is_first_call = False)
 
     ##  Overridden from InstanceContainer, similar to setMetaDataEntry.
     #   without this function the setName would only set the name of the specific nozzle / material / machine combination container

+ 18 - 16
resources/qml/Preferences/MaterialView.qml

@@ -12,7 +12,8 @@ TabView
 {
     id: base
 
-    property QtObject properties;
+    property QtObject properties
+    property var currentMaterialNode: null
 
     property bool editingEnabled: false;
     property string currency: UM.Preferences.getValue("cura/currency") ? UM.Preferences.getValue("cura/currency") : "€"
@@ -27,15 +28,16 @@ TabView
     property bool reevaluateLinkedMaterials: false
     property string linkedMaterialNames:
     {
-        if (reevaluateLinkedMaterials)
-        {
+        if (reevaluateLinkedMaterials) {
             reevaluateLinkedMaterials = false;
         }
-        if(!base.containerId || !base.editingEnabled)
-        {
+        if (!base.containerId || !base.editingEnabled) {
+            return ""
+        }
+        var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode);
+        if (linkedMaterials.length <= 1) {
             return ""
         }
-        var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.containerId);
         return linkedMaterials.join(", ");
     }
 
@@ -80,18 +82,18 @@ TabView
                 {
                     id: textField;
                     width: scrollView.columnWidth;
-                    text: properties.supplier;
+                    text: properties.brand;
                     readOnly: !base.editingEnabled;
-                    onEditingFinished: base.updateMaterialSupplier(properties.supplier, text)
+                    onEditingFinished: base.updateMaterialBrand(properties.brand, text)
                 }
 
                 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") }
                 ReadOnlyTextField
                 {
                     width: scrollView.columnWidth;
-                    text: properties.material_type;
+                    text: properties.material;
                     readOnly: !base.editingEnabled;
-                    onEditingFinished: base.updateMaterialType(properties.material_type, text)
+                    onEditingFinished: base.updateMaterialType(properties.material, text)
                 }
 
                 Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") }
@@ -251,7 +253,7 @@ TabView
                     visible: base.linkedMaterialNames != ""
                     onClicked:
                     {
-                        Cura.ContainerManager.unlinkMaterial(base.containerId)
+                        Cura.ContainerManager.unlinkMaterial(base.currentMaterialNode)
                         base.reevaluateLinkedMaterials = true
                     }
                 }
@@ -466,12 +468,12 @@ TabView
     // update the type of the material
     function updateMaterialType (old_type, new_type) {
         base.setMetaDataEntry("material", old_type, new_type)
-        materialProperties.material_type = new_type
+        materialProperties.material= new_type
     }
 
-    // update the supplier of the material
-    function updateMaterialSupplier (old_supplier, new_supplier) {
-        base.setMetaDataEntry("brand", old_supplier, new_supplier)
-        materialProperties.supplier = new_supplier
+    // update the brand of the material
+    function updateMaterialBrand (old_brand, new_brand) {
+        base.setMetaDataEntry("brand", old_brand, new_brand)
+        materialProperties.brand = new_brand
     }
 }

+ 12 - 9
resources/qml/Preferences/MaterialsPage.qml

@@ -34,6 +34,8 @@ Item
         text: catalog.i18nc("@title:tab", "Materials")
     }
 
+    property var hasCurrentItem: materialListView.currentItem != null;
+
     property var currentItem:
     {
         var current_index = materialListView.currentIndex;
@@ -65,9 +67,6 @@ Item
             onClicked: {
                 forceActiveFocus()
 
-                var current_index = materialListView.currentIndex;
-                var item = materialsModel.getItem(current_index);
-
                 const extruder_position = Cura.ExtruderManager.activeExtruderIndex;
                 Cura.MachineManager.setMaterial(extruder_position, base.currentItem.container_node);
             }
@@ -87,10 +86,11 @@ Item
         Button {
             text: catalog.i18nc("@action:button", "Duplicate");
             iconName: "list-add"
-            enabled: true //TODO
+            enabled: base.hasCurrentItem
             onClicked: {
                 forceActiveFocus()
-                // TODO
+
+                Cura.ContainerManager.duplicateMaterial(base.currentItem.container_node);
             }
         }
 
@@ -277,6 +277,7 @@ Item
                 {
                     var model = materialsModel.getItem(currentIndex);
                     materialDetailsView.containerId = model.container_id;
+                    materialDetailsView.currentMaterialNode = model.container_node;
 
                     detailsPanel.updateMaterialPropertiesObject();
                 }
@@ -303,8 +304,8 @@ Item
                 materialProperties.name = currentItem.name;
                 materialProperties.guid = currentItem.guid;
 
-                materialProperties.supplier = currentItem.brand ? currentItem.brand : "Unknown";
-                materialProperties.material_type = currentItem.material ? currentItem.material : "Unknown";
+                materialProperties.brand = currentItem.brand ? currentItem.brand : "Unknown";
+                materialProperties.material = currentItem.material ? currentItem.material : "Unknown";
                 materialProperties.color_name = currentItem.color_name ? currentItem.color_name : "Yellow";
                 materialProperties.color_code = currentItem.color_code ? currentItem.color_code : "yellow";
 
@@ -358,6 +359,7 @@ Item
 
                     properties: materialProperties
                     containerId: base.currentItem != null ? base.currentItem.id : ""
+                    currentMaterialNode: base.currentItem
 
                     property alias pane: base
                 }
@@ -369,8 +371,9 @@ Item
                     property string guid: "00000000-0000-0000-0000-000000000000"
                     property string name: "Unknown";
                     property string profile_type: "Unknown";
-                    property string supplier: "Unknown";
-                    property string material_type: "Unknown";
+                    property string brand: "Unknown";
+                    property string material: "Unknown";  // This needs to be named as "material" to be consistent with
+                                                          // the material container's metadata entry
 
                     property string color_name: "Yellow";
                     property color color_code: "yellow";