Browse Source

Merge branch 'master' into mypy_fixes

Conflicts:
cura/Backups/Backup.py
cura/Settings/ExtruderManager.py
cura/Settings/MachineManager.py
Diego Prado Gesto 6 years ago
parent
commit
554a3fd908

+ 15 - 19
cura/API/Backups.py

@@ -3,30 +3,26 @@
 from cura.Backups.BackupsManager import BackupsManager
 from cura.Backups.BackupsManager import BackupsManager
 
 
 
 
+##  The back-ups API provides a version-proof bridge between Cura's
+#   BackupManager and plug-ins that hook into it.
+#
+#   Usage:
+#       ``from cura.API import CuraAPI
+#       api = CuraAPI()
+#       api.backups.createBackup()
+#       api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})``
 class Backups:
 class Backups:
-    """
-    The backups API provides a version-proof bridge between Cura's BackupManager and plugins that hook into it.
-
-    Usage:
-        from cura.API import CuraAPI
-        api = CuraAPI()
-        api.backups.createBackup()
-        api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})
-    """
-
     manager = BackupsManager()  # Re-used instance of the backups manager.
     manager = BackupsManager()  # Re-used instance of the backups manager.
 
 
+    ##  Create a new back-up using the BackupsManager.
+    #   \return Tuple containing a ZIP file with the back-up data and a dict
+    #   with metadata about the back-up.
     def createBackup(self) -> (bytes, dict):
     def createBackup(self) -> (bytes, dict):
-        """
-        Create a new backup using the BackupsManager.
-        :return: Tuple containing a ZIP file with the backup data and a dict with meta data about the backup.
-        """
         return self.manager.createBackup()
         return self.manager.createBackup()
 
 
+    ##  Restore a back-up using the BackupsManager.
+    #   \param zip_file A ZIP file containing the actual back-up data.
+    #   \param meta_data Some metadata needed for restoring a back-up, like the
+    #   Cura version number.
     def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
     def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
-        """
-        Restore a backup using the BackupManager.
-        :param zip_file: A ZIP file containing the actual backup data.
-        :param meta_data: Some meta data needed for restoring a backup, like the Cura version number.
-        """
         return self.manager.restoreBackup(zip_file, meta_data)
         return self.manager.restoreBackup(zip_file, meta_data)

+ 6 - 7
cura/API/__init__.py

@@ -3,14 +3,13 @@
 from UM.PluginRegistry import PluginRegistry
 from UM.PluginRegistry import PluginRegistry
 from cura.API.Backups import Backups
 from cura.API.Backups import Backups
 
 
-
+##  The official Cura API that plug-ins can use to interact with Cura.
+#
+#   Python does not technically prevent talking to other classes as well, but
+#   this API provides a version-safe interface with proper deprecation warnings
+#   etc. Usage of any other methods than the ones provided in this API can cause
+#   plug-ins to be unstable.
 class CuraAPI:
 class CuraAPI:
-    """
-    The official Cura API that plugins can use to interact with Cura.
-    Python does not technically prevent talking to other classes as well,
-    but this API provides a version-safe interface with proper deprecation warnings etc.
-    Usage of any other methods than the ones provided in this API can cause plugins to be unstable.
-    """
 
 
     # For now we use the same API version to be consistent.
     # For now we use the same API version to be consistent.
     VERSION = PluginRegistry.APIVersion
     VERSION = PluginRegistry.APIVersion

+ 9 - 8
cura/Arranging/Arrange.py

@@ -1,10 +1,12 @@
 # Copyright (c) 2018 Ultimaker B.V.
 # Copyright (c) 2018 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 # Cura is released under the terms of the LGPLv3 or higher.
+from typing import List
 
 
 from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
 from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
 from UM.Logger import Logger
 from UM.Logger import Logger
 from UM.Math.Polygon import Polygon
 from UM.Math.Polygon import Polygon
 from UM.Math.Vector import Vector
 from UM.Math.Vector import Vector
+from UM.Scene.SceneNode import SceneNode
 from cura.Arranging.ShapeArray import ShapeArray
 from cura.Arranging.ShapeArray import ShapeArray
 from cura.Scene import ZOffsetDecorator
 from cura.Scene import ZOffsetDecorator
 
 
