Browse Source

Merge branch 'master' of https://github.com/Ultimaker/Cura into master-using-platform

Thomas Karl Pietrowski 8 years ago
parent
commit
536e0f489a

+ 0 - 1
cura/BuildVolume.py

@@ -199,7 +199,6 @@ class BuildVolume(SceneNode):
         disallowed_areas = self._active_container_stack.getProperty("machine_disallowed_areas", "value")
         areas = []
 
-        skirt_size = 0.0
         skirt_size = self._getSkirtSize(self._active_container_stack)
 
         if disallowed_areas:

+ 19 - 3
cura/CrashHandler.py

@@ -5,17 +5,33 @@ import webbrowser
 
 from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QCoreApplication
 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit
+
+from UM.Logger import Logger
 from UM.i18n import i18nCatalog
 catalog = i18nCatalog("cura")
 
+# List of exceptions that should be considered "fatal" and abort the program.
+# These are primarily some exception types that we simply cannot really recover from
+# (MemoryError and SystemError) and exceptions that indicate grave errors in the
+# code that cause the Python interpreter to fail (SyntaxError, ImportError). 
+fatal_exception_types = [
+    MemoryError,
+    SyntaxError,
+    ImportError,
+    SystemError,
+]
+
 def show(exception_type, value, tb):
     debug_mode = False
     if QCoreApplication.instance():
         debug_mode = QCoreApplication.instance().getCommandLineOption("debug-mode", False)
 
-    traceback.print_exception(exception_type, value, tb)
+    Logger.log("c", "An uncaught exception has occurred!")
+    for line in traceback.format_exception(exception_type, value, tb):
+        for part in line.rstrip("\n").split("\n"):
+            Logger.log("c", part)
 
-    if not debug_mode:
+    if not debug_mode and exception_type not in fatal_exception_types:
         return
 
     application = QCoreApplication.instance()
@@ -29,7 +45,7 @@ def show(exception_type, value, tb):
 
     label = QLabel(dialog)
     layout.addWidget(label)
-    label.setText(catalog.i18nc("@label", "<p>An uncaught exception has occurred!</p><p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>"))
+    label.setText(catalog.i18nc("@label", "<p>A fatal exception has occurred that we could not recover from!</p><p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>"))
 
     textarea = QTextEdit(dialog)
     layout.addWidget(textarea)

+ 208 - 0
cura/CuraContainerRegistry.py

