Просмотр исходного кода

Merge branch 'master' into mypy_fixes

Conflicts:
cura/Backups/Backup.py
cura/Settings/ExtruderManager.py
cura/Settings/MachineManager.py
Diego Prado Gesto 6 лет назад
Родитель
Сommit
554a3fd908

+ 15 - 19
cura/API/Backups.py

@@ -3,30 +3,26 @@
 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:
-    """
-    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.
 
+    ##  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):
-        """
-        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()
 
+    ##  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:
-        """
-        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)

+ 6 - 7
cura/API/__init__.py

@@ -3,14 +3,13 @@
 from UM.PluginRegistry import PluginRegistry
 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:
-    """
-    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.
     VERSION = PluginRegistry.APIVersion

+ 9 - 8
cura/Arranging/Arrange.py

@@ -1,10 +1,12 @@
 # Copyright (c) 2018 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
+from typing import List
 
 from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
 from UM.Logger import Logger
 from UM.Math.Polygon import Polygon
 from UM.Math.Vector import Vector
+from UM.Scene.SceneNode import SceneNode
 from cura.Arranging.ShapeArray import ShapeArray
 from cura.Scene import ZOffsetDecorator
 
@@ -85,8 +87,7 @@ class Arrange:
     #   \param node
     #   \param offset_shape_arr ShapeArray with offset, for placing the shape
     #   \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(
             hull_shape_arr, start_prio = self._last_priority, step = step)
         x, y = best_spot.x, best_spot.y
@@ -95,21 +96,21 @@ class Arrange:
         self._last_priority = best_spot.priority
 
         # 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:
             center_y = 0
 
         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
             self.place(x, y, offset_shape_arr)  # place the object in arranger
         else:
             Logger.log("d", "Could not find spot!"),
             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
     #   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
 
 
+##  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:
-    """
-    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.
     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.meta_data = meta_data  # type: Optional[dict]
 
+    ##  Create a back-up from the current user config folder.
     def makeFromCurrent(self) -> None:
-        """
-        Create a backup from the current user config folder.
-        """
         cura_release = CuraApplication.getInstance().getVersion()
         version_data_dir = Resources.getDataStoragePath()
 
@@ -77,12 +74,10 @@ class Backup:
             "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]:
-        """
-        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))
         try:
             archive = ZipFile(buffer, "w", ZIP_DEFLATED)
@@ -101,15 +96,13 @@ class Backup:
                                    "Could not create archive from user data directory: {}".format(error)))
             return None
 
+    ##  Show a UI message.
     def _showMessage(self, message: str) -> None:
-        """Show a UI message"""
         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:
-        """
-        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):
             # We can restore without the minimum required information.
             Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.")
@@ -142,14 +135,12 @@ class Backup:
 
         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
     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)
         Resources.factoryReset()
         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
 
 
+##  The BackupsManager is responsible for managing the creating and restoring of
+#   back-ups.
+#
+#   Back-ups themselves are represented in a different class.
 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):
         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]):
-        """
-        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()
         backup = Backup()
         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.
         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:
-        """
-        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 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.")
@@ -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.
             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):
-        """Here we try to disable the auto-save plugin as it might interfere with restoring a backup."""
         self._application.setSaveDataEnabled(False)
 
+    ##  Re-enable auto-save after we're done.
     def _enableAutoSave(self):
-        """Re-enable auto-save after we're done."""
         self._application.setSaveDataEnabled(True)

+ 4 - 5
cura/CuraApplication.py

@@ -225,6 +225,8 @@ class CuraApplication(QtApplication):
 
         from cura.Settings.CuraContainerRegistry import 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
     # before the pre-start.
@@ -511,7 +513,6 @@ class CuraApplication(QtApplication):
         preferences.addPreference("cura/asked_dialog_on_project_save", False)
         preferences.addPreference("cura/choice_on_profile_override", "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/currency", "€")
@@ -1601,9 +1602,7 @@ class CuraApplication(QtApplication):
         self._currently_loading_files.remove(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
 
         root = self.getController().getScene().getRoot()
@@ -1676,7 +1675,7 @@ class CuraApplication(QtApplication):
                             return
 
                         # 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
             # 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):
-    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["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
+
+        super().initialize()

+ 4 - 3
cura/MultiplyObjectsJob.py

@@ -64,10 +64,11 @@ class MultiplyObjectsJob(Job):
             arranger.resetLastPriority()
             for i in range(self._count):
                 # 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:
-                    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:
                     found_solution_for_all = False
                     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.Math.Vector import Vector
 from UM.Scene.Selection import Selection
+from UM.Scene.SceneNodeSettings import SceneNodeSettings
 
 from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
 
@@ -80,6 +81,10 @@ class PlatformPhysics:
 
             # 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"):
+                # Do not move locked nodes
+                if node.getSetting(SceneNodeSettings.LockPosition):
+                    continue
+
                 # Check for collisions between convex hulls
                 for other_node in BreadthFirstIterator(root):
                     # 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._machine_manager = self._application.getMachineManager()
         self._material_manager = self._application.getMaterialManager()
+        self._quality_manager = self._application.getQualityManager()
         self._container_name_filters = {}
 
     @pyqtSlot(str, str, result=str)
@@ -312,11 +313,19 @@ class ContainerManager(QObject):
 
         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())
         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.
             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()):
                 Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
                 continue

Некоторые файлы не были показаны из-за большого количества измененных файлов