@@ -85,8 +87,7 @@ class Arrange:
     #   \param node
     #   \param node
     #   \param offset_shape_arr ShapeArray with offset, for placing the shape
     #   \param offset_shape_arr ShapeArray with offset, for placing the shape
     #   \param hull_shape_arr ShapeArray without offset, used to find location
     #   \param hull_shape_arr ShapeArray without offset, used to find location
-    def findNodePlacement(self, node, offset_shape_arr, hull_shape_arr, step = 1):
-        new_node = copy.deepcopy(node)
+    def findNodePlacement(self, node: SceneNode, offset_shape_arr: ShapeArray, hull_shape_arr: ShapeArray, step = 1):
         best_spot = self.bestSpot(
         best_spot = self.bestSpot(
             hull_shape_arr, start_prio = self._last_priority, step = step)
             hull_shape_arr, start_prio = self._last_priority, step = step)
         x, y = best_spot.x, best_spot.y
         x, y = best_spot.x, best_spot.y
@@ -95,21 +96,21 @@ class Arrange:
         self._last_priority = best_spot.priority
         self._last_priority = best_spot.priority
 
 
         # Ensure that the object is above the build platform
         # Ensure that the object is above the build platform
-        new_node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
-        if new_node.getBoundingBox():
-            center_y = new_node.getWorldPosition().y - new_node.getBoundingBox().bottom
+        node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
+        if node.getBoundingBox():
+            center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
         else:
         else:
             center_y = 0
             center_y = 0
 
 
         if x is not None:  # We could find a place
         if x is not None:  # We could find a place
-            new_node.setPosition(Vector(x, center_y, y))
+            node.setPosition(Vector(x, center_y, y))
             found_spot = True
             found_spot = True
             self.place(x, y, offset_shape_arr)  # place the object in arranger
             self.place(x, y, offset_shape_arr)  # place the object in arranger
         else:
         else:
             Logger.log("d", "Could not find spot!"),
             Logger.log("d", "Could not find spot!"),
             found_spot = False
             found_spot = False
-            new_node.setPosition(Vector(200, center_y, 100))
-        return new_node, found_spot
+            node.setPosition(Vector(200, center_y, 100))
+        return found_spot
 
 
     ##  Fill priority, center is best. Lower value is better
     ##  Fill priority, center is best. Lower value is better
     #   This is a strategy for the arranger.
     #   This is a strategy for the arranger.

+ 15 - 24
cura/Backups/Backup.py

@@ -17,12 +17,11 @@ from UM.Resources import Resources
 from cura.CuraApplication import CuraApplication
 from cura.CuraApplication import CuraApplication
 
 
 
 
+##  The back-up class holds all data about a back-up.
+#
+#   It is also responsible for reading and writing the zip file to the user data
+#   folder.
 class Backup:
 class Backup:
-    """
-    The backup class holds all data about a backup.
-    It is also responsible for reading and writing the zip file to the user data folder.
-    """
-
     # These files should be ignored when making a backup.
     # These files should be ignored when making a backup.
     IGNORED_FILES = [r"cura\.log", r"plugins\.json", r"cache", r"__pycache__", r"\.qmlc", r"\.pyc"]
     IGNORED_FILES = [r"cura\.log", r"plugins\.json", r"cache", r"__pycache__", r"\.qmlc", r"\.pyc"]
 
 
@@ -33,10 +32,8 @@ class Backup:
         self.zip_file = zip_file  # type: Optional[bytes]
         self.zip_file = zip_file  # type: Optional[bytes]
         self.meta_data = meta_data  # type: Optional[dict]
         self.meta_data = meta_data  # type: Optional[dict]
 
 
+    ##  Create a back-up from the current user config folder.
     def makeFromCurrent(self) -> None:
     def makeFromCurrent(self) -> None:
-        """
-        Create a backup from the current user config folder.
-        """
         cura_release = CuraApplication.getInstance().getVersion()
         cura_release = CuraApplication.getInstance().getVersion()
         version_data_dir = Resources.getDataStoragePath()
         version_data_dir = Resources.getDataStoragePath()
 
 
@@ -77,12 +74,10 @@ class Backup:
             "plugin_count": str(plugin_count)
             "plugin_count": str(plugin_count)
         }
         }
 
 
+    ##  Make a full archive from the given root path with the given name.
+    #   \param root_path The root directory to archive recursively.
+    #   \return The archive as bytes.
     def _makeArchive(self, buffer: "io.BytesIO", root_path: str) -> Optional[ZipFile]:
     def _makeArchive(self, buffer: "io.BytesIO", root_path: str) -> Optional[ZipFile]:
