This commit is contained in:
Andreea Scorojitu 2018-06-11 15:48:08 +02:00
commit 26fb02e499
6 changed files with 60 additions and 65 deletions

View file

@ -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)

View file

@ -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

View file

@ -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) -> (bool, Optional[str]): def makeFromCurrent(self) -> (bool, Optional[str]):
"""
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()
@ -75,12 +72,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)
@ -99,15 +94,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.")
@ -140,14 +133,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)

View file

@ -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)

View file

@ -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.

View file

@ -306,6 +306,11 @@ class MachineManager(QObject):
for position, extruder in global_stack.extruders.items(): for position, extruder in global_stack.extruders.items():
material_dict[position] = extruder.material.getMetaDataEntry("base_file") material_dict[position] = extruder.material.getMetaDataEntry("base_file")
self._current_root_material_id = material_dict self._current_root_material_id = material_dict
# Update materials to make sure that the diameters match with the machine's
for position in global_stack.extruders:
self._updateMaterialWithVariant(position)
global_quality = global_stack.quality global_quality = global_stack.quality
quality_type = global_quality.getMetaDataEntry("quality_type") quality_type = global_quality.getMetaDataEntry("quality_type")
global_quality_changes = global_stack.qualityChanges global_quality_changes = global_stack.qualityChanges