Browse Source

Merge branch 'master' of https://github.com/Ultimaker/Cura

fieldOfView 8 years ago
parent
commit
184247ced6

+ 1 - 1
cura/ConvexHullDecorator.py

@@ -101,7 +101,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
         if key == "print_sequence" and property_name == "value":
             self._onChanged()
 
-    def _onChanged(self):
+    def _onChanged(self, *args):
         if self._convex_hull_job:
             self._convex_hull_job.cancel()
         self.setConvexHull(None)

+ 5 - 3
cura/CuraApplication.py

@@ -18,6 +18,7 @@ from UM.JobQueue import JobQueue
 from UM.SaveFile import SaveFile
 from UM.Scene.Selection import Selection
 from UM.Scene.GroupDecorator import GroupDecorator
+import UM.Settings.Validator
 
 from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
 from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
@@ -30,7 +31,7 @@ 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
@@ -89,6 +90,7 @@ class CuraApplication(QtApplication):
 
         # Need to do this before ContainerRegistry tries to load the machines
         SettingDefinition.addSupportedProperty("global_only", DefinitionPropertyType.Function, default = False)
+        SettingDefinition.addSettingType("extruder", int, str, UM.Settings.Validator)
 
         super().__init__(name = "cura", version = CuraVersion)
 
@@ -327,8 +329,6 @@ class CuraApplication(QtApplication):
         qmlRegisterSingletonType(MachineManagerModel.MachineManagerModel, "Cura", 1, 0, "MachineManager",
                                  MachineManagerModel.createMachineManagerModel)
 
-        self._extruder_manager = ExtruderManager.ExtruderManager()
-
         self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
         self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
         self.initializeEngine()
@@ -368,6 +368,8 @@ class CuraApplication(QtApplication):
 
         qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
 
