diff --git a/cura/API/Backups.py b/cura/API/Backups.py index ba416bd870..a2423bd798 100644 --- a/cura/API/Backups.py +++ b/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) diff --git a/cura/API/__init__.py b/cura/API/__init__.py index 7dd5d8f79e..13f6722336 100644 --- a/cura/API/__init__.py +++ b/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 diff --git a/cura/Backups/Backup.py b/cura/Backups/Backup.py index c4fe720b2b..70807a96d7 100644 --- a/cura/Backups/Backup.py +++ b/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) -> (bool, Optional[str]): - """ - Create a backup from the current user config folder. - """ cura_release = CuraApplication.getInstance().getVersion() version_data_dir = Resources.getDataStoragePath() @@ -75,12 +72,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) @@ -99,15 +94,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.") @@ -140,14 +133,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) diff --git a/cura/Backups/BackupsManager.py b/cura/Backups/BackupsManager.py index fa75ddb587..850b0a2edc 100644 --- a/cura/Backups/BackupsManager.py +++ b/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) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index e34d0a07ab..af65d63e70 100755 --- a/cura/CuraApplication.py +++ b/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. diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 49f095941a..f65633ed66 100644 --- a/cura/CuraPackageManager.py +++ b/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() diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index 6b539a4574..8ddcdbfb2f 100755 --- a/cura/PlatformPhysics.py +++ b/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. diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index ea2821ce25..b1800facb5 100644 --- a/cura/Settings/ContainerManager.py +++ b/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) @@ -313,10 +314,18 @@ 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) + 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 diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 16b2bd6bb2..e14ed69e01 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -501,15 +501,6 @@ class ExtruderManager(QObject): def getInstanceExtruderValues(self, 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. # # This is exposed to SettingFunction to use in value functions. diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 39bda6a4a4..1039085cf3 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -306,6 +306,11 @@ class MachineManager(QObject): for position, extruder in global_stack.extruders.items(): material_dict[position] = extruder.material.getMetaDataEntry("base_file") 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 quality_type = global_quality.getMetaDataEntry("quality_type") global_quality_changes = global_stack.qualityChanges @@ -1200,7 +1205,7 @@ class MachineManager(QObject): current_quality_type, quality_type) 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: return if position is None: @@ -1286,7 +1291,7 @@ class MachineManager(QObject): self._setMaterial(position, material_container_node) else: self._global_container_stack.extruders[position].material = self._empty_material_container - self._updateMaterialWithVariant(position) + self.updateMaterialWithVariant(position) if configuration.buildplateConfiguration is not None: 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() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setGlobalVariant(container_node) - self._updateMaterialWithVariant(None) # Update all materials + self.updateMaterialWithVariant(None) # Update all materials self._updateQualityWithMaterial() @pyqtSlot(str, str) @@ -1369,7 +1374,7 @@ class MachineManager(QObject): self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setVariantNode(position, container_node) - self._updateMaterialWithVariant(position) + self.updateMaterialWithVariant(position) self._updateQualityWithMaterial() # 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: return with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): - self._updateMaterialWithVariant(None) + self.updateMaterialWithVariant(None) self._updateQualityWithMaterial() diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index a662027d8f..f14977866e 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -9,6 +9,7 @@ from UM.Signal import Signal, signalemitter from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Logger import Logger +from UM.Util import parseBool from UM.Application import Application @@ -39,7 +40,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): user_container = InstanceContainer(container_id = self._generateUniqueName()) user_container.addMetaDataEntry("type", "user") 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_thumbnail_visible_mesh = False @@ -48,13 +49,25 @@ class SettingOverrideDecorator(SceneNodeDecorator): 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._updateNextStack() def _generateUniqueName(self): 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): ## Create a fresh decorator object deep_copy = SettingOverrideDecorator() @@ -63,13 +76,13 @@ class SettingOverrideDecorator(SceneNodeDecorator): instance_container = copy.deepcopy(self._stack.getContainer(0), memo) # 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. deep_copy._stack.replaceContainer(0, instance_container) # 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" # has not been updated yet. @@ -82,7 +95,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): # # \return An extruder's container stack. def getActiveExtruder(self): - return self._extruder_stack + return self._extruder_stack.getId() ## Gets the signal that emits if the active extruder changed. # @@ -124,20 +137,16 @@ class SettingOverrideDecorator(SceneNodeDecorator): # kept up to date. def _updateNextStack(self): if self._extruder_stack: - extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = self._extruder_stack) - if extruder_stack: - 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() + if self._stack.getNextStack(): + old_extruder_stack_id = self._stack.getNextStack().getId() 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: self._stack.setNextStack(Application.getInstance().getGlobalContainerStack()) @@ -145,7 +154,14 @@ class SettingOverrideDecorator(SceneNodeDecorator): # # \param extruder_stack_id The new extruder stack to print with. 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() ExtruderManager.getInstance().resetSelectedObjectExtruders() self.activeExtruderChanged.emit() diff --git a/plugins/ChangeLogPlugin/ChangeLog.py b/plugins/ChangeLogPlugin/ChangeLog.py index e93d5c4395..723c83a021 100644 --- a/plugins/ChangeLogPlugin/ChangeLog.py +++ b/plugins/ChangeLogPlugin/ChangeLog.py @@ -55,7 +55,7 @@ class ChangeLog(Extension, QObject,): def loadChangeLogs(self): 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_header = "" # Initialise to an empty header in case there is no "*" in the first line of the changelog for line in f: diff --git a/plugins/GCodeProfileReader/GCodeProfileReader.py b/plugins/GCodeProfileReader/GCodeProfileReader.py index 4b50a600ba..9fbae7b473 100644 --- a/plugins/GCodeProfileReader/GCodeProfileReader.py +++ b/plugins/GCodeProfileReader/GCodeProfileReader.py @@ -57,7 +57,7 @@ class GCodeProfileReader(ProfileReader): # TODO: Consider moving settings to the start? serialized = "" # Will be filled with the serialized profile. try: - with open(file_name, "r") as f: + with open(file_name, "r", encoding = "utf-8") as f: for line in f: if line.startswith(prefix): # Remove the prefix and the newline from the line and add it to the rest. diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index 3c2b9bfa76..40a843e6c4 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -100,7 +100,7 @@ class LegacyProfileReader(ProfileReader): return None 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. except IOError as e: Logger.log("e", "Could not open DictionaryOfDoom.json for reading: %s", str(e)) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index 7d5b317475..0f8e0fa1ca 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -158,4 +158,4 @@ class MachineSettingsAction(MachineAction): @pyqtSlot(int) def updateMaterialForDiameter(self, extruder_position: int): # 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) diff --git a/plugins/USBPrinting/avr_isp/intelHex.py b/plugins/USBPrinting/avr_isp/intelHex.py index a51c798d8e..671f1788f7 100644 --- a/plugins/USBPrinting/avr_isp/intelHex.py +++ b/plugins/USBPrinting/avr_isp/intelHex.py @@ -13,7 +13,7 @@ def readHex(filename): """ data = [] extra_addr = 0 - f = io.open(filename, "r") + f = io.open(filename, "r", encoding = "utf-8") for line in f: line = line.strip() if len(line) < 1: diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py index 7505911049..730a62e591 100644 --- a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py +++ b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py @@ -94,7 +94,7 @@ class VersionUpgrade22to24(VersionUpgrade): if variant_path.endswith("_variant.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) return config_name @@ -105,9 +105,9 @@ class VersionUpgrade22to24(VersionUpgrade): result = [] 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) - with open(entry.path, "r") as fhandle: + with open(entry.path, "r", encoding = "utf-8") as fhandle: config.read_file(fhandle) if config.has_section("general") and config.has_option("general", "name"): result.append( { "path": entry.path, "name": config.get("general", "name") } ) diff --git a/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py b/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py index e1c14be2e1..54b561c847 100644 --- a/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py +++ b/plugins/VersionUpgrade/VersionUpgrade25to26/VersionUpgrade25to26.py @@ -249,11 +249,11 @@ class VersionUpgrade25to26(VersionUpgrade): definition_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.DefinitionChangesContainer) 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()) - 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()) - 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()) ## Creates a definition changes container which doesn't contain anything for the Custom FDM Printers. diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 4985c2fd87..9d456c833d 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -1018,7 +1018,7 @@ class XmlMaterialProfile(InstanceContainer): @classmethod 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") - 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 = {key: [value] for key, value in product_to_id_map.items()} return product_to_id_map diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 7841c7d506..5de74ac3a8 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -170,7 +170,7 @@ UM.PreferencesPage append({ text: "日本語", code: "ja_JP" }) append({ text: "한국어", code: "ko_KR" }) 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", code: "pt_PT" }) append({ text: "Русский", code: "ru_RU" }) diff --git a/tests/TestProfileRequirements.py b/tests/TestProfileRequirements.py index edeec909f2..f75ca9da8d 100644 --- a/tests/TestProfileRequirements.py +++ b/tests/TestProfileRequirements.py @@ -19,7 +19,7 @@ import pytest 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. 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.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"]