-        """
-        Make a full archive from the given root path with the given name.
-        :param root_path: The root directory to archive recursively.
-        :return: The archive as bytes.
-        """
         ignore_string = re.compile("|".join(self.IGNORED_FILES))
         ignore_string = re.compile("|".join(self.IGNORED_FILES))
         try:
         try:
             archive = ZipFile(buffer, "w", ZIP_DEFLATED)
             archive = ZipFile(buffer, "w", ZIP_DEFLATED)
@@ -101,15 +96,13 @@ class Backup:
                                    "Could not create archive from user data directory: {}".format(error)))
                                    "Could not create archive from user data directory: {}".format(error)))
             return None
             return None
 
 
+    ##  Show a UI message.
     def _showMessage(self, message: str) -> None:
     def _showMessage(self, message: str) -> None:
-        """Show a UI message"""
         Message(message, title=self.catalog.i18nc("@info:title", "Backup"), lifetime=30).show()
         Message(message, title=self.catalog.i18nc("@info:title", "Backup"), lifetime=30).show()
 
 
+    ##  Restore this back-up.
+    #   \return Whether we had success or not.
     def restore(self) -> bool:
     def restore(self) -> bool:
-        """
-        Restore this backups
-        :return: A boolean whether we had success or not.
-        """
         if not self.zip_file or not self.meta_data or not self.meta_data.get("cura_release", None):
         if not self.zip_file or not self.meta_data or not self.meta_data.get("cura_release", None):
             # We can restore without the minimum required information.
             # We can restore without the minimum required information.
             Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.")
             Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.")
@@ -142,14 +135,12 @@ class Backup:
 
 
         return extracted
         return extracted
 
 
+    ##  Extract the whole archive to the given target path.
+    #   \param archive The archive as ZipFile.
+    #   \param target_path The target path.
+    #   \return Whether we had success or not.
     @staticmethod
     @staticmethod
     def _extractArchive(archive: "ZipFile", target_path: str) -> bool:
     def _extractArchive(archive: "ZipFile", target_path: str) -> bool:
-        """
-        Extract the whole archive to the given target path.
-        :param archive: The archive as ZipFile.
-        :param target_path: The target path.
-        :return: A boolean whether we had success or not.
-        """
         Logger.log("d", "Removing current data in location: %s", target_path)
         Logger.log("d", "Removing current data in location: %s", target_path)
         Resources.factoryReset()
         Resources.factoryReset()
         Logger.log("d", "Extracting backup to location: %s", target_path)
         Logger.log("d", "Extracting backup to location: %s", target_path)

+ 14 - 15
cura/Backups/BackupsManager.py

@@ -7,19 +7,18 @@ from cura.Backups.Backup import Backup
 from cura.CuraApplication import CuraApplication
 from cura.CuraApplication import CuraApplication
 
 
 
 
+##  The BackupsManager is responsible for managing the creating and restoring of
+#   back-ups.
+#
+#   Back-ups themselves are represented in a different class.
 class BackupsManager:
 class BackupsManager:
-    """
-    The BackupsManager is responsible for managing the creating and restoring of backups.
-    Backups themselves are represented in a different class.
-    """
     def __init__(self):
     def __init__(self):
         self._application = CuraApplication.getInstance()
         self._application = CuraApplication.getInstance()
 
 
+    ##  Get a back-up of the current configuration.
+    #   \return A tuple containing a ZipFile (the actual back-up) and a dict
+    #   containing some metadata (like version).
     def createBackup(self) -> (Optional[bytes], Optional[dict]):
     def createBackup(self) -> (Optional[bytes], Optional[dict]):
-        """
-        Get a backup of the current configuration.
-        :return: A Tuple containing a ZipFile (the actual backup) and a dict containing some meta data (like version).
-        """
         self._disableAutoSave()
         self._disableAutoSave()
         backup = Backup()
         backup = Backup()
         backup.makeFromCurrent()
         backup.makeFromCurrent()
@@ -27,12 +26,11 @@ class BackupsManager:
         # We don't return a Backup here because we want plugins only to interact with our API and not full objects.
         # We don't return a Backup here because we want plugins only to interact with our API and not full objects.
         return backup.zip_file, backup.meta_data
         return backup.zip_file, backup.meta_data
 
 
+    ##  Restore a back-up from a given ZipFile.
+    #   \param zip_file A bytes object containing the actual back-up.
+    #   \param meta_data A dict containing some metadata that is needed to
+    #   restore the back-up correctly.
     def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
     def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