+        qmlRegisterType(ExtrudersModel.ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
+
         qmlRegisterSingletonType(QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")), "Cura", 1, 0, "Actions")
 
         for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles):

+ 45 - 6
cura/Extruder.py

@@ -25,8 +25,8 @@ class Extruder:
         self._nozzles += container_registry.findInstanceContainers(type = "nozzle", definitions = self._definition.getId())
 
         #Create a container stack for this extruder.
-        name = self._uniqueName(self._definition.getId())
-        self._container_stack = UM.Settings.ContainerStack(name)
+        self._name = self._uniqueName(self._definition.getId())
+        self._container_stack = UM.Settings.ContainerStack(self._name)
         self._container_stack.addMetaDataEntry("type", "extruder_train")
         self._container_stack.addContainer(self._definition)
 
@@ -73,15 +73,38 @@ class Extruder:
             self._container_stack.addContainer(self._quality)
 
         #Add an empty user profile.
-        self._user_profile = UM.Settings.InstanceContainer(name + "_current_settings")
+        self._user_profile = UM.Settings.InstanceContainer(self._name + "_current_settings")
         self._user_profile.addMetaDataEntry("type", "user")
         self._container_stack.addContainer(self._user_profile)
 
         self._container_stack.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
 
-    nozzle_changed = UM.Signal.Signal()
-    material_changed = UM.Signal.Signal()
-    quality_changed = UM.Signal.Signal()
+    definition_changed = UM.Signal()
+    material_changed = UM.Signal()
+    name_changed = UM.Signal()
+    nozzle_changed = UM.Signal()
+    quality_changed = UM.Signal()
+
+    ##  Gets the definition container of this extruder.
+    #
+    #   \return The definition container of this extruder.
+    @property
+    def definition(self):
+        return self._definition
+
+    ##  Changes the definition container of this extruder.
+    #
+    #   \param value The new definition for this extruder.
+    @definition.setter
+    def definition(self, value):
+        try:
+            position = self._container_stack.index(self._definition)
+        except ValueError: #Definition is not in the list. Big trouble!
+            UM.Logger.log("e", "I've lost my old extruder definition, so I can't find where to insert the new definition.")
+            return
+        self._container_stack.replaceContainer(position, value)
+        self._definition = value
+        self.definition_changed.emit()
 
     ##  Gets the currently active material on this extruder.
     #
@@ -104,6 +127,22 @@ class Extruder:
         self._material = value
         self.material_changed.emit()
 
+    ##  Gets the name of this extruder.
+    #
+    #   \return The name of this extruder.
+    @property
+    def name(self):
+        return self._name
+
+    ##  Changes the name of this extruder.
+    #
+    #   \param value The new name for this extruder.
+    @name.setter
+    def name(self, value):
+        self._name = value
+        self._container_stack.setName(value) #Also update in container stack, being defensive.
+        self.name_changed.emit()
+
     ##  Gets the currently active nozzle on this extruder.
     #
     #   \return The currently active nozzle on this extruder.

+ 36 - 13
cura/ExtruderManager.py

@@ -1,13 +1,11 @@
 # Copyright (c) 2016 Ultimaker B.V.
 # Cura is released under the terms of the AGPLv3 or higher.
 
-import re
-
 from cura.Extruder import Extruder #The individual extruders managed by this manager.
-from UM.Application import Application #To get the global container stack to find the current machine.
-from UM.Logger import Logger
-from UM.Settings.ContainerStack import ContainerStack #To create container stacks for each extruder.
-from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID.
+import UM.Application #To get the global container stack to find the current machine.
+import UM.Logger
+import UM.Settings.ContainerRegistry #Finding containers by ID.
+import UM.Signal #To notify other components of changes in the extruders.
 
 
 ##  Class that handles the current extruder stack.
@@ -16,19 +14,42 @@ from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers
 #   and makes sure that whenever the machine is swapped, this list is kept up to
 #   date. It also contains and updates the setting stacks for the extruders.
 class ExtruderManager:
+    ##  The singleton instance of this manager.
+    __instance = None
+
+    ##  Signal to notify other components when the list of extruders changes.
+    extrudersChanged = UM.Signal()
+
     ##  Registers listeners and such to listen to changes to the extruders.
     def __init__(self):
         self._extruders = [] #Extruders for the current machine.
         self._global_container_stack = None
+        self._next_item = 0 #For when you use this class as iterator.
+
+        UM.Application.getInstance().globalContainerStackChanged.connect(self._reconnectExtruderReload) #When the current machine changes, we need to reload all extruders belonging to the new machine.
 
-        Application.getInstance().globalContainerStackChanged.connect(self._reconnectExtruderReload) #When the current machine changes, we need to reload all extruders belonging to the new machine.
+    ##  Gets an instance of this extruder manager.
+    #
+    #   If an instance was already created, the old instance is returned. This
+    #   implements the singleton pattern.
+    @classmethod
+    def getInstance(cls):
+        if not cls.__instance:
+            cls.__instance = ExtruderManager()
+        return cls.__instance
+
+    ##  Creates an iterator over the extruders in this manager.
+    #
+    #   \return An iterator over the extruders in this manager.
+    def __iter__(self):
+        return iter(self._extruders)
 
     ##  When the global container stack changes, this reconnects to the new
     #   signal for containers changing.
     def _reconnectExtruderReload(self):
         if self._global_container_stack:
             self._global_container_stack.containersChanged.disconnect(self._reloadExtruders) #Disconnect from the old global container stack.
-        self._global_container_stack = Application.getInstance().getGlobalContainerStack()
+        self._global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
         self._global_container_stack.containersChanged.connect(self._reloadExtruders) #When the current machine changes, we need to reload all extruders belonging to the new machine.
 
     ##  (Re)loads all extruders of the currently active machine.
@@ -36,18 +57,20 @@ class ExtruderManager:
     #   This looks at the global container stack to see which machine is active.
     #   Then it loads the extruders for that machine and loads each of them in a
     #   list of extruders.
-    def _reloadExtruders(self):
+    def _reloadExtruders(self, *args):
         self._extruders = []
         if not self._global_container_stack: #No machine has been added yet.
+            self.extrudersChanged.emit() #Yes, we just cleared the _extruders list!
             return #Then leave them empty!
 
         #Get the extruder definitions belonging to the current machine.
         machine = self._global_container_stack.getBottom()
-        extruder_train_ids = machine.getMetaData("machine_extruder_trains")
+        extruder_train_ids = machine.getMetaDataEntry("machine_extruder_trains")
         for extruder_train_id in extruder_train_ids:
-            extruder_definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = extruder_train_id) #Should be only 1 definition if IDs are unique, but add the whole list anyway.
+            extruder_definitions = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = extruder_train_id) #Should be only 1 definition if IDs are unique, but add the whole list anyway.
             if not extruder_definitions: #Empty list or error.
-                Logger.log("w", "Machine definition %s refers to an extruder train \"%s\", but no such extruder was found.", machine.getId(), extruder_train_id)
+                UM.Logger.log("w", "Machine definition %s refers to an extruder train \"%s\", but no such extruder was found.", machine.getId(), extruder_train_id)
                 continue
             for extruder_definition in extruder_definitions:
-                self._extruders.append(Extruder(extruder_definition))
+                self._extruders.append(Extruder(extruder_definition))
+        self.extrudersChanged.emit()

+ 56 - 0
cura/ExtrudersModel.py

@@ -0,0 +1,56 @@
+# Copyright (c) 2016 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+from PyQt5.QtCore import Qt
+
+import cura.ExtruderManager
+import UM.Qt.ListModel
+
+##  Model that holds extruders.
+#
+#   This model is designed for use by any list of extruders, but specifically
+#   intended for drop-down lists of extruders in place of settings.
+class ExtrudersModel(UM.Qt.ListModel.ListModel):
+    ##  Human-readable name of the extruder.
+    NameRole = Qt.UserRole + 1
+
+    ##  Colour of the material loaded in the extruder.
+    ColourRole = Qt.UserRole + 2
+
+    ##  Index of the extruder, which is also the value of the setting itself.
+    #
+    #   An index of 0 indicates the first extruder, an index of 1 the second
+    #   one, and so on. This is the value that will be saved in instance
+    #   containers.
+    IndexRole = Qt.UserRole + 3
+
+    ##  Initialises the extruders model, defining the roles and listening for
+    #   changes in the data.
+    #
+    #   \param parent Parent QtObject of this list.
+    def __init__(self, parent = None):
+        super().__init__(parent)
+
+        self.addRoleName(self.NameRole, "name")
+        self.addRoleName(self.ColourRole, "colour")
+        self.addRoleName(self.IndexRole, "index")
+
+        #Listen to changes.
+        manager = cura.ExtruderManager.ExtruderManager.getInstance()
+        manager.extrudersChanged.connect(self._updateExtruders)
+        self._updateExtruders()
+
+    ##  Update the list of extruders.
+    #
+    #   This should be called whenever the list of extruders changes.
+    def _updateExtruders(self):
+        self.clear()
+        manager = cura.ExtruderManager.ExtruderManager.getInstance()
+        for index, extruder in enumerate(manager):
+            item = { #Construct an item with only the relevant information.
+                "name": extruder.name,
+                "colour": extruder.material.getMetaDataEntry("color_code", default = "#FFFF00"),
+                "index": index
+            }
+            self.appendItem(item)
+        self.sort(lambda item: item["index"])

+ 6 - 0
cura/SettingOverrideDecorator.py

@@ -18,11 +18,17 @@ class SettingOverrideDecorator(SceneNodeDecorator):
         self._instance = InstanceContainer(container_id = "SettingOverrideInstanceContainer")
         self._stack.addContainer(self._instance)
 
+        self._stack.propertyChanged.connect(self._onSettingChanged)
+
         ContainerRegistry.getInstance().addContainer(self._stack)
 
         Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
         self._onGlobalContainerStackChanged()
 
+    def _onSettingChanged(self, instance, property):
+        if property == "value":  # Only reslice if the value has changed.
+            Application.getInstance().getBackend().forceSlice()
+
     def _onGlobalContainerStackChanged(self):
         ## Ensure that the next stack is always the global stack.
         self._stack.setNextStack(Application.getInstance().getGlobalContainerStack())

+ 17 - 2
plugins/CuraEngineBackend/StartSliceJob.py

@@ -3,7 +3,6 @@
 
 import numpy
 from string import Formatter
-import traceback
 from enum import IntEnum
 
 from UM.Job import Job
@@ -59,7 +58,7 @@ class StartSliceJob(Job):
             self.setResult(StartJobResult.Error)
             return
 
-        #Don't slice if there is a setting with an error value.
+        # Don't slice if there is a setting with an error value.
         for key in stack.getAllKeys():
             validation_state = stack.getProperty(key, "validationState")
             if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
@@ -69,6 +68,22 @@ class StartSliceJob(Job):
 
             Job.yieldThread()
 
+        # Don't slice if there is a per object setting with an error value.
+        for node in DepthFirstIterator(self._scene.getRoot()):
+            if type(node) is not SceneNode or not node.isSelectable():
+                continue
+
+            node_stack = node.callDecoration("getStack")
+            if node_stack:
+                for key in node_stack.getAllKeys():
+                    validation_state = node_stack.getProperty(key, "validationState")
+                    if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
+                        Logger.log("w", "Per object setting %s is not valid, but %s. Aborting slicing.", key, validation_state)
+                        self.setResult(StartJobResult.SettingError)
+                        return
+
+                    Job.yieldThread()
+
         with self._scene.getSceneLock():
             # Remove old layer data.
             for node in DepthFirstIterator(self._scene.getRoot()):

