Merge branch 'master' into CURA-5334_extruder_def_for_all

This commit is contained in:
Jack Ha 2018-06-11 16:56:42 +02:00
commit c97f276583
21 changed files with 131 additions and 115 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

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

View file

@ -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): def __init__(self, application, parent = None):
super().__init__(parent) 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()

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

@ -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)
@ -313,10 +314,18 @@ class ContainerManager(QObject):
self._machine_manager.blurSettings.emit() self._machine_manager.blurSettings.emit()
global_stack = self._machine_manager.activeMachine 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)
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

View file

@ -501,15 +501,6 @@ class ExtruderManager(QObject):
def getInstanceExtruderValues(self, key): def getInstanceExtruderValues(self, key):
return ExtruderManager.getExtruderValues(key) return ExtruderManager.getExtruderValues(key)
## Updates the material container to a material that matches the material diameter set for the printer
def updateMaterialForDiameter(self, extruder_position: int, global_stack = None):
if not global_stack:
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return
Application.getInstance().getMachineManager()._updateMaterialWithVariant(extruder_position)
## Get the value for a setting from a specific extruder. ## Get the value for a setting from a specific extruder.
# #
# This is exposed to SettingFunction to use in value functions. # This is exposed to SettingFunction to use in value functions.

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
@ -1200,7 +1205,7 @@ class MachineManager(QObject):
current_quality_type, quality_type) current_quality_type, quality_type)
self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True) self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True)
def _updateMaterialWithVariant(self, position: Optional[str]): def updateMaterialWithVariant(self, position: Optional[str]):
if self._global_container_stack is None: if self._global_container_stack is None:
return return
if position is None: if position is None:
@ -1286,7 +1291,7 @@ class MachineManager(QObject):
self._setMaterial(position, material_container_node) self._setMaterial(position, material_container_node)
else: else:
self._global_container_stack.extruders[position].material = self._empty_material_container self._global_container_stack.extruders[position].material = self._empty_material_container
self._updateMaterialWithVariant(position) self.updateMaterialWithVariant(position)
if configuration.buildplateConfiguration is not None: if configuration.buildplateConfiguration is not None:
global_variant_container_node = self._variant_manager.getBuildplateVariantNode(self._global_container_stack.definition.getId(), configuration.buildplateConfiguration) global_variant_container_node = self._variant_manager.getBuildplateVariantNode(self._global_container_stack.definition.getId(), configuration.buildplateConfiguration)
@ -1332,7 +1337,7 @@ class MachineManager(QObject):
self.blurSettings.emit() self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._setGlobalVariant(container_node) self._setGlobalVariant(container_node)
self._updateMaterialWithVariant(None) # Update all materials self.updateMaterialWithVariant(None) # Update all materials
self._updateQualityWithMaterial() self._updateQualityWithMaterial()
@pyqtSlot(str, str) @pyqtSlot(str, str)
@ -1369,7 +1374,7 @@ class MachineManager(QObject):
self.blurSettings.emit() self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._setVariantNode(position, container_node) self._setVariantNode(position, container_node)
self._updateMaterialWithVariant(position) self.updateMaterialWithVariant(position)
self._updateQualityWithMaterial() self._updateQualityWithMaterial()
# See if we need to show the Discard or Keep changes screen # See if we need to show the Discard or Keep changes screen
@ -1433,5 +1438,5 @@ class MachineManager(QObject):
if self._global_container_stack is None: if self._global_container_stack is None:
return return
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._updateMaterialWithVariant(None) self.updateMaterialWithVariant(None)
self._updateQualityWithMaterial() self._updateQualityWithMaterial()

View file