-        """
-        Restore a backup from a given ZipFile.
-        :param zip_file: A bytes object containing the actual backup.
-        :param meta_data: A dict containing some meta data that is needed to restore the backup correctly.
-        """
         if not meta_data.get("cura_release", None):
         if not meta_data.get("cura_release", None):
             # If there is no "cura_release" specified in the meta data, we don't execute a backup restore.
             # If there is no "cura_release" specified in the meta data, we don't execute a backup restore.
             Logger.log("w", "Tried to restore a backup without specifying a Cura version number.")
             Logger.log("w", "Tried to restore a backup without specifying a Cura version number.")
@@ -47,10 +45,11 @@ class BackupsManager:
             # We don't want to store the data at this point as that would override the just-restored backup.
             # We don't want to store the data at this point as that would override the just-restored backup.
             self._application.windowClosed(save_data=False)
             self._application.windowClosed(save_data=False)
 
 
+    ##  Here we try to disable the auto-save plug-in as it might interfere with
+    #   restoring a back-up.
     def _disableAutoSave(self):
     def _disableAutoSave(self):
-        """Here we try to disable the auto-save plugin as it might interfere with restoring a backup."""
         self._application.setSaveDataEnabled(False)
         self._application.setSaveDataEnabled(False)
 
 
+    ##  Re-enable auto-save after we're done.
     def _enableAutoSave(self):
     def _enableAutoSave(self):
-        """Re-enable auto-save after we're done."""
         self._application.setSaveDataEnabled(True)
         self._application.setSaveDataEnabled(True)

+ 4 - 5
cura/CuraApplication.py

@@ -225,6 +225,8 @@ class CuraApplication(QtApplication):
 
 
         from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
         from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
         self._container_registry_class = CuraContainerRegistry
         self._container_registry_class = CuraContainerRegistry
+        from cura.CuraPackageManager import CuraPackageManager
+        self._package_manager_class = CuraPackageManager
 
 
     # Adds command line options to the command line parser. This should be called after the application is created and
     # Adds command line options to the command line parser. This should be called after the application is created and
     # before the pre-start.
     # before the pre-start.
@@ -511,7 +513,6 @@ class CuraApplication(QtApplication):
         preferences.addPreference("cura/asked_dialog_on_project_save", False)
         preferences.addPreference("cura/asked_dialog_on_project_save", False)
         preferences.addPreference("cura/choice_on_profile_override", "always_ask")
         preferences.addPreference("cura/choice_on_profile_override", "always_ask")
         preferences.addPreference("cura/choice_on_open_project", "always_ask")
         preferences.addPreference("cura/choice_on_open_project", "always_ask")
-        preferences.addPreference("cura/not_arrange_objects_on_load", False)
         preferences.addPreference("cura/use_multi_build_plate", False)
         preferences.addPreference("cura/use_multi_build_plate", False)
 
 
         preferences.addPreference("cura/currency", "€")
         preferences.addPreference("cura/currency", "€")
@@ -1601,9 +1602,7 @@ class CuraApplication(QtApplication):
         self._currently_loading_files.remove(filename)
         self._currently_loading_files.remove(filename)
 
 
         self.fileLoaded.emit(filename)
         self.fileLoaded.emit(filename)
-        arrange_objects_on_load = (
-            not self.getPreferences().getValue("cura/use_multi_build_plate") or
-            not self.getPreferences().getValue("cura/not_arrange_objects_on_load"))
+        arrange_objects_on_load = not self.getPreferences().getValue("cura/use_multi_build_plate")
         target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
         target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
 
 
         root = self.getController().getScene().getRoot()
         root = self.getController().getScene().getRoot()
@@ -1676,7 +1675,7 @@ class CuraApplication(QtApplication):
                             return
                             return
 
 
                         # Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
                         # Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
-                        node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
+                        arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
 
 
             # This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
             # This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
             # of BuildPlateDecorator produces one that's associated with build plate -1. So, here we need to check if
             # of BuildPlateDecorator produces one that's associated with build plate -1. So, here we need to check if

+ 5 - 2
cura/CuraPackageManager.py

@@ -7,8 +7,11 @@ from UM.Resources import Resources #To find storage paths for some resource type
 
 
 
 
 class CuraPackageManager(PackageManager):
 class CuraPackageManager(PackageManager):