@@ -0,0 +1,208 @@
+# Copyright (c) 2016 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+import os
+import os.path
+import re
+from PyQt5.QtWidgets import QMessageBox
+
+from UM.Settings.ContainerRegistry import ContainerRegistry
+from UM.Settings.ContainerStack import ContainerStack
+from UM.Settings.InstanceContainer import InstanceContainer
+from UM.Application import Application
+from UM.Logger import Logger
+from UM.Message import Message
+from UM.Platform import Platform
+from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with.
+from UM.Util import parseBool
+
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+
+class CuraContainerRegistry(ContainerRegistry):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+    ##  Create a name that is not empty and unique
+    #   \param container_type \type{string} Type of the container (machine, quality, ...)
+    #   \param current_name \type{} Current name of the container, which may be an acceptable option
+    #   \param new_name \type{string} Base name, which may not be unique
+    #   \param fallback_name \type{string} Name to use when (stripped) new_name is empty
+    #   \return \type{string} Name that is unique for the specified type and name/id
+    def createUniqueName(self, container_type, current_name, new_name, fallback_name):
+        new_name = new_name.strip()
+        num_check = re.compile("(.*?)\s*#\d+$").match(new_name)
+        if num_check:
+            new_name = num_check.group(1)
+        if new_name == "":
+            new_name = fallback_name
+
+        unique_name = new_name
+        i = 1
+        # In case we are renaming, the current name of the container is also a valid end-result
+        while self._containerExists(container_type, unique_name) and unique_name != current_name:
+            i += 1
+            unique_name = "%s #%d" % (new_name, i)
+
+        return unique_name
+
+    ##  Check if a container with of a certain type and a certain name or id exists
+    #   Both the id and the name are checked, because they may not be the same and it is better if they are both unique
+    #   \param container_type \type{string} Type of the container (machine, quality, ...)
+    #   \param container_name \type{string} Name to check
+    def _containerExists(self, container_type, container_name):
+        container_class = ContainerStack if container_type == "machine" else InstanceContainer
+
+        return self.findContainers(container_class, id = container_name, type = container_type) or \
+                self.findContainers(container_class, name = container_name, type = container_type)
+
+    ##  Exports an profile to a file
+    #
+    #   \param instance_id \type{str} the ID of the profile to export.
+    #   \param file_name \type{str} the full path and filename to export to.
+    #   \param file_type \type{str} the file type with the format "<description> (*.<extension>)"
+    def exportProfile(self, instance_id, file_name, file_type):
+        Logger.log('d', 'exportProfile instance_id: '+str(instance_id))
+
+        # Parse the fileType to deduce what plugin can save the file format.
+        # fileType has the format "<description> (*.<extension>)"
+        split = file_type.rfind(" (*.")  # Find where the description ends and the extension starts.
+        if split < 0:  # Not found. Invalid format.
+            Logger.log("e", "Invalid file format identifier %s", file_type)
+            return
+        description = file_type[:split]
+        extension = file_type[split + 4:-1]  # Leave out the " (*." and ")".
+        if not file_name.endswith("." + extension):  # Auto-fill the extension if the user did not provide any.
+            file_name += "." + extension
+
+        # On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself.
+        if not Platform.isWindows():
+            if os.path.exists(file_name):
+                result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
+                                              catalog.i18nc("@label", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
+                if result == QMessageBox.No:
+                    return
+
+        containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id)
+        if not containers:
+            return
+        container = containers[0]
+
+        profile_writer = self._findProfileWriter(extension, description)
+
+        try:
+            success = profile_writer.write(file_name, container)
+        except Exception as e:
+            Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
+            m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), lifetime = 0)
+            m.show()
+            return
+        if not success:
+            Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name)
+            m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name), lifetime = 0)
+            m.show()
+            return
+        m = Message(catalog.i18nc("@info:status", "Exported profile to <filename>{0}</filename>", file_name))
+        m.show()
+
+    ##  Gets the plugin object matching the criteria
+    #   \param extension
+    #   \param description
+    #   \return The plugin object matching the given extension and description.
+    def _findProfileWriter(self, extension, description):
+        pr = PluginRegistry.getInstance()
+        for plugin_id, meta_data in self._getIOPlugins("profile_writer"):
+            for supported_type in meta_data["profile_writer"]:  # All file types this plugin can supposedly write.
+                supported_extension = supported_type.get("extension", None)
+                if supported_extension == extension:  # This plugin supports a file type with the same extension.
+                    supported_description = supported_type.get("description", None)
+                    if supported_description == description:  # The description is also identical. Assume it's the same file type.
+                        return pr.getPluginObject(plugin_id)
+        return None
+
+    ##  Imports a profile from a file
+    #
+    #   \param file_name \type{str} the full path and filename of the profile to import
+    #   \return \type{Dict} dict with a 'status' key containing the string 'ok' or 'error', and a 'message' key
+    #       containing a message for the user
+    def importProfile(self, file_name):
+        if not file_name:
+            return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")}
+
+        pr = PluginRegistry.getInstance()
+        for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
+            profile_reader = pr.getPluginObject(plugin_id)
+            try:
+                profile = profile_reader.read(file_name) #Try to open the file with the profile reader.
+            except Exception as e:
+                #Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
+                Logger.log("e", "Failed to import profile from %s: %s", file_name, str(e))
+                return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, str(e))}
+            if profile: #Success!
+                profile.setReadOnly(False)
+
+                new_name = self.createUniqueName("quality", "", os.path.splitext(os.path.basename(file_name))[0],
+                                                 catalog.i18nc("@label", "Custom profile"))
+                profile.setName(new_name)
+
+                if self._machineHasOwnQualities():
+                    profile.setDefinition(self._activeDefinition())
+                    if self._machineHasOwnMaterials():
+                        profile.addMetaDataEntry("material", self._activeMaterialId())
+                else:
+                    profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
+                ContainerRegistry.getInstance().addContainer(profile)
+
+                return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) }
+
+        #If it hasn't returned by now, none of the plugins loaded the profile successfully.
+        return { "status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type.", file_name)}
+
+    ##  Gets a list of profile writer plugins
+    #   \return List of tuples of (plugin_id, meta_data).
+    def _getIOPlugins(self, io_type):
+        pr = PluginRegistry.getInstance()
+        active_plugin_ids = pr.getActivePlugins()
+
+        result = []
+        for plugin_id in active_plugin_ids:
+            meta_data = pr.getMetaData(plugin_id)
+            if io_type in meta_data:
+                result.append( (plugin_id, meta_data) )
+        return result
+
+    ##  Gets the active definition
+    #   \return the active definition object or None if there is no definition
+    def _activeDefinition(self):
+        global_container_stack = Application.getInstance().getGlobalContainerStack()
+        if global_container_stack:
+            definition = global_container_stack.getBottom()
+            if definition:
+                return definition
+        return None
+
+    ##  Returns true if the current machine requires its own materials
+    #   \return True if the current machine requires its own materials
+    def _machineHasOwnMaterials(self):
+        global_container_stack = Application.getInstance().getGlobalContainerStack()
+        if global_container_stack:
+            return global_container_stack.getMetaDataEntry("has_materials", False)
+        return False
+
+    ##  Gets the ID of the active material
+    #   \return the ID of the active material or the empty string
+    def _activeMaterialId(self):
+        global_container_stack = Application.getInstance().getGlobalContainerStack()
+        if global_container_stack:
+            material = global_container_stack.findContainer({"type": "material"})
+            if material:
+                return material.getId()
+        return ""
+
+    ##  Returns true if the current machien requires its own quality profiles
+    #   \return true if the current machien requires its own quality profiles
+    def _machineHasOwnQualities(self):
+        global_container_stack = Application.getInstance().getGlobalContainerStack()
+        if global_container_stack:
+            return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False))
+        return False