@ -9,6 +9,7 @@ from UM.Signal import Signal, signalemitter
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Logger import Logger from UM.Logger import Logger
from UM.Util import parseBool
from UM.Application import Application from UM.Application import Application
@ -39,7 +40,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
user_container = InstanceContainer(container_id = self._generateUniqueName()) user_container = InstanceContainer(container_id = self._generateUniqueName())
user_container.addMetaDataEntry("type", "user") user_container.addMetaDataEntry("type", "user")
self._stack.userChanges = user_container self._stack.userChanges = user_container
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0)
self._is_non_printing_mesh = False self._is_non_printing_mesh = False
self._is_non_thumbnail_visible_mesh = False self._is_non_thumbnail_visible_mesh = False
@ -48,13 +49,25 @@ class SettingOverrideDecorator(SceneNodeDecorator):
Application.getInstance().getContainerRegistry().addContainer(self._stack) Application.getInstance().getContainerRegistry().addContainer(self._stack)
Application.getInstance().globalContainerStackChanged.connect(self._updateNextStack) Application.getInstance().globalContainerStackChanged.connect(self._onNumberOfExtrudersEnabledChanged)
Application.getInstance().getMachineManager().numberExtrudersEnabledChanged.connect(self._onNumberOfExtrudersEnabledChanged)
self.activeExtruderChanged.connect(self._updateNextStack) self.activeExtruderChanged.connect(self._updateNextStack)
self._updateNextStack() self._updateNextStack()
def _generateUniqueName(self): def _generateUniqueName(self):
return "SettingOverrideInstanceContainer-%s" % uuid.uuid1() return "SettingOverrideInstanceContainer-%s" % uuid.uuid1()
def _onNumberOfExtrudersEnabledChanged(self, *args, **kwargs):
if not parseBool(self._extruder_stack.getMetaDataEntry("enabled", "True")):
# switch to the first extruder that's available
global_stack = Application.getInstance().getMachineManager().activeMachine
for _, extruder in sorted(list(global_stack.extruders.items())):
if parseBool(extruder.getMetaDataEntry("enabled", "True")):
self._extruder_stack = extruder
self._updateNextStack()
break
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
## Create a fresh decorator object ## Create a fresh decorator object
deep_copy = SettingOverrideDecorator() deep_copy = SettingOverrideDecorator()
@ -63,13 +76,13 @@ class SettingOverrideDecorator(SceneNodeDecorator):
instance_container = copy.deepcopy(self._stack.getContainer(0), memo) instance_container = copy.deepcopy(self._stack.getContainer(0), memo)
# A unique name must be added, or replaceContainer will not replace it # A unique name must be added, or replaceContainer will not replace it
instance_container.setMetaDataEntry("id", self._generateUniqueName) instance_container.setMetaDataEntry("id", self._generateUniqueName())
## Set the copied instance as the first (and only) instance container of the stack. ## Set the copied instance as the first (and only) instance container of the stack.
deep_copy._stack.replaceContainer(0, instance_container) deep_copy._stack.replaceContainer(0, instance_container)
# Properly set the right extruder on the copy # Properly set the right extruder on the copy
deep_copy.setActiveExtruder(self._extruder_stack) deep_copy.setActiveExtruder(self._extruder_stack.getId())
# use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh" # use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh"
# has not been updated yet. # has not been updated yet.
@ -82,7 +95,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# #
# \return An extruder's container stack. # \return An extruder's container stack.
def getActiveExtruder(self): def getActiveExtruder(self):
return self._extruder_stack return self._extruder_stack.getId()
## Gets the signal that emits if the active extruder changed. ## Gets the signal that emits if the active extruder changed.
# #
@ -124,20 +137,16 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# kept up to date. # kept up to date.
def _updateNextStack(self): def _updateNextStack(self):
if self._extruder_stack: if self._extruder_stack:
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = self._extruder_stack) if self._stack.getNextStack():
if extruder_stack: old_extruder_stack_id = self._stack.getNextStack().getId()
if self._stack.getNextStack():
old_extruder_stack_id = self._stack.getNextStack().getId()
else:
old_extruder_stack_id = ""
self._stack.setNextStack(extruder_stack[0])
# Trigger slice/need slicing if the extruder changed.
if self._stack.getNextStack().getId() != old_extruder_stack_id:
Application.getInstance().getBackend().needsSlicing()
Application.getInstance().getBackend().tickle()
else: else:
Logger.log("e", "Extruder stack %s below per-object settings does not exist.", self._extruder_stack) old_extruder_stack_id = ""
self._stack.setNextStack(self._extruder_stack)
# Trigger slice/need slicing if the extruder changed.
if self._stack.getNextStack().getId() != old_extruder_stack_id:
Application.getInstance().getBackend().needsSlicing()
Application.getInstance().getBackend().tickle()
else: else:
self._stack.setNextStack(Application.getInstance().getGlobalContainerStack()) self._stack.setNextStack(Application.getInstance().getGlobalContainerStack())
@ -145,7 +154,14 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# #
# \param extruder_stack_id The new extruder stack to print with. # \param extruder_stack_id The new extruder stack to print with.
def setActiveExtruder(self, extruder_stack_id): def setActiveExtruder(self, extruder_stack_id):
self._extruder_stack = extruder_stack_id if self._extruder_stack.getId() == extruder_stack_id:
return
global_stack = Application.getInstance().getMachineManager().activeMachine
for extruder in global_stack.extruders.values():
if extruder.getId() == extruder_stack_id:
self._extruder_stack = extruder
break
self._updateNextStack() self._updateNextStack()
ExtruderManager.getInstance().resetSelectedObjectExtruders() ExtruderManager.getInstance().resetSelectedObjectExtruders()
self.activeExtruderChanged.emit() self.activeExtruderChanged.emit()