+ 2 - 1
plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py

@@ -5,7 +5,8 @@ from UM.Logger import Logger
 
 from cura.SettingOverrideDecorator import SettingOverrideDecorator
 
-
+##  The per object setting visibility handler ensures that only setting defintions that have a matching instance Container
+#   are returned as visible. 
 class PerObjectSettingVisibilityHandler(QObject):
     def __init__(self, parent = None, *args, **kwargs):
         super().__init__(parent = parent, *args, **kwargs)

+ 0 - 75
plugins/PerObjectSettingsTool/PerObjectSettingsModel.py

@@ -1,75 +0,0 @@
-# Copyright (c) 2015 Ultimaker B.V.
-# Uranium is released under the terms of the AGPLv3 or higher.
-
-from PyQt5.QtCore import Qt, pyqtSlot, QUrl
-
-from UM.Application import Application
-from UM.Qt.ListModel import ListModel
-from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
-from UM.Scene.SceneNode import SceneNode
-#from UM.Settings.SettingOverrideDecorator import SettingOverrideDecorator
-#from UM.Settings.ProfileOverrideDecorator import ProfileOverrideDecorator
-
-from . import SettingOverrideModel
-
-class PerObjectSettingsModel(ListModel):
-    IdRole = Qt.UserRole + 1  # ID of the node
-
-    def __init__(self, parent = None):
-        super().__init__(parent)
-        self._scene = Application.getInstance().getController().getScene()
-        self._root = self._scene.getRoot()
-        self.addRoleName(self.IdRole,"id")
-
-        self._updateModel()
-
-    @pyqtSlot("quint64", str)
-    def setObjectProfile(self, object_id, profile_name):
-        self.setProperty(self.find("id", object_id), "profile", profile_name)
-
-        profile = None
-        '''if profile_name != "global":
-            profile = Application.getInstance().getMachineManager().findProfile(profile_name)
-
-        node = self._scene.findObject(object_id)
-        if profile:
-            if not node.getDecorator(ProfileOverrideDecorator):
-                node.addDecorator(ProfileOverrideDecorator())
-            node.callDecoration("setProfile", profile)
-        else:
-            if node.getDecorator(ProfileOverrideDecorator):
-                node.removeDecorator(ProfileOverrideDecorator)'''
-
-    @pyqtSlot("quint64", str)
-    def addOverride(self, object_id, key):
-        machine = Application.getInstance().getMachineManager().getActiveMachineInstance()
-        if not machine:
-            return
-
-        node = self._scene.findObject(object_id)
-        #if not node.getDecorator(SettingOverrideDecorator):
-        #    node.addDecorator(SettingOverrideDecorator())
-
-        node.callDecoration("addSetting", key)
-
-    @pyqtSlot("quint64", str)
-    def removerOverride(self, object_id, key):
-        node = self._scene.findObject(object_id)
-        node.callDecoration("removeSetting", key)
-
-        #if len(node.callDecoration("getAllSettings")) == 0:
-        #    node.removeDecorator(SettingOverrideDecorator)
-
-    def _updateModel(self):
-        self.clear()
-
-        for node in BreadthFirstIterator(self._root):
-            if type(node) is not SceneNode or not node.isSelectable():
-                continue
-
-            node_stack = node.callDecoration("getStack")
-
-            if not node_stack:
-                self.appendItem({
-                    "id": id(node)
-                })

+ 7 - 0
plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml

@@ -45,6 +45,7 @@ Item {
             {
                 Loader
                 {
+                    id: settingLoader
                     width: UM.Theme.getSize("setting").width;
                     height: UM.Theme.getSize("section").height;
 
@@ -57,6 +58,12 @@ Item {
                     //causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
                     asynchronous: model.type != "enum"
 
+                    onLoaded: {
+                        settingLoader.item.showRevertButton = false
+                        settingLoader.item.showInheritButton = false
+                        settingLoader.item.doDepthIdentation = false
+                    }
+
                     source:
                     {
                         switch(model.type) // TODO: This needs to be fixed properly. Got frustrated with it not working, so this is the patch job!

Some files were not shown because too many files changed in this diff