+ 11 - 9
cura/ExtruderManager.py

@@ -18,7 +18,7 @@ class ExtruderManager(QObject):
     ##  Notify when the user switches the currently active extruder.
     activeExtruderChanged = pyqtSignal()
 
-    ##  Registers listeners and such to listen to chafnges to the extruders.
+    ##  Registers listeners and such to listen to changes to the extruders.
     def __init__(self, parent = None):
         super().__init__(parent)
         self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
@@ -67,12 +67,14 @@ class ExtruderManager(QObject):
         self.activeExtruderChanged.emit()
 
     def getActiveExtruderStack(self):
-        try:
-            return self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getBottom().getId()][str(self._active_extruder_index)]
-        except AttributeError:
-            return None
-        except KeyError:
-            return None
+        global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
+        if global_container_stack:
+            global_definition_container = UM.Application.getInstance().getGlobalContainerStack().getBottom()
+            if global_definition_container:
+                if global_definition_container.getId() in self._extruder_trains:
+                    if str(self._active_extruder_index) in self._extruder_trains[global_definition_container.getId()]:
+                        return self._extruder_trains[global_definition_container.getId()][str(self._active_extruder_index)]
+
 
     ##  Adds all extruders of a specific machine definition to the extruder
     #   manager.
@@ -100,7 +102,7 @@ class ExtruderManager(QObject):
         for extruder_train in extruder_trains:
             self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
 
-            ## Ensure that the extruder train stacks are linked to global stack.
+            #Ensure that the extruder train stacks are linked to global stack.
             extruder_train.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
 
         if extruder_trains:
@@ -180,7 +182,7 @@ class ExtruderManager(QObject):
             quality = qualities[0]
         preferred_quality_id = machine_definition.getMetaDataEntry("preferred_quality")
         if preferred_quality_id:
-            preferred_quality = container_registry.findInstanceContainers(id = preferred_quality_id.lower(), type = "quality")
+            preferred_quality = container_registry.findInstanceContainers(id = preferred_quality_id, type = "quality")
             if len(preferred_quality) >= 1:
                 quality = preferred_quality[0]
             else:

+ 5 - 4
cura/ExtrudersModel.py

@@ -28,9 +28,9 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
     #   containers.
     IndexRole = Qt.UserRole + 4
 
-    ##  Colour to display if there is no material or the material has no known
+    ##  List of colours to display if there is no material or the material has no known
     #   colour.
-    defaultColour = "#FFFF00"
+    defaultColours = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
 
     ##  Initialises the extruders model, defining the roles and listening for
     #   changes in the data.
@@ -75,7 +75,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
 
         if self._add_global:
             material = global_container_stack.findContainer({ "type": "material" })