-    def __init__(self, parent = None):
-        super().__init__(parent)
+    def __init__(self, application, parent = None):
+        super().__init__(application, parent)
 
 
+    def initialize(self):
         self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
         self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
         self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
         self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
+
+        super().initialize()

+ 4 - 3
cura/MultiplyObjectsJob.py

@@ -64,10 +64,11 @@ class MultiplyObjectsJob(Job):
             arranger.resetLastPriority()
             arranger.resetLastPriority()
             for i in range(self._count):
             for i in range(self._count):
                 # We do place the nodes one by one, as we want to yield in between.
                 # We do place the nodes one by one, as we want to yield in between.
+                new_node = copy.deepcopy(node)
+                solution_found = False
                 if not node_too_big:
                 if not node_too_big:
-                    new_node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
-                else:
-                    new_node = copy.deepcopy(node)
+                    solution_found = arranger.findNodePlacement(new_node, offset_shape_arr, hull_shape_arr)
+
                 if node_too_big or not solution_found:
                 if node_too_big or not solution_found:
                     found_solution_for_all = False
                     found_solution_for_all = False
                     new_location = new_node.getPosition()
                     new_location = new_node.getPosition()

+ 5 - 0
cura/PlatformPhysics.py

@@ -8,6 +8,7 @@ from UM.Scene.SceneNode import SceneNode
 from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
 from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
 from UM.Math.Vector import Vector
 from UM.Math.Vector import Vector
 from UM.Scene.Selection import Selection
 from UM.Scene.Selection import Selection
+from UM.Scene.SceneNodeSettings import SceneNodeSettings
 
 
 from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
 from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
 
 
@@ -80,6 +81,10 @@ class PlatformPhysics:
 
 
             # only push away objects if this node is a printing mesh
             # only push away objects if this node is a printing mesh
             if not node.callDecoration("isNonPrintingMesh") and Application.getInstance().getPreferences().getValue("physics/automatic_push_free"):
             if not node.callDecoration("isNonPrintingMesh") and Application.getInstance().getPreferences().getValue("physics/automatic_push_free"):
+                # Do not move locked nodes
+                if node.getSetting(SceneNodeSettings.LockPosition):
+                    continue
+
                 # Check for collisions between convex hulls
                 # Check for collisions between convex hulls
                 for other_node in BreadthFirstIterator(root):
                 for other_node in BreadthFirstIterator(root):
                     # Ignore root, ourselves and anything that is not a normal SceneNode.
                     # Ignore root, ourselves and anything that is not a normal SceneNode.

+ 10 - 1
cura/Settings/ContainerManager.py

@@ -42,6 +42,7 @@ class ContainerManager(QObject):
         self._container_registry = self._application.getContainerRegistry()
         self._container_registry = self._application.getContainerRegistry()
         self._machine_manager = self._application.getMachineManager()
         self._machine_manager = self._application.getMachineManager()
         self._material_manager = self._application.getMaterialManager()
         self._material_manager = self._application.getMaterialManager()
+        self._quality_manager = self._application.getQualityManager()
         self._container_name_filters = {}
         self._container_name_filters = {}
 
 
     @pyqtSlot(str, str, result=str)
     @pyqtSlot(str, str, result=str)
@@ -312,11 +313,19 @@ class ContainerManager(QObject):
 
 
         self._machine_manager.blurSettings.emit()
         self._machine_manager.blurSettings.emit()
 
 
-        global_stack = self._machine_manager.activeMachine
+        current_quality_changes_name = global_stack.qualityChanges.getName()
+        current_quality_type = global_stack.quality.getMetaDataEntry("quality_type")
         extruder_stacks = list(global_stack.extruders.values())
         extruder_stacks = list(global_stack.extruders.values())
         for stack in [global_stack] + extruder_stacks:
         for stack in [global_stack] + extruder_stacks:
             # Find the quality_changes container for this stack and merge the contents of the top container into it.
             # Find the quality_changes container for this stack and merge the contents of the top container into it.
             quality_changes = stack.qualityChanges
             quality_changes = stack.qualityChanges
+
+            if quality_changes.getId() == "empty_quality_changes":
+                quality_changes = self._quality_manager._createQualityChanges(current_quality_type, current_quality_changes_name,
+                                                                              global_stack, stack)
+                self._container_registry.addContainer(quality_changes)
+                stack.qualityChanges = quality_changes
+
             if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
             if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
                 Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
                 Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
                 continue
                 continue

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