View file

@ -55,7 +55,7 @@ class ChangeLog(Extension, QObject,):
def loadChangeLogs(self): def loadChangeLogs(self):
self._change_logs = collections.OrderedDict() self._change_logs = collections.OrderedDict()
with open(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.txt"), "r",-1, "utf-8") as f: with open(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.txt"), "r", encoding = "utf-8") as f:
open_version = None open_version = None
open_header = "" # Initialise to an empty header in case there is no "*" in the first line of the changelog open_header = "" # Initialise to an empty header in case there is no "*" in the first line of the changelog
for line in f: for line in f:

View file

@ -57,7 +57,7 @@ class GCodeProfileReader(ProfileReader):
# TODO: Consider moving settings to the start? # TODO: Consider moving settings to the start?
serialized = "" # Will be filled with the serialized profile. serialized = "" # Will be filled with the serialized profile.
try: try:
with open(file_name, "r") as f: with open(file_name, "r", encoding = "utf-8") as f:
for line in f: for line in f:
if line.startswith(prefix): if line.startswith(prefix):
# Remove the prefix and the newline from the line and add it to the rest. # Remove the prefix and the newline from the line and add it to the rest.

View file

@ -100,7 +100,7 @@ class LegacyProfileReader(ProfileReader):
return None return None
try: try:
with open(os.path.join(PluginRegistry.getInstance().getPluginPath("LegacyProfileReader"), "DictionaryOfDoom.json"), "r", -1, "utf-8") as f: with open(os.path.join(PluginRegistry.getInstance().getPluginPath("LegacyProfileReader"), "DictionaryOfDoom.json"), "r", encoding = "utf-8") as f:
dict_of_doom = json.load(f) # Parse the Dictionary of Doom. dict_of_doom = json.load(f) # Parse the Dictionary of Doom.
except IOError as e: except IOError as e:
Logger.log("e", "Could not open DictionaryOfDoom.json for reading: %s", str(e)) Logger.log("e", "Could not open DictionaryOfDoom.json for reading: %s", str(e))

View file

@ -158,4 +158,4 @@ class MachineSettingsAction(MachineAction):
@pyqtSlot(int) @pyqtSlot(int)
def updateMaterialForDiameter(self, extruder_position: int): def updateMaterialForDiameter(self, extruder_position: int):
# Updates the material container to a material that matches the material diameter set for the printer # Updates the material container to a material that matches the material diameter set for the printer
self._application.getExtruderManager().updateMaterialForDiameter(extruder_position) self._application.getMachineManager().updateMaterialWithVariant(extruder_position)

View file

@ -13,7 +13,7 @@ def readHex(filename):
""" """
data = [] data = []
extra_addr = 0 extra_addr = 0
f = io.open(filename, "r") f = io.open(filename, "r", encoding = "utf-8")
for line in f: for line in f:
line = line.strip() line = line.strip()
if len(line) < 1: if len(line) < 1:

View file

@ -94,7 +94,7 @@ class VersionUpgrade22to24(VersionUpgrade):
if variant_path.endswith("_variant.inst.cfg"): if variant_path.endswith("_variant.inst.cfg"):
variant_path = variant_path[:-len("_variant.inst.cfg")] + "_settings.inst.cfg" variant_path = variant_path[:-len("_variant.inst.cfg")] + "_settings.inst.cfg"
with open(os.path.join(machine_instances_dir, os.path.basename(variant_path)), "w") as fp: with open(os.path.join(machine_instances_dir, os.path.basename(variant_path)), "w", encoding = "utf-8") as fp:
variant_config.write(fp) variant_config.write(fp)
return config_name return config_name
@ -105,9 +105,9 @@ class VersionUpgrade22to24(VersionUpgrade):
result = [] result = []
for entry in os.scandir(variants_dir): for entry in os.scandir(variants_dir):
if entry.name.endswith('.inst.cfg') and entry.is_file(): if entry.name.endswith(".inst.cfg") and entry.is_file():
config = configparser.ConfigParser(interpolation = None) config = configparser.ConfigParser(interpolation = None)
with open(entry.path, "r") as fhandle: with open(entry.path, "r", encoding = "utf-8") as fhandle:
config.read_file(fhandle) config.read_file(fhandle)
if config.has_section("general") and config.has_option("general", "name"): if config.has_section("general") and config.has_option("general", "name"):
result.append( { "path": entry.path, "name": config.get("general", "name") } ) result.append( { "path": entry.path, "name": config.get("general", "name") } )

View file

@ -249,11 +249,11 @@ class VersionUpgrade25to26(VersionUpgrade):
definition_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.DefinitionChangesContainer) definition_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.DefinitionChangesContainer)
user_settings_dir = Resources.getPath(CuraApplication.ResourceTypes.UserInstanceContainer) user_settings_dir = Resources.getPath(CuraApplication.ResourceTypes.UserInstanceContainer)
with open(os.path.join(definition_changes_dir, definition_changes_filename), "w") as f: with open(os.path.join(definition_changes_dir, definition_changes_filename), "w", encoding = "utf-8") as f:
f.write(definition_changes_output.getvalue()) f.write(definition_changes_output.getvalue())
with open(os.path.join(user_settings_dir, user_settings_filename), "w") as f: with open(os.path.join(user_settings_dir, user_settings_filename), "w", encoding = "utf-8") as f:
f.write(user_settings_output.getvalue()) f.write(user_settings_output.getvalue())
with open(os.path.join(extruder_stack_dir, extruder_filename), "w") as f: with open(os.path.join(extruder_stack_dir, extruder_filename), "w", encoding = "utf-8") as f:
f.write(extruder_output.getvalue()) f.write(extruder_output.getvalue())
## Creates a definition changes container which doesn't contain anything for the Custom FDM Printers. ## Creates a definition changes container which doesn't contain anything for the Custom FDM Printers.

View file

@ -1018,7 +1018,7 @@ class XmlMaterialProfile(InstanceContainer):
@classmethod @classmethod
def getProductIdMap(cls) -> Dict[str, List[str]]: def getProductIdMap(cls) -> Dict[str, List[str]]:
product_to_id_file = os.path.join(os.path.dirname(sys.modules[cls.__module__].__file__), "product_to_id.json") product_to_id_file = os.path.join(os.path.dirname(sys.modules[cls.__module__].__file__), "product_to_id.json")
with open(product_to_id_file) as f: with open(product_to_id_file, encoding = "utf-8") as f:
product_to_id_map = json.load(f) product_to_id_map = json.load(f)
product_to_id_map = {key: [value] for key, value in product_to_id_map.items()} product_to_id_map = {key: [value] for key, value in product_to_id_map.items()}
return product_to_id_map return product_to_id_map

View file

@ -170,7 +170,7 @@ UM.PreferencesPage
append({ text: "日本語", code: "ja_JP" }) append({ text: "日本語", code: "ja_JP" })
append({ text: "한국어", code: "ko_KR" }) append({ text: "한국어", code: "ko_KR" })
append({ text: "Nederlands", code: "nl_NL" }) append({ text: "Nederlands", code: "nl_NL" })
append({ text: "Polski", code: "pl_PL" }) //Polish is disabled for being incomplete: append({ text: "Polski", code: "pl_PL" })
append({ text: "Português do Brasil", code: "pt_BR" }) append({ text: "Português do Brasil", code: "pt_BR" })
append({ text: "Português", code: "pt_PT" }) append({ text: "Português", code: "pt_PT" })
append({ text: "Русский", code: "ru_RU" }) append({ text: "Русский", code: "ru_RU" })

View file

@ -19,7 +19,7 @@ import pytest
def test_ultimaker3extended_variants(um3_file, um3e_file): def test_ultimaker3extended_variants(um3_file, um3e_file):
directory = os.path.join(os.path.dirname(__file__), "..", "resources", "variants") #TODO: Hardcoded path relative to this test file. directory = os.path.join(os.path.dirname(__file__), "..", "resources", "variants") #TODO: Hardcoded path relative to this test file.
um3 = configparser.ConfigParser() um3 = configparser.ConfigParser()
um3.read_file(open(os.path.join(directory, um3_file))) um3.read_file(open(os.path.join(directory, um3_file), encoding = "utf-8"))
um3e = configparser.ConfigParser() um3e = configparser.ConfigParser()
um3e.read_file(open(os.path.join(directory, um3e_file))) um3e.read_file(open(os.path.join(directory, um3e_file), encoding = "utf-8"))
assert um3["values"] == um3e["values"] assert um3["values"] == um3e["values"]