-            colour = material.getMetaDataEntry("color_code", default = self.defaultColour) if material else self.defaultColour
+            colour = material.getMetaDataEntry("color_code", default = self.defaultColours[0]) if material else self.defaultColours[0]
             item = {
                 "id": global_container_stack.getId(),
                 "name": "Global",
@@ -86,12 +86,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
 
         for extruder in manager.getMachineExtruders(global_container_stack.getBottom().getId()):
             material = extruder.findContainer({ "type": "material" })
-            colour = material.getMetaDataEntry("color_code", default = self.defaultColour) if material else self.defaultColour
             position = extruder.getBottom().getMetaDataEntry("position", default = "0") #Position in the definition.
             try:
                 position = int(position)
             except ValueError: #Not a proper int.
                 position = -1
+            default_colour = self.defaultColours[position] if position >= 0 and position < len(self.defaultColours) else defaultColours[0]
+            colour = material.getMetaDataEntry("color_code", default = default_colour) if material else default_colour
             item = { #Construct an item with only the relevant information.
                 "id": extruder.getId(),
                 "name": extruder.getName(),

+ 33 - 42
cura/MachineManagerModel.py

@@ -1,5 +1,5 @@
-
-import re
+# 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
 from UM.Application import Application
@@ -8,6 +8,8 @@ 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
@@ -49,6 +51,8 @@ class MachineManagerModel(QObject):
 
         active_machine_id = Preferences.getInstance().getValue("cura/active_machine")
 
+        Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
+
         if active_machine_id != "":
             # An active machine was saved, so restore it.
             self.setActiveMachine(active_machine_id)
@@ -61,16 +65,14 @@ class MachineManagerModel(QObject):
     activeStackChanged = pyqtSignal()
 
     globalValueChanged = pyqtSignal()  # Emitted whenever a value inside global container is changed.
-    globalValidationChanged = pyqtSignal()  # Emitted whenever a validation inside global container is changed.
+    globalValidationChanged = pyqtSignal()  # Emitted whenever a validation inside global container is changed
+
+    blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
+
+    outputDevicesChanged = pyqtSignal()
 
-    @pyqtProperty("QVariantMap", notify = globalContainerChanged)
-    def extrudersIds(self):
-        ## Find all extruders that reference the new stack
-        extruders = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(**{"machine": self._global_container_stack.getId()})
-        result = {}
-        for extruder in extruders:
-            result[extruder.getMetaDataEntry("position")] = extruder.getId()
-        return result
+    def _onOutputDevicesChanged(self):
+        self.outputDevicesChanged.emit()
 
     def _onGlobalPropertyChanged(self, key, property_name):
         if property_name == "value":
@@ -102,8 +104,15 @@ class MachineManagerModel(QObject):
             self._global_stack_valid = not self._checkStackForErrors(self._global_container_stack)
 
     def _onActiveExtruderStackChanged(self):
+        if self._active_container_stack and self._active_container_stack != self._global_container_stack:
+            self._active_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
+            self._active_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
+
         self._active_container_stack = ExtruderManager.ExtruderManager.getInstance().getActiveExtruderStack()
-        if not self._active_container_stack:
+        if self._active_container_stack:
+            self._active_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
+            self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
+        else:
             self._active_container_stack = self._global_container_stack
 
     def _onInstanceContainersChanged(self, container):
@@ -155,6 +164,10 @@ class MachineManagerModel(QObject):
 
             Application.getInstance().setGlobalContainerStack(new_global_stack)
 
+    @pyqtProperty("QVariantList", notify = outputDevicesChanged)
+    def printerOutputDevices(self):
+        return [printer_output_device for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices() if isinstance(printer_output_device, PrinterOutputDevice)]
+
     ##  Create a name that is not empty and unique
     #   \param container_type \type{string} Type of the container (machine, quality, ...)
     #   \param current_name \type{} Current name of the container, which may be an acceptable option
@@ -162,31 +175,7 @@ class MachineManagerModel(QObject):
     #   \param fallback_name \type{string} Name to use when (stripped) new_name is empty
     #   \return \type{string} Name that is unique for the specified type and name/id
     def _createUniqueName(self, container_type, current_name, new_name, fallback_name):
-        new_name = new_name.strip()
-        num_check = re.compile("(.*?)\s*#\d+$").match(new_name)
-        if num_check:
-            new_name = num_check.group(1)
-        if new_name == "":
-            new_name = fallback_name
-
-        unique_name = new_name
-        i = 1
-        # In case we are renaming, the current name of the container is also a valid end-result
-        while self._containerExists(container_type, unique_name) and unique_name != current_name:
-            i += 1
-            unique_name = "%s #%d" % (new_name, i)
-
-        return unique_name
-
-    ##  Check if a container with of a certain type and a certain name or id exists
-    #   Both the id and the name are checked, because they may not be the same and it is better if they are both unique
-    #   \param container_type \type{string} Type of the container (machine, quality, ...)
-    #   \param container_name \type{string} Name to check
-    def _containerExists(self, container_type, container_name):
-        container_class = ContainerStack if container_type == "machine" else InstanceContainer
-
-        return UM.Settings.ContainerRegistry.getInstance().findContainers(container_class, id = container_name, type = container_type) or \
-                UM.Settings.ContainerRegistry.getInstance().findContainers(container_class, name = container_name, type = container_type)
+        return UM.Settings.ContainerRegistry.getInstance().createUniqueName(container_type, current_name, new_name, fallback_name)
 
     ##  Convenience function to check if a stack has errors.
     def _checkStackForErrors(self, stack):
@@ -205,6 +194,7 @@ class MachineManagerModel(QObject):
         if not self._active_container_stack:
             return
 
+        self.blurSettings.emit()
         user_settings = self._active_container_stack.getTop()
         user_settings.clear()
 
@@ -292,6 +282,7 @@ class MachineManagerModel(QObject):
         new_container_id = self.duplicateContainer(self.activeQualityId)
         if new_container_id == "":
             return
+        self.blurSettings.emit()
         self.setActiveQuality(new_container_id)
         self.updateQualityContainerFromUserContainer()
 
@@ -435,7 +426,7 @@ class MachineManagerModel(QObject):
 
     @pyqtProperty(str, notify = globalContainerChanged)
     def activeDefinitionId(self):
-        if self._active_container_stack:
+        if self._global_container_stack:
             definition = self._global_container_stack.getBottom()
             if definition:
                 return definition.id
@@ -469,15 +460,15 @@ class MachineManagerModel(QObject):
 
     @pyqtProperty(bool, notify = globalContainerChanged)
     def hasMaterials(self):
-        if self._active_container_stack:
-            return bool(self._active_container_stack.getMetaDataEntry("has_materials", False))
+        if self._global_container_stack:
+            return bool(self._global_container_stack.getMetaDataEntry("has_materials", False))
 
         return False
 
     @pyqtProperty(bool, notify = globalContainerChanged)
     def hasVariants(self):
-        if self._active_container_stack:
-            return bool(self._active_container_stack.getMetaDataEntry("has_variants", False))
+        if self._global_container_stack:
+            return bool(self._global_container_stack.getMetaDataEntry("has_variants", False))
 
         return False
 

+ 3 - 1
cura/PrinterOutputDevice.py

@@ -3,6 +3,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
 from enum import IntEnum  # For the connection state tracking.
 from UM.Logger import Logger
 
+from UM.Signal import signalemitter
 
 ##  Printer output device adds extra interface options on top of output device.
 #
@@ -13,7 +14,8 @@ from UM.Logger import Logger
 #   functions to actually have the implementation.
 #
 #   For all other uses it should be used in the same way as a "regular" OutputDevice.
-class PrinterOutputDevice(OutputDevice, QObject):
+@signalemitter
+class PrinterOutputDevice(QObject, OutputDevice):
     def __init__(self, device_id, parent = None):
         super().__init__(device_id = device_id, parent = parent)
 

+ 3 - 3
cura/SettingOverrideDecorator.py

@@ -51,8 +51,8 @@ class SettingOverrideDecorator(SceneNodeDecorator):
     def getActiveExtruder(self):
         return self._extruder_stack
 
-    def _onSettingChanged(self, instance, property):
-        if property == "value":  # Only reslice if the value has changed.
+    def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function
+        if property_name == "value":  # Only reslice if the value has changed.
             Application.getInstance().getBackend().forceSlice()
 
     ##  Makes sure that the stack upon which the container stack is placed is
@@ -78,4 +78,4 @@ class SettingOverrideDecorator(SceneNodeDecorator):
         self.activeExtruderChanged.emit()
 
     def getStack(self):
-        return self._stack
+        return self._stack

+ 4 - 0
cura_app.py

@@ -35,6 +35,7 @@ sys.excepthook = exceptHook
 import Arcus #@UnusedImport
 from UM.Platform import Platform
 import cura.CuraApplication
+import cura.CuraContainerRegistry
 
 if Platform.isWindows() and hasattr(sys, "frozen"):
     dirpath = os.path.expanduser("~/AppData/Local/cura/")
@@ -42,5 +43,8 @@ if Platform.isWindows() and hasattr(sys, "frozen"):
     sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w")
     sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w")
 
+# Force an instance of CuraContainerRegistry to be created and reused later.
+cura.CuraContainerRegistry.CuraContainerRegistry.getInstance()
+
 app = cura.CuraApplication.CuraApplication.getInstance()
 app.run()

+ 0 - 3
plugins/CuraEngineBackend/CuraEngineBackend.py

@@ -113,9 +113,6 @@ class CuraEngineBackend(Backend):
         json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
         return [Preferences.getInstance().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, "-vv"]
 
-    def close(self):
-        self._terminate()   # Forcefully shutdown the backend.
-
     ##  Emitted when we get a message containing print duration and material amount. This also implies the slicing has finished.
     #   \param time The amount of time the print will take.
     #   \param material_amount The amount of material the print will use.

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