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/Arranging/Arrange.py b/cura/Arranging/Arrange.py index 21ed45dbf1..ee5db3f1fb 100644 --- a/cura/Arranging/Arrange.py +++ b/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. 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..9db53c0836 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. @@ -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 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/Machines/Models/BrandMaterialsModel.py b/cura/Machines/Models/BrandMaterialsModel.py index f6c9a14632..236f105d12 100644 --- a/cura/Machines/Models/BrandMaterialsModel.py +++ b/cura/Machines/Models/BrandMaterialsModel.py @@ -47,22 +47,38 @@ class BrandMaterialsModel(ListModel): self.addRoleName(self.MaterialsRole, "materials") self._extruder_position = 0 + self._extruder_stack = None from cura.CuraApplication import CuraApplication self._machine_manager = CuraApplication.getInstance().getMachineManager() self._extruder_manager = CuraApplication.getInstance().getExtruderManager() self._material_manager = CuraApplication.getInstance().getMaterialManager() + self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack) self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines. self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes. self._update() + def _updateExtruderStack(self): + global_stack = self._machine_manager.activeMachine + if global_stack is None: + return + + if self._extruder_stack is not None: + self._extruder_stack.pyqtContainersChanged.disconnect(self._update) + self._extruder_stack = global_stack.extruders.get(str(self._extruder_position)) + if self._extruder_stack is not None: + self._extruder_stack.pyqtContainersChanged.connect(self._update) + # Force update the model when the extruder stack changes + self._update() + def setExtruderPosition(self, position: int): - if self._extruder_position != position: + if self._extruder_stack is None or self._extruder_position != position: self._extruder_position = position + self._updateExtruderStack() self.extruderPositionChanged.emit() - @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged) + @pyqtProperty(int, fset=setExtruderPosition, notify=extruderPositionChanged) def extruderPosition(self) -> int: return self._extruder_position diff --git a/cura/MultiplyObjectsJob.py b/cura/MultiplyObjectsJob.py index 57db7734e7..3cbf795952 100644 --- a/cura/MultiplyObjectsJob.py +++ b/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() 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..0ba4963886 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) @@ -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 diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 0f8cb9ae23..25690fcbde 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -461,10 +461,6 @@ class ExtruderManager(QObject): if global_stack.definitionChanges.hasProperty(key, "value"): global_stack.definitionChanges.removeInstance(key, postpone_emit = True) - # Update material diameter for extruders - for position in extruder_positions_to_update: - self.updateMaterialForDiameter(position, global_stack = global_stack) - ## Get all extruder values for a certain setting. # # This is exposed to SettingFunction so it can be used in value functions. @@ -556,96 +552,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 - - if not global_stack.getMetaDataEntry("has_materials", False): - return - - extruder_stack = global_stack.extruders[str(extruder_position)] - - material_diameter = extruder_stack.material.getProperty("material_diameter", "value") - if not material_diameter: - # in case of "empty" material - material_diameter = 0 - - material_approximate_diameter = str(round(material_diameter)) - material_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value") - setting_provider = extruder_stack - if not material_diameter: - if extruder_stack.definition.hasProperty("material_diameter", "value"): - material_diameter = extruder_stack.definition.getProperty("material_diameter", "value") - else: - material_diameter = global_stack.definition.getProperty("material_diameter", "value") - setting_provider = global_stack - - if isinstance(material_diameter, SettingFunction): - material_diameter = material_diameter(setting_provider) - - machine_approximate_diameter = str(round(material_diameter)) - - if material_approximate_diameter != machine_approximate_diameter: - Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.") - - if global_stack.getMetaDataEntry("has_machine_materials", False): - materials_definition = global_stack.definition.getId() - has_material_variants = global_stack.getMetaDataEntry("has_variants", False) - else: - materials_definition = "fdmprinter" - has_material_variants = False - - old_material = extruder_stack.material - search_criteria = { - "type": "material", - "approximate_diameter": machine_approximate_diameter, - "material": old_material.getMetaDataEntry("material", "value"), - "brand": old_material.getMetaDataEntry("brand", "value"), - "supplier": old_material.getMetaDataEntry("supplier", "value"), - "color_name": old_material.getMetaDataEntry("color_name", "value"), - "definition": materials_definition - } - if has_material_variants: - search_criteria["variant"] = extruder_stack.variant.getId() - - container_registry = Application.getInstance().getContainerRegistry() - empty_material = container_registry.findInstanceContainers(id = "empty_material")[0] - - if old_material == empty_material: - search_criteria.pop("material", None) - search_criteria.pop("supplier", None) - search_criteria.pop("brand", None) - search_criteria.pop("definition", None) - search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material") - - materials = container_registry.findInstanceContainers(**search_criteria) - if not materials: - # Same material with new diameter is not found, search for generic version of the same material type - search_criteria.pop("supplier", None) - search_criteria.pop("brand", None) - search_criteria["color_name"] = "Generic" - materials = container_registry.findInstanceContainers(**search_criteria) - if not materials: - # Generic material with new diameter is not found, search for preferred material - search_criteria.pop("color_name", None) - search_criteria.pop("material", None) - search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material") - materials = container_registry.findInstanceContainers(**search_criteria) - if not materials: - # Preferred material with new diameter is not found, search for any material - search_criteria.pop("id", None) - materials = container_registry.findInstanceContainers(**search_criteria) - if not materials: - # Just use empty material as a final fallback - materials = [empty_material] - - Logger.log("i", "Selecting new material: %s", materials[0].getId()) - - extruder_stack.material = materials[0] - ## 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..27ae1d69f0 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -63,7 +63,7 @@ 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) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 90241c6e72..ecccfc77ac 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -13,6 +13,7 @@ from UM.Workspace.WorkspaceReader import WorkspaceReader from UM.Application import Application from UM.Logger import Logger +from UM.Message import Message from UM.i18n import i18nCatalog from UM.Signal import postponeSignals, CompressTechnique from UM.Settings.ContainerFormatError import ContainerFormatError @@ -470,6 +471,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Logger.log("w", "File %s is not a valid workspace.", file_name) return WorkspaceReader.PreReadResult.failed + # Check if the machine definition exists. If not, indicate failure because we do not import definition files. + def_results = self._container_registry.findDefinitionContainersMetadata(id = machine_definition_id) + if not def_results: + message = Message(i18n_catalog.i18nc("@info:status Don't translate the XML tags or !", + "Project file {0} contains an unknown machine type" + " {1}. Cannot import the machine." + " Models will be imported instead.", file_name, machine_definition_id), + title = i18n_catalog.i18nc("@info:title", "Open Project File")) + message.show() + + Logger.log("i", "Could unknown machine definition %s in project file %s, cannot import it.", + self._machine_info.definition_id, file_name) + return WorkspaceReader.PreReadResult.failed + # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog. if not show_dialog: return WorkspaceReader.PreReadResult.accepted 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/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index d298ff1aca..3e66edc203 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -379,6 +379,14 @@ class CuraEngineBackend(QObject, Backend): else: self.backendStateChange.emit(BackendState.NotStarted) + if job.getResult() == StartSliceJob.StartJobResult.ObjectsWithDisabledExtruder: + self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()), + title = catalog.i18nc("@info:title", "Unable to slice")) + self._error_message.show() + self.backendStateChange.emit(BackendState.Error) + self.backendError.emit(job) + return + if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice: if Application.getInstance().platformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."), diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 467351dd4b..b704ca0d2f 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -32,6 +32,7 @@ class StartJobResult(IntEnum): MaterialIncompatible = 5 BuildPlateError = 6 ObjectSettingError = 7 #When an error occurs in per-object settings. + ObjectsWithDisabledExtruder = 8 ## Formatter class that handles token expansion in start/end gcod @@ -213,16 +214,27 @@ class StartSliceJob(Job): extruders_enabled = {position: stack.isEnabled for position, stack in Application.getInstance().getGlobalContainerStack().extruders.items()} filtered_object_groups = [] + has_model_with_disabled_extruders = False + associated_siabled_extruders = set() for group in object_groups: stack = Application.getInstance().getGlobalContainerStack() skip_group = False for node in group: - if not extruders_enabled[node.callDecoration("getActiveExtruderPosition")]: + extruder_position = node.callDecoration("getActiveExtruderPosition") + if not extruders_enabled[extruder_position]: skip_group = True + has_model_with_disabled_extruders = True + associated_siabled_extruders.add(extruder_position) break if not skip_group: filtered_object_groups.append(group) + if has_model_with_disabled_extruders: + self.setResult(StartJobResult.ObjectsWithDisabledExtruder) + associated_siabled_extruders = [str(c) for c in sorted([int(p) + 1 for p in associated_siabled_extruders])] + self.setMessage(", ".join(associated_siabled_extruders)) + return + # There are cases when there is nothing to slice. This can happen due to one at a time slicing not being # able to find a possible sequence or because there are no objects on the build plate (or they are outside # the build volume) 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..4c459e69cf 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(str(extruder_position)) diff --git a/plugins/SimulationView/SimulationSliderLabel.qml b/plugins/SimulationView/SimulationSliderLabel.qml index 6f7749df63..8615a382da 100644 --- a/plugins/SimulationView/SimulationSliderLabel.qml +++ b/plugins/SimulationView/SimulationSliderLabel.qml @@ -53,7 +53,7 @@ UM.PointingRectangle { verticalCenter: parent.verticalCenter } - width: 40 * screenScaleFactor + width: maximumValue.toString().length * 12 * screenScaleFactor text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array horizontalAlignment: TextInput.AlignRight @@ -77,11 +77,12 @@ UM.PointingRectangle { if (valueLabel.text != "") { // -startFrom because we need to convert back to an array structure sliderLabelRoot.setValue(parseInt(valueLabel.text) - startFrom) + } } validator: IntValidator { - bottom:startFrom + bottom: startFrom top: sliderLabelRoot.maximumValue + startFrom // +startFrom because maybe we want to start in a different value rather than 0 } } diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index c29c673c8a..2f26e21d65 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -262,12 +262,14 @@ class Toolbox(QObject, Extension): # list of old plugins old_plugin_ids = self._plugin_registry.getInstalledPlugins() installed_package_ids = self._package_manager.getAllInstalledPackageIDs() + scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs() self._old_plugin_ids = [] self._old_plugin_metadata = [] for plugin_id in old_plugin_ids: - if plugin_id not in installed_package_ids: + # Neither the installed packages nor the packages that are scheduled to remove are old plugins + if plugin_id not in installed_package_ids and plugin_id not in scheduled_to_remove_package_ids: Logger.log('i', 'Found a plugin that was installed with the old plugin browser: %s', plugin_id) old_metadata = self._plugin_registry.getMetaData(plugin_id) 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 01a79de57b..779473fdff 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -650,35 +650,36 @@ class XmlMaterialProfile(InstanceContainer): machine_manufacturer = identifier.get("manufacturer", definition.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition. - if machine_compatibility: - new_material_id = self.getId() + "_" + machine_id + # Always create the instance of the material even if it is not compatible, otherwise it will never + # show as incompatible if the material profile doesn't define hotends in the machine - CURA-5444 + new_material_id = self.getId() + "_" + machine_id - # The child or derived material container may already exist. This can happen when a material in a - # project file and the a material in Cura have the same ID. - # In the case if a derived material already exists, override that material container because if - # the data in the parent material has been changed, the derived ones should be updated too. - if ContainerRegistry.getInstance().isLoaded(new_material_id): - new_material = ContainerRegistry.getInstance().findContainers(id = new_material_id)[0] - is_new_material = False - else: - new_material = XmlMaterialProfile(new_material_id) - is_new_material = True + # The child or derived material container may already exist. This can happen when a material in a + # project file and the a material in Cura have the same ID. + # In the case if a derived material already exists, override that material container because if + # the data in the parent material has been changed, the derived ones should be updated too. + if ContainerRegistry.getInstance().isLoaded(new_material_id): + new_material = ContainerRegistry.getInstance().findContainers(id = new_material_id)[0] + is_new_material = False + else: + new_material = XmlMaterialProfile(new_material_id) + is_new_material = True - new_material.setMetaData(copy.deepcopy(self.getMetaData())) - new_material.getMetaData()["id"] = new_material_id - new_material.getMetaData()["name"] = self.getName() - new_material.setDefinition(machine_id) - # Don't use setMetadata, as that overrides it for all materials with same base file - new_material.getMetaData()["compatible"] = machine_compatibility - new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer - new_material.getMetaData()["definition"] = machine_id + new_material.setMetaData(copy.deepcopy(self.getMetaData())) + new_material.getMetaData()["id"] = new_material_id + new_material.getMetaData()["name"] = self.getName() + new_material.setDefinition(machine_id) + # Don't use setMetadata, as that overrides it for all materials with same base file + new_material.getMetaData()["compatible"] = machine_compatibility + new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer + new_material.getMetaData()["definition"] = machine_id - new_material.setCachedValues(cached_machine_setting_properties) + new_material.setCachedValues(cached_machine_setting_properties) - new_material._dirty = False + new_material._dirty = False - if is_new_material: - containers_to_add.append(new_material) + if is_new_material: + containers_to_add.append(new_material) # Find the buildplates compatibility buildplates = machine.iterfind("./um:buildplate", self.__namespaces) @@ -898,22 +899,23 @@ class XmlMaterialProfile(InstanceContainer): machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition. - if machine_compatibility: - new_material_id = container_id + "_" + machine_id + # Always create the instance of the material even if it is not compatible, otherwise it will never + # show as incompatible if the material profile doesn't define hotends in the machine - CURA-5444 + new_material_id = container_id + "_" + machine_id - # Do not look for existing container/container metadata with the same ID although they may exist. - # In project loading and perhaps some other places, we only want to get information (metadata) - # from a file without changing the current state of the system. If we overwrite the existing - # metadata here, deserializeMetadata() will not be safe for retrieving information. - new_material_metadata = {} + # Do not look for existing container/container metadata with the same ID although they may exist. + # In project loading and perhaps some other places, we only want to get information (metadata) + # from a file without changing the current state of the system. If we overwrite the existing + # metadata here, deserializeMetadata() will not be safe for retrieving information. + new_material_metadata = {} - new_material_metadata.update(base_metadata) - new_material_metadata["id"] = new_material_id - new_material_metadata["compatible"] = machine_compatibility - new_material_metadata["machine_manufacturer"] = machine_manufacturer - new_material_metadata["definition"] = machine_id + new_material_metadata.update(base_metadata) + new_material_metadata["id"] = new_material_id + new_material_metadata["compatible"] = machine_compatibility + new_material_metadata["machine_manufacturer"] = machine_manufacturer + new_material_metadata["definition"] = machine_id - result_metadata.append(new_material_metadata) + result_metadata.append(new_material_metadata) buildplates = machine.iterfind("./um:buildplate", cls.__namespaces) buildplate_map = {} @@ -1055,7 +1057,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/plugins/XmlMaterialProfile/product_to_id.json b/plugins/XmlMaterialProfile/product_to_id.json index d6b8f3bade..3e7ce9311f 100644 --- a/plugins/XmlMaterialProfile/product_to_id.json +++ b/plugins/XmlMaterialProfile/product_to_id.json @@ -8,5 +8,6 @@ "Ultimaker 3 Extended": "ultimaker3_extended", "Ultimaker Original": "ultimaker_original", "Ultimaker Original+": "ultimaker_original_plus", + "Ultimaker Original Dual Extrusion": "ultimaker_original_dual", "IMADE3D JellyBOX": "imade3d_jellybox" } \ No newline at end of file diff --git a/resources/i18n/fr_FR/fdmprinter.def.json.po b/resources/i18n/fr_FR/fdmprinter.def.json.po index 581c1ee054..97b197c138 100644 --- a/resources/i18n/fr_FR/fdmprinter.def.json.po +++ b/resources/i18n/fr_FR/fdmprinter.def.json.po @@ -1998,7 +1998,7 @@ msgstr "La vitesse à laquelle le filament est préparé pendant une rétraction #: fdmprinter.def.json msgctxt "retraction_extra_prime_amount label" msgid "Retraction Extra Prime Amount" -msgstr "Degré supplémentaire de rétraction primaire" +msgstr "Volume supplémentaire à l'amorçage" #: fdmprinter.def.json msgctxt "retraction_extra_prime_amount description" diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 7841c7d506..5e3c4f700b 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" }) @@ -741,21 +741,6 @@ UM.PreferencesPage } } - UM.TooltipArea - { - width: childrenRect.width - height: childrenRect.height - text: catalog.i18nc("@info:tooltip", "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)") - - CheckBox - { - id: arrangeOnLoadCheckbox - text: catalog.i18nc("@option:check", "Do not arrange objects on load") - checked: boolCheck(UM.Preferences.getValue("cura/not_arrange_objects_on_load")) - onCheckedChanged: UM.Preferences.setValue("cura/not_arrange_objects_on_load", checked) - } - } - Connections { target: UM.Preferences diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index 013371e528..48b3cd306d 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -264,7 +264,7 @@ Item { { // Another special case. The setting that is overriden is only 1 instance container deeper, // so we can remove it. - propertyProvider.removeFromContainer(0) + propertyProvider.removeFromContainer(last_entry - 1) } else { @@ -281,7 +281,7 @@ Item { color: UM.Theme.getColor("setting_control_button") hoverColor: UM.Theme.getColor("setting_control_button_hover") - iconSource: UM.Theme.getIcon("notice"); + iconSource: UM.Theme.getIcon("formula"); onEntered: { hoverTimer.stop(); base.showTooltip(catalog.i18nc("@label", "This setting is normally calculated, but it currently has an absolute value set.\n\nClick to restore the calculated value.")) } onExited: base.showTooltip(base.tooltipText); diff --git a/resources/quality/coarse.inst.cfg b/resources/quality/coarse.inst.cfg index ffbd57263b..478d8bc30c 100644 --- a/resources/quality/coarse.inst.cfg +++ b/resources/quality/coarse.inst.cfg @@ -1,6 +1,6 @@ [general] version = 4 -name = Coarse Quality +name = Coarse definition = fdmprinter [metadata] diff --git a/resources/quality/draft.inst.cfg b/resources/quality/draft.inst.cfg index 2e0e97e812..cf32886039 100644 --- a/resources/quality/draft.inst.cfg +++ b/resources/quality/draft.inst.cfg @@ -1,6 +1,6 @@ [general] version = 4 -name = Draft Quality +name = Draft definition = fdmprinter [metadata] diff --git a/resources/quality/extra_coarse.inst.cfg b/resources/quality/extra_coarse.inst.cfg index 98aeecbc79..271dfb8a63 100644 --- a/resources/quality/extra_coarse.inst.cfg +++ b/resources/quality/extra_coarse.inst.cfg @@ -1,6 +1,6 @@ [general] version = 4 -name = Extra Coarse Quality +name = Extra Coarse definition = fdmprinter [metadata] diff --git a/resources/quality/fast.inst.cfg b/resources/quality/fast.inst.cfg index b77932eb9b..8227b2fe63 100644 --- a/resources/quality/fast.inst.cfg +++ b/resources/quality/fast.inst.cfg @@ -1,6 +1,6 @@ [general] version = 4 -name = Low Quality +name = Normal definition = fdmprinter [metadata] diff --git a/resources/quality/ultimaker2/um2_draft.inst.cfg b/resources/quality/ultimaker2/um2_draft.inst.cfg index faf2b68208..27c40da15f 100644 --- a/resources/quality/ultimaker2/um2_draft.inst.cfg +++ b/resources/quality/ultimaker2/um2_draft.inst.cfg @@ -1,6 +1,6 @@ [general] version = 4 -name = Draft Quality +name = Draft definition = ultimaker2 [metadata] diff --git a/resources/quality/ultimaker2/um2_fast.inst.cfg b/resources/quality/ultimaker2/um2_fast.inst.cfg index 0534f821e1..236a6f73cd 100644 --- a/resources/quality/ultimaker2/um2_fast.inst.cfg +++ b/resources/quality/ultimaker2/um2_fast.inst.cfg @@ -1,6 +1,6 @@ [general] version = 4 -name = Low Quality +name = Normal definition = ultimaker2 [metadata] diff --git a/resources/quality/ultimaker2_plus/um2p_abs_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_abs_0.4_fast.inst.cfg index 1a1cd78cb1..4d7f6eb535 100644 --- a/resources/quality/ultimaker2_plus/um2p_abs_0.4_fast.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_abs_0.4_fast.inst.cfg @@ -25,3 +25,6 @@ speed_travel = 150 speed_wall = =math.ceil(speed_print * 40 / 55) top_bottom_thickness = 0.75 wall_thickness = 0.7 +speed_wall_0 = =math.ceil(speed_print * 40 / 55) +speed_wall_x = =math.ceil(speed_print * 80 / 55) +speed_infill = =math.ceil(speed_print * 100 / 55) diff --git a/resources/quality/ultimaker2_plus/um2p_abs_0.4_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_abs_0.4_high.inst.cfg index e955186e37..155d890d78 100644 --- a/resources/quality/ultimaker2_plus/um2p_abs_0.4_high.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_abs_0.4_high.inst.cfg @@ -23,3 +23,5 @@ speed_print = 45 speed_wall = =math.ceil(speed_print * 30 / 45) top_bottom_thickness = 0.72 wall_thickness = 1.05 +speed_topbottom = =math.ceil(speed_print * 15 / 45) +speed_infill = =math.ceil(speed_print * 80 / 45) diff --git a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_fast.inst.cfg index 7a92009ea1..2c3eebc1de 100644 --- a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_fast.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_fast.inst.cfg @@ -24,3 +24,7 @@ speed_travel = 150 speed_wall = =math.ceil(speed_print * 40 / 45) top_bottom_thickness = 0.75 wall_thickness = 0.7 +speed_wall_0 = =math.ceil(speed_print * 40 / 45) +speed_topbottom = =math.ceil(speed_print * 30 / 45) +speed_wall_x = =math.ceil(speed_print * 80 / 45) +speed_infill = =math.ceil(speed_print * 100 / 45) diff --git a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_high.inst.cfg index 176bd4c3b9..8facfa298c 100644 --- a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_high.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_high.inst.cfg @@ -23,3 +23,5 @@ speed_print = 45 speed_wall = =math.ceil(speed_print * 30 / 45) top_bottom_thickness = 0.72 wall_thickness = 1.05 +speed_topbottom = =math.ceil(speed_print * 15 / 45) +speed_infill = =math.ceil(speed_print * 80 / 45) diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.25_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.25_high.inst.cfg index a84f8853d1..b4622b1cdc 100644 --- a/resources/quality/ultimaker2_plus/um2p_nylon_0.25_high.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.25_high.inst.cfg @@ -42,3 +42,4 @@ support_xy_distance = 0.6 support_z_distance = =layer_height * 2 top_bottom_thickness = 1.2 wall_thickness = 1 +speed_infill = =math.ceil(speed_print * 80 / 40) diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.4_fast.inst.cfg index 6527c98990..36fe6170af 100644 --- a/resources/quality/ultimaker2_plus/um2p_nylon_0.4_fast.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.4_fast.inst.cfg @@ -41,3 +41,6 @@ support_xy_distance = 0.6 support_z_distance = =layer_height * 2 top_bottom_thickness = 0.75 wall_thickness = 1.06 +speed_wall_0 = =math.ceil(speed_print * 40 / 45) +speed_wall_x = =math.ceil(speed_print * 80 / 45) +speed_infill = =math.ceil(speed_print * 100 / 45) diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.6_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.6_fast.inst.cfg index f0a141bfa6..527a78a6c9 100644 --- a/resources/quality/ultimaker2_plus/um2p_nylon_0.6_fast.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.6_fast.inst.cfg @@ -46,3 +46,4 @@ support_xy_distance = 0.7 support_z_distance = =layer_height * 2 top_bottom_thickness = 1.2 wall_thickness = 1.2 +speed_infill = =math.ceil(speed_print * 100 / 55) diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.25_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.25_high.inst.cfg index d7cafd20a5..4a18f2ac65 100644 --- a/resources/quality/ultimaker2_plus/um2p_pc_0.25_high.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_pc_0.25_high.inst.cfg @@ -36,3 +36,5 @@ support_infill_rate = 20 support_pattern = lines support_z_distance = 0.19 wall_thickness = 0.88 +speed_topbottom = =math.ceil(speed_print * 15 / 30) +speed_infill = =math.ceil(speed_print * 80 / 30) diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.4_fast.inst.cfg index 23ace92112..5cf189f8da 100644 --- a/resources/quality/ultimaker2_plus/um2p_pc_0.4_fast.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_pc_0.4_fast.inst.cfg @@ -37,3 +37,5 @@ support_infill_rate = 20 support_pattern = lines support_z_distance = 0.19 wall_thickness = 1.2 +speed_topbottom = =math.ceil(speed_print * 30 / 45) +speed_infill = =math.ceil(speed_print * 100 / 45) diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.6_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.6_fast.inst.cfg index a038e6d2b8..09b60aadff 100644 --- a/resources/quality/ultimaker2_plus/um2p_pc_0.6_fast.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_pc_0.6_fast.inst.cfg @@ -43,3 +43,4 @@ support_pattern = lines support_z_distance = 0.21 top_bottom_thickness = 0.75 wall_thickness = 1.06 +speed_infill = =math.ceil(speed_print * 100 / 45) diff --git a/resources/quality/ultimaker2_plus/um2p_pp_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pp_0.4_fast.inst.cfg index cf4de8bb39..4a12fe0883 100644 --- a/resources/quality/ultimaker2_plus/um2p_pp_0.4_fast.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_pp_0.4_fast.inst.cfg @@ -69,3 +69,5 @@ travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =round(line_width * 0.38 / 0.38, 2) wall_thickness = 0.76 +speed_wall_x = =math.ceil(speed_print * 80 / 25) +speed_infill = =math.ceil(speed_print * 100 / 25) diff --git a/resources/quality/ultimaker2_plus/um2p_pp_0.6_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pp_0.6_fast.inst.cfg index 4421aba1ac..a8dd425d3d 100644 --- a/resources/quality/ultimaker2_plus/um2p_pp_0.6_fast.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_pp_0.6_fast.inst.cfg @@ -70,3 +70,5 @@ travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =round(line_width * 0.57 / 0.57, 2) wall_thickness = 1.14 +speed_wall_x = =math.ceil(speed_print * 80 / 25) +speed_infill = =math.ceil(speed_print * 100 / 25) diff --git a/resources/quality/ultimaker2_plus/um2p_tpu_0.25_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_tpu_0.25_high.inst.cfg index 384a96b2e1..559a636445 100644 --- a/resources/quality/ultimaker2_plus/um2p_tpu_0.25_high.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_tpu_0.25_high.inst.cfg @@ -41,3 +41,4 @@ support_xy_distance = 0.6 support_z_distance = =layer_height * 2 top_bottom_thickness = 1.2 wall_thickness = 0.88 +speed_infill = =math.ceil(speed_print * 80 / 40) diff --git a/resources/quality/ultimaker2_plus/um2p_tpu_0.6_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_tpu_0.6_fast.inst.cfg index aff0d6d0d5..87a429134f 100644 --- a/resources/quality/ultimaker2_plus/um2p_tpu_0.6_fast.inst.cfg +++ b/resources/quality/ultimaker2_plus/um2p_tpu_0.6_fast.inst.cfg @@ -43,3 +43,4 @@ support_xy_distance = 0.7 support_z_distance = =layer_height * 2 top_bottom_thickness = 1.2 wall_thickness = 1.14 +speed_infill = =math.ceil(speed_print * 100 / 45) diff --git a/resources/themes/cura-light/icons/formula.svg b/resources/themes/cura-light/icons/formula.svg new file mode 100644 index 0000000000..ae810bc014 --- /dev/null +++ b/resources/themes/cura-light/icons/formula.svg @@ -0,0 +1,53 @@ + + + + + + image/svg+xml + + + + + + + + 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"]