From f23d969c237d0e716b4c8078cf54e243ea0ea9b8 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 11 May 2017 10:55:56 +0200 Subject: [PATCH 01/66] Extend the zoomrange to cater for smaller and larger buildvolumes --- cura/CuraApplication.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3b5557b7d8..ea82bf14cf 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -619,7 +619,9 @@ class CuraApplication(QtApplication): camera.lookAt(Vector(0, 0, 0)) controller.getScene().setActiveCamera("3d") - self.getController().getTool("CameraTool").setOrigin(Vector(0, 100, 0)) + camera_tool = self.getController().getTool("CameraTool") + camera_tool.setOrigin(Vector(0, 100, 0)) + camera_tool.setZoomRange(0.1, 200000) self._camera_animation = CameraAnimation.CameraAnimation() self._camera_animation.setCameraTool(self.getController().getTool("CameraTool")) From fdc6fbbac16865ab3cb13360d0a38ef317514d5b Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 11 May 2017 17:50:12 +0200 Subject: [PATCH 02/66] Handle limit_to_extruder in {Extruder,Global}Stack getProperty This way we do not need to special case limit_to_extruder and instead always use limit_to_extruder if it is set. Contributest to CURA-3738 --- cura/Settings/ExtruderStack.py | 6 ++++++ cura/Settings/GlobalStack.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index 18a9969828..5fca888e4d 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -59,6 +59,12 @@ class ExtruderStack(CuraContainerStack): if not super().getProperty(key, "settable_per_extruder"): return self.getNextStack().getProperty(key, property_name) + limit_to_extruder = super().getProperty(key, "limit_to_extruder") + if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder): + result = self.getNextStack().extruders[int(limit_to_extruder)].getProperty(key, property_name) + if result is not None: + return result + return super().getProperty(key, property_name) @override(CuraContainerStack) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index cee141cf93..2f5cb48743 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -71,6 +71,7 @@ class GlobalStack(CuraContainerStack): if not self.definition.findDefinitions(key = key): return None + # Handle the "resolve" property. if self._shouldResolve(key, property_name): self._resolving_settings.add(key) resolve = super().getProperty(key, "resolve") @@ -78,6 +79,13 @@ class GlobalStack(CuraContainerStack): if resolve is not None: return resolve + # Handle the "limit_to_extruder" property. + limit_to_extruder = super().getProperty(key, "limit_to_extruder") + if limit_to_extruder is not None and limit_to_extruder != "-1": + result = self._extruders[int(limit_to_extruder)].getProperty(key, property_name) + if result is not None: + return result + return super().getProperty(key, property_name) ## Overridden from ContainerStack From 94881f59f5d49d50d4ed0592645141164fabf2d9 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 11 May 2017 17:51:10 +0200 Subject: [PATCH 03/66] Remove most of the "extruderValue" calls from fdmprinter support settings Since limit_to_extruder is now handled by the stacks themselves, we no longer need to account for it in the definition. Contributes to CURA-3738 --- resources/definitions/fdmprinter.def.json | 42 +++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 46b3b125a4..85c4a40be7 100755 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3170,7 +3170,7 @@ "default_value": 0.1, "type": "float", "enabled": "support_enable", - "value": "extruderValue(support_extruder_nr, 'support_z_distance')", + "value": "support_z_distance", "limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr", "settable_per_mesh": true }, @@ -3182,7 +3182,7 @@ "minimum_value": "0", "maximum_value_warning": "machine_nozzle_size", "default_value": 0.1, - "value": "extruderValue(support_extruder_nr, 'support_z_distance') if resolveOrValue('support_type') == 'everywhere' else 0", + "value": "support_z_distance if support_type == 'everywhere' else 0", "limit_to_extruder": "support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr", "type": "float", "enabled": "support_enable and resolveOrValue('support_type') == 'everywhere'", @@ -3225,11 +3225,11 @@ "unit": "mm", "type": "float", "minimum_value": "0", - "maximum_value_warning": "extruderValue(support_infill_extruder_nr, 'support_xy_distance')", + "maximum_value_warning": "support_xy_distance", "default_value": 0.2, "value": "machine_nozzle_size / 2", "limit_to_extruder": "support_infill_extruder_nr", - "enabled": "support_enable and extruderValue(support_infill_extruder_nr, 'support_xy_overrides_z') == 'z_overrides_xy'", + "enabled": "support_enable and support_xy_overrides_z == 'z_overrides_xy'", "settable_per_mesh": true }, "support_bottom_stair_step_height": @@ -3327,10 +3327,10 @@ "type": "float", "default_value": 1, "minimum_value": "0", - "minimum_value_warning": "0.2 + resolveOrValue('layer_height')", + "minimum_value_warning": "0.2 + layer_height", "maximum_value_warning": "10", "limit_to_extruder": "support_interface_extruder_nr", - "enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", + "enabled": "support_interface_enable and support_enable", "settable_per_mesh": true, "children": { @@ -3342,7 +3342,7 @@ "type": "float", "default_value": 1, "minimum_value": "0", - "minimum_value_warning": "0.2 + resolveOrValue('layer_height')", + "minimum_value_warning": "0.2 + layer_height", "maximum_value_warning": "10", "value": "extruderValue(support_roof_extruder_nr, 'support_interface_height')", "limit_to_extruder": "support_roof_extruder_nr", @@ -3358,7 +3358,7 @@ "default_value": 1, "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_height')", "minimum_value": "0", - "minimum_value_warning": "min(0.2 + resolveOrValue('layer_height'), extruderValue(support_bottom_extruder_nr, 'support_bottom_stair_step_height'))", + "minimum_value_warning": "min(0.2 + layer_height, extruderValue(support_bottom_extruder_nr, 'support_bottom_stair_step_height'))", "maximum_value_warning": "10", "limit_to_extruder": "support_bottom_extruder_nr", "enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", @@ -3375,7 +3375,7 @@ "minimum_value": "0", "maximum_value_warning": "support_interface_height", "limit_to_extruder": "support_interface_extruder_nr", - "enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", + "enabled": "support_interface_enable and support_enable", "settable_per_mesh": true }, "support_interface_density": @@ -3388,7 +3388,7 @@ "minimum_value": "0", "maximum_value_warning": "100", "limit_to_extruder": "support_interface_extruder_nr", - "enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", + "enabled": "support_interface_enable and support_enable", "settable_per_mesh": false, "settable_per_extruder": true, "children": @@ -3403,7 +3403,7 @@ "minimum_value": "0", "maximum_value": "100", "limit_to_extruder": "support_roof_extruder_nr", - "enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable", + "enabled": "support_roof_enable and support_enable", "settable_per_mesh": false, "settable_per_extruder": true, "children": @@ -3419,7 +3419,7 @@ "minimum_value_warning": "support_roof_line_width - 0.0001", "value": "0 if support_roof_density == 0 else (support_roof_line_width * 100) / support_roof_density * (2 if support_roof_pattern == 'grid' else (3 if support_roof_pattern == 'triangles' else 1))", "limit_to_extruder": "support_roof_extruder_nr", - "enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable", + "enabled": "support_roof_enable and support_enable", "settable_per_mesh": false, "settable_per_extruder": true } @@ -3435,7 +3435,7 @@ "minimum_value": "0", "maximum_value": "100", "limit_to_extruder": "support_bottom_extruder_nr", - "enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", + "enabled": "support_bottom_enable and support_enable", "settable_per_mesh": false, "settable_per_extruder": true, "children": @@ -3451,7 +3451,7 @@ "minimum_value_warning": "support_bottom_line_width - 0.0001", "value": "0 if support_bottom_density == 0 else (support_bottom_line_width * 100) / support_bottom_density * (2 if support_bottom_pattern == 'grid' else (3 if support_bottom_pattern == 'triangles' else 1))", "limit_to_extruder": "support_bottom_extruder_nr", - "enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", + "enabled": "support_bottom_enable and support_enable", "settable_per_mesh": false, "settable_per_extruder": true } @@ -3475,7 +3475,7 @@ }, "default_value": "concentric", "limit_to_extruder": "support_interface_extruder_nr", - "enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", + "enabled": "support_interface_enable and support_enable", "settable_per_mesh": false, "settable_per_extruder": true, "children": @@ -3497,7 +3497,7 @@ "default_value": "concentric", "value": "support_interface_pattern", "limit_to_extruder": "support_roof_extruder_nr", - "enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable", + "enabled": "support_roof_enable and support_enable", "settable_per_mesh": false, "settable_per_extruder": true }, @@ -3518,7 +3518,7 @@ "default_value": "concentric", "value": "support_interface_pattern", "limit_to_extruder": "support_bottom_extruder_nr", - "enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", + "enabled": "support_bottom_enable and support_enable", "settable_per_mesh": false, "settable_per_extruder": true } @@ -3545,7 +3545,7 @@ "minimum_value": "0", "minimum_value_warning": "2 * machine_nozzle_size", "maximum_value_warning": "20", - "enabled": "support_enable and extruderValue(support_infill_extruder_nr, 'support_use_towers')", + "enabled": "support_enable and support_use_towers", "settable_per_mesh": true }, "support_minimal_diameter": @@ -3559,8 +3559,8 @@ "minimum_value": "0", "minimum_value_warning": "2 * machine_nozzle_size", "maximum_value_warning": "20", - "maximum_value": "extruderValue(support_infill_extruder_nr, 'support_tower_diameter')", - "enabled": "support_enable and extruderValue(support_infill_extruder_nr, 'support_use_towers')", + "maximum_value": "support_tower_diameter", + "enabled": "support_enable and support_use_towers", "settable_per_mesh": true }, "support_tower_roof_angle": @@ -3573,7 +3573,7 @@ "maximum_value": "90", "default_value": 65, "limit_to_extruder": "support_infill_extruder_nr", - "enabled": "support_enable and extruderValue(support_infill_extruder_nr, 'support_use_towers')", + "enabled": "support_enable and support_use_towers", "settable_per_mesh": true } } From 434aaed767d609d2d5bf03e4cc76d3889c4d11f4 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 11 May 2017 17:56:31 +0200 Subject: [PATCH 04/66] Remove extruderValue call from raft setting Contiributes to CURA-3738 --- resources/definitions/fdmprinter.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 85c4a40be7..32df02f94d 100755 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3926,7 +3926,7 @@ "default_value": 1.6, "value": "raft_base_line_width * 2", "minimum_value": "0", - "minimum_value_warning": "extruderValue(adhesion_extruder_nr, 'raft_base_line_width')", + "minimum_value_warning": "raft_base_line_width", "maximum_value_warning": "100", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, From dadb5923282b2864a352ef7d296f65b2a99e166b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 11 May 2017 21:26:15 +0200 Subject: [PATCH 05/66] JSON feat: mold roof height (CURA-3797) --- resources/definitions/fdmprinter.def.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 46b3b125a4..a88f4653b2 100755 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4518,6 +4518,18 @@ "settable_per_mesh": true, "enabled": "mold_enabled" }, + "mold_roof_height": + { + "label": "Mold Roof Height", + "description": "The height above horizontal parts in your model which to print mold.", + "unit": "mm", + "type": "float", + "minimum_value": "0", + "maximum_value_warning": "5", + "default_value": 0.5, + "settable_per_mesh": true, + "enabled": "mold_enabled" + }, "mold_angle": { "label": "Mold Angle", From f8691d619019f35b42ed02c65d04d06019176aba Mon Sep 17 00:00:00 2001 From: Scheepers Date: Sat, 13 May 2017 13:10:24 +0200 Subject: [PATCH 06/66] New Cartesio build platform --- resources/definitions/cartesio.def.json | 4 ++-- resources/meshes/cartesio_platform.stl | Bin 84884 -> 94884 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/cartesio.def.json b/resources/definitions/cartesio.def.json index 6567f61fb3..f14f6572e4 100644 --- a/resources/definitions/cartesio.def.json +++ b/resources/definitions/cartesio.def.json @@ -19,7 +19,7 @@ "variants_name": "Nozzle size", "preferred_variant": "*0.8*", "preferred_material": "*pla*", - "preferred_quality": "*high*", + "preferred_quality": "*normal*", "machine_extruder_trains": { @@ -29,7 +29,7 @@ "3": "cartesio_extruder_3" }, "platform": "cartesio_platform.stl", - "platform_offset": [ -120, -1.5, 130], + "platform_offset": [ -220, -5, 150], "first_start_actions": ["MachineSettingsAction"], "supported_actions": ["MachineSettingsAction"] }, diff --git a/resources/meshes/cartesio_platform.stl b/resources/meshes/cartesio_platform.stl index 65f0204881879029f9e619b64c63e8bf8fa25670..d73581838be2a35790e5c1d4ec09c39dcc15c13a 100644 GIT binary patch literal 94884 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 literal 84884 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 Date: Mon, 15 May 2017 11:36:02 +0200 Subject: [PATCH 07/66] Fixes unit fallthrough unit test CURA-3738 --- tests/Settings/TestExtruderStack.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Settings/TestExtruderStack.py b/tests/Settings/TestExtruderStack.py index 4e55411d9d..4cafde5127 100644 --- a/tests/Settings/TestExtruderStack.py +++ b/tests/Settings/TestExtruderStack.py @@ -250,7 +250,8 @@ def test_getPropertyFallThrough(extruder_stack): container_indices = cura.Settings.CuraContainerStack._ContainerIndexes #Cache. for type_id, type_name in container_indices.IndexTypeMap.items(): container = unittest.mock.MagicMock() - container.getProperty = lambda key, property, type_id = type_id: type_id if (key == "layer_height" and property == "value") else None #Returns the container type ID as layer height, in order to identify it. + # Return type_id when asking for value and -1 when asking for limit_to_extruder + container.getProperty = lambda key, property, type_id = type_id: type_id if (key == "layer_height" and property == "value") else (None if property != "limit_to_extruder" else "-1") #Returns the container type ID as layer height, in order to identify it. container.hasProperty = lambda key, property: key == "layer_height" container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name) mock_layer_heights[type_id] = container From 06dc1d070e4f8908ee6ea0ce108725dd9801227d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 15 May 2017 11:46:22 +0200 Subject: [PATCH 08/66] Fixed two remaining failing unit tests CURA-3738 --- tests/Settings/TestGlobalStack.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py index 1eb3c43746..5b28e401d8 100755 --- a/tests/Settings/TestGlobalStack.py +++ b/tests/Settings/TestGlobalStack.py @@ -350,7 +350,7 @@ def test_getPropertyResolveInInstance(global_stack): instance_containers = {} for container_type in container_indices.IndexTypeMap: instance_containers[container_type] = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature. - instance_containers[container_type].getProperty = lambda key, property: (7.5 if property == "resolve" else (InstanceState.User if property == "state" else 5)) if (key == "material_bed_temperature") else None #7.5 resolve, 5 value. + instance_containers[container_type].getProperty = lambda key, property: (7.5 if property == "resolve" else (InstanceState.User if property == "state" else (5 if property != "limit_to_extruder" else "-1"))) if (key == "material_bed_temperature") else None #7.5 resolve, 5 value. instance_containers[container_type].getMetaDataEntry = unittest.mock.MagicMock(return_value = container_indices.IndexTypeMap[container_type]) #Make queries for the type return the desired type. instance_containers[container_indices.Definition].getProperty = lambda key, property: 10 if (key == "material_bed_temperature" and property == "value") else None #Definition only has value. with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. @@ -374,7 +374,7 @@ def test_getPropertyResolveInInstance(global_stack): # definitions. def test_getPropertyInstancesBeforeResolve(global_stack): value = unittest.mock.MagicMock() #Sets just the value. - value.getProperty = lambda key, property: (10 if property == "value" else InstanceState.User) if key == "material_bed_temperature" else None + value.getProperty = lambda key, property: (10 if property == "value" else (InstanceState.User if property != "limit_to_extruder" else "-1")) if key == "material_bed_temperature" else None value.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality") resolve = unittest.mock.MagicMock() #Sets just the resolve. resolve.getProperty = lambda key, property: 7.5 if (key == "material_bed_temperature" and property == "resolve") else None From 2cc56c5edf1f92ef24a3b23cc216c40c86a46afb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 15 May 2017 11:55:32 +0200 Subject: [PATCH 09/66] Fixed type hinting for Extruder stack --- cura/Settings/ExtruderStack.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index 5fca888e4d..f1bb5aec5f 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -1,7 +1,7 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from typing import Any +from typing import Any, TYPE_CHECKING, Optional from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot @@ -17,6 +17,9 @@ from . import Exceptions from .CuraContainerStack import CuraContainerStack from .ExtruderManager import ExtruderManager +if TYPE_CHECKING: + from cura.Settings.GlobalStack import GlobalStack + ## Represents an Extruder and its related containers. # # @@ -38,6 +41,10 @@ class ExtruderStack(CuraContainerStack): # For backward compatibility: Register the extruder with the Extruder Manager ExtruderManager.getInstance().registerExtruder(self, stack.id) + @override(ContainerStack) + def getNextStack(self) -> Optional["GlobalStack"]: + return super().getNextStack() + @classmethod def getLoadingPriority(cls) -> int: return 3 From ba8842f48999faf6cd6a1350bf1ad47e9433b889 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 15 May 2017 11:56:19 +0200 Subject: [PATCH 10/66] Removed unused imports --- cura/Settings/ExtruderStack.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index f1bb5aec5f..9187ecb35d 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -3,14 +3,10 @@ from typing import Any, TYPE_CHECKING, Optional -from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot - from UM.Decorators import override from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase -from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError +from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerRegistry import ContainerRegistry -from UM.Settings.InstanceContainer import InstanceContainer -from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.Interfaces import ContainerInterface from . import Exceptions From 02f04188897ddb713321b6f7fcaee1fd516199f8 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 16 May 2017 13:42:28 +0200 Subject: [PATCH 11/66] Prevent infinite recursion relating to limit_to_extruder With limit_to_extruder, we always try to get the value of a setting from an extruder. However, if the setting is not settable_per_extruder we bypass the extruder and go directly to the global stack. This would cause an infinite recursion. Contributes to CURA-3738 --- cura/Settings/GlobalStack.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 2f5cb48743..7d13f22180 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -82,9 +82,12 @@ class GlobalStack(CuraContainerStack): # Handle the "limit_to_extruder" property. limit_to_extruder = super().getProperty(key, "limit_to_extruder") if limit_to_extruder is not None and limit_to_extruder != "-1": - result = self._extruders[int(limit_to_extruder)].getProperty(key, property_name) - if result is not None: - return result + if super().getProperty(key, "settable_per_extruder"): + result = self._extruders[int(limit_to_extruder)].getProperty(key, property_name) + if result is not None: + return result + else: + Logger.log("e", "Setting {setting} has limit_to_extruder but is not settable per extruder!", setting = key) return super().getProperty(key, property_name) From 910811810b1e96f994b680d71269f04990ffb475 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 16 May 2017 13:54:45 +0200 Subject: [PATCH 12/66] Allow setting negative values on extruder offset x/y fields --- .../MachineSettingsAction.qml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.qml b/plugins/MachineSettingsAction/MachineSettingsAction.qml index 654030b013..23dc7f073f 100644 --- a/plugins/MachineSettingsAction/MachineSettingsAction.qml +++ b/plugins/MachineSettingsAction/MachineSettingsAction.qml @@ -341,7 +341,6 @@ Cura.MachineAction sourceComponent: numericTextFieldWithUnit property var propertyProvider: gantryHeightProvider property string unit: catalog.i18nc("@label", "mm") - property bool forceUpdateOnChange: false } Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } @@ -385,7 +384,6 @@ Cura.MachineAction sourceComponent: numericTextFieldWithUnit property var propertyProvider: materialDiameterProvider property string unit: catalog.i18nc("@label", "mm") - property bool forceUpdateOnChange: false } Label { @@ -399,7 +397,6 @@ Cura.MachineAction sourceComponent: numericTextFieldWithUnit property var propertyProvider: machineNozzleSizeProvider property string unit: catalog.i18nc("@label", "mm") - property bool forceUpdateOnChange: false } } } @@ -550,7 +547,6 @@ Cura.MachineAction sourceComponent: numericTextFieldWithUnit property var propertyProvider: extruderNozzleSizeProvider property string unit: catalog.i18nc("@label", "mm") - property bool forceUpdateOnChange: false } Label @@ -564,6 +560,7 @@ Cura.MachineAction property var propertyProvider: extruderOffsetXProvider property string unit: catalog.i18nc("@label", "mm") property bool forceUpdateOnChange: true + property bool allowNegative: true } Label { @@ -576,6 +573,7 @@ Cura.MachineAction property var propertyProvider: extruderOffsetYProvider property string unit: catalog.i18nc("@label", "mm") property bool forceUpdateOnChange: true + property bool allowNegative: true } } @@ -655,17 +653,21 @@ Cura.MachineAction Item { height: textField.height width: textField.width + + property bool _allowNegative: (typeof(allowNegative) === 'undefined') ? false : allowNegative + property bool _forceUpdateOnChange: (typeof(forceUpdateOnChange) === 'undefined') ? false: forceUpdateOnChange + TextField { id: textField text: (propertyProvider.properties.value) ? propertyProvider.properties.value : "" - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + validator: RegExpValidator { regExp: _allowNegative ? /-?[0-9\.]{0,6}/ : /[0-9\.]{0,6}/ } onEditingFinished: { if (propertyProvider && text != propertyProvider.properties.value) { propertyProvider.setPropertyValue("value", text); - if(forceUpdateOnChange) + if(_forceUpdateOnChange) { var extruderIndex = ExtruderManager.activeExtruderIndex; manager.forceUpdate(); From a7bf205dbca820206c8ba47db63f1fa701240acd Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 17 May 2017 10:58:28 +0200 Subject: [PATCH 13/66] Turn GlobalStack's extruders property into a dict We do not load the extruders in a fixed order, thus we cannot assume that addExtruder is called in the right order. This means that index 0 of the extruders is not necessarily the extruder with position 0. So instead, use the extruder position as a dict key. Contributes to CURA-3738 --- cura/Settings/ExtruderStack.py | 2 +- cura/Settings/GlobalStack.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index 9187ecb35d..70d2b0f6e4 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -64,7 +64,7 @@ class ExtruderStack(CuraContainerStack): limit_to_extruder = super().getProperty(key, "limit_to_extruder") if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder): - result = self.getNextStack().extruders[int(limit_to_extruder)].getProperty(key, property_name) + result = self.getNextStack().extruders[str(limit_to_extruder)].getProperty(key, property_name) if result is not None: return result diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 7d13f22180..33dc9ab2b5 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -1,7 +1,7 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from typing import Any +from typing import Any, Dict from PyQt5.QtCore import pyqtProperty @@ -24,7 +24,7 @@ class GlobalStack(CuraContainerStack): self.addMetaDataEntry("type", "machine") # For backward compatibility - self._extruders = [] + self._extruders = {} # This property is used to track which settings we are calculating the "resolve" for # and if so, to bypass the resolve to prevent an infinite recursion that would occur @@ -34,8 +34,8 @@ class GlobalStack(CuraContainerStack): ## Get the list of extruders of this stack. # # \return The extruders registered with this stack. - @pyqtProperty("QVariantList") - def extruders(self) -> list: + @pyqtProperty("QVariantMap") + def extruders(self) -> Dict[str, "ExtruderStack"]: return self._extruders @classmethod @@ -53,7 +53,7 @@ class GlobalStack(CuraContainerStack): if extruder_count and len(self._extruders) + 1 > extruder_count: Logger.log("w", "Adding extruder {meta} to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count, meta = str(extruder.getMetaData()))) - self._extruders.append(extruder) + self._extruders[extruder.getMetaDataEntry("position", "0")] = extruder ## Overridden from ContainerStack # @@ -83,7 +83,7 @@ class GlobalStack(CuraContainerStack): limit_to_extruder = super().getProperty(key, "limit_to_extruder") if limit_to_extruder is not None and limit_to_extruder != "-1": if super().getProperty(key, "settable_per_extruder"): - result = self._extruders[int(limit_to_extruder)].getProperty(key, property_name) + result = self._extruders[str(limit_to_extruder)].getProperty(key, property_name) if result is not None: return result else: From 61faaf47763595ad20d565d3b8a5538bdf99bdf0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 May 2017 11:02:16 +0200 Subject: [PATCH 14/66] Removed unneeded calls to extruder value CURA-3738 --- resources/definitions/fdmprinter.def.json | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 6c50a63b13..be283f134f 100755 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1953,7 +1953,7 @@ "minimum_value": "0.1", "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", "maximum_value_warning": "150", - "enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", + "enabled": "support_interface_enable and support_enable", "limit_to_extruder": "support_interface_extruder_nr", "value": "speed_support / 1.5", "settable_per_mesh": false, @@ -1970,7 +1970,7 @@ "minimum_value": "0.1", "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", "maximum_value_warning": "150", - "enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable", + "enabled": "support_roof_enable and support_enable", "limit_to_extruder": "support_roof_extruder_nr", "value": "speed_support_interface", "settable_per_mesh": false, @@ -1986,7 +1986,7 @@ "minimum_value": "0.1", "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", "maximum_value_warning": "150", - "enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", + "enabled": "support_bottom_enable and support_enable", "limit_to_extruder": "support_bottom_extruder_nr", "value": "speed_support_interface", "settable_per_mesh": false, @@ -2275,7 +2275,7 @@ "minimum_value": "0.1", "minimum_value_warning": "100", "maximum_value_warning": "10000", - "enabled": "resolveOrValue('acceleration_enabled') and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", + "enabled": "resolveOrValue('acceleration_enabled') and support_interface_enable and support_enable", "limit_to_extruder": "support_interface_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true, @@ -2292,7 +2292,7 @@ "minimum_value": "0.1", "minimum_value_warning": "100", "maximum_value_warning": "10000", - "enabled": "resolveOrValue('acceleration_enabled') and extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable", + "enabled": "resolveOrValue('acceleration_enabled') and support_roof_enable and support_enable", "limit_to_extruder": "support_roof_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true @@ -2308,7 +2308,7 @@ "minimum_value": "0.1", "minimum_value_warning": "100", "maximum_value_warning": "10000", - "enabled": "resolveOrValue('acceleration_enabled') and extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", + "enabled": "resolveOrValue('acceleration_enabled') and support_bottom_enable and support_enable", "limit_to_extruder": "support_bottom_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true @@ -2540,7 +2540,7 @@ "value": "jerk_support", "minimum_value": "0.1", "maximum_value_warning": "50", - "enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable", + "enabled": "resolveOrValue('jerk_enabled') and support_interface_enable and support_enable", "limit_to_extruder": "support_interface_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true, @@ -2556,7 +2556,7 @@ "value": "jerk_support_interface", "minimum_value": "0.1", "maximum_value_warning": "50", - "enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable", + "enabled": "resolveOrValue('jerk_enabled') and support_roof_enable and support_enable", "limit_to_extruder": "support_roof_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true @@ -2571,7 +2571,7 @@ "value": "jerk_support_interface", "minimum_value": "0.1", "maximum_value_warning": "50", - "enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", + "enabled": "resolveOrValue('jerk_enabled') and support_bottom_enable and support_enable", "limit_to_extruder": "support_bottom_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true @@ -3330,9 +3330,9 @@ "minimum_value": "0", "minimum_value_warning": "0.2 + layer_height", "maximum_value_warning": "10", - "value": "extruderValue(support_roof_extruder_nr, 'support_interface_height')", + "value": "support_interface_height", "limit_to_extruder": "support_roof_extruder_nr", - "enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable", + "enabled": "support_roof_enable and support_enable", "settable_per_mesh": true }, "support_bottom_height": @@ -3342,12 +3342,12 @@ "unit": "mm", "type": "float", "default_value": 1, - "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_height')", + "value": "support_interface_height", "minimum_value": "0", - "minimum_value_warning": "min(0.2 + layer_height, extruderValue(support_bottom_extruder_nr, 'support_bottom_stair_step_height'))", + "minimum_value_warning": "min(0.2 + layer_height, support_bottom_stair_step_height)", "maximum_value_warning": "10", "limit_to_extruder": "support_bottom_extruder_nr", - "enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable", + "enabled": "support_bottom_enable and support_enable", "settable_per_mesh": true } } @@ -3785,7 +3785,7 @@ "value": "resolveOrValue('layer_height')", "minimum_value": "0.001", "minimum_value_warning": "0.04", - "maximum_value_warning": "0.75 * extruderValue(adhesion_extruder_nr, 'machine_nozzle_size')", + "maximum_value_warning": "0.75 * machine_nozzle_size", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, "settable_per_extruder": true, @@ -3800,8 +3800,8 @@ "default_value": 0.4, "value": "line_width", "minimum_value": "0.001", - "minimum_value_warning": "extruderValue(adhesion_extruder_nr, 'machine_nozzle_size') * 0.1", - "maximum_value_warning": "extruderValue(adhesion_extruder_nr, 'machine_nozzle_size') * 2", + "minimum_value_warning": "machine_nozzle_size * 0.1", + "maximum_value_warning": "machine_nozzle_size * 2", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, "settable_per_extruder": true, @@ -3815,8 +3815,8 @@ "type": "float", "default_value": 0.4, "minimum_value": "0", - "minimum_value_warning": "extruderValue(adhesion_extruder_nr, 'raft_surface_line_width')", - "maximum_value_warning": "extruderValue(adhesion_extruder_nr, 'raft_surface_line_width') * 3", + "minimum_value_warning": "raft_surface_line_width", + "maximum_value_warning": "raft_surface_line_width * 3", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "value": "raft_surface_line_width", "settable_per_mesh": false, @@ -3833,7 +3833,7 @@ "value": "resolveOrValue('layer_height') * 1.5", "minimum_value": "0.001", "minimum_value_warning": "0.04", - "maximum_value_warning": "0.75 * extruderValue(adhesion_extruder_nr, 'machine_nozzle_size')", + "maximum_value_warning": "0.75 * machine_nozzle_size", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, "settable_per_extruder": true, @@ -3848,8 +3848,8 @@ "default_value": 0.7, "value": "line_width * 2", "minimum_value": "0.001", - "minimum_value_warning": "extruderValue(adhesion_extruder_nr, 'machine_nozzle_size') * 0.5", - "maximum_value_warning": "extruderValue(adhesion_extruder_nr, 'machine_nozzle_size') * 3", + "minimum_value_warning": "machine_nozzle_size * 0.5", + "maximum_value_warning": "machine_nozzle_size * 3", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, "settable_per_extruder": true, @@ -3864,7 +3864,7 @@ "default_value": 0.9, "value": "raft_interface_line_width + 0.2", "minimum_value": "0", - "minimum_value_warning": "extruderValue(adhesion_extruder_nr, 'raft_interface_line_width')", + "minimum_value_warning": "raft_interface_line_width", "maximum_value_warning": "15.0", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, @@ -3881,7 +3881,7 @@ "value": "resolveOrValue('layer_height_0') * 1.2", "minimum_value": "0.001", "minimum_value_warning": "0.04", - "maximum_value_warning": "0.75 * extruderValue(adhesion_extruder_nr, 'raft_base_line_width')", + "maximum_value_warning": "0.75 * raft_base_line_width", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, "settable_per_extruder": true, @@ -3895,9 +3895,9 @@ "type": "float", "default_value": 0.8, "minimum_value": "0.001", - "value": "extruderValue(adhesion_extruder_nr, 'machine_nozzle_size') * 2", - "minimum_value_warning": "extruderValue(adhesion_extruder_nr, 'machine_nozzle_size') * 0.5", - "maximum_value_warning": "extruderValue(adhesion_extruder_nr, 'machine_nozzle_size') * 3", + "value": "machine_nozzle_size * 2", + "minimum_value_warning": "machine_nozzle_size * 0.5", + "maximum_value_warning": "machine_nozzle_size * 3", "enabled": "resolveOrValue('adhesion_type') == 'raft'", "settable_per_mesh": false, "settable_per_extruder": true, From a9ec3f2712c32c7d45e6c7444ef8f142dc850a69 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 17 May 2017 11:25:45 +0200 Subject: [PATCH 15/66] Add docs to project loading code CURA-3756 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index c0b8e73a8c..a2504ddeec 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -470,6 +470,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader): container_type = instance_container.getMetaDataEntry("type") Job.yieldThread() + # + # IMPORTANT: + # If an instance container (or maybe other type of container) exists, and user chooses "Create New", + # we need to rename this container and all references to it, and changing those references are VERY + # HARD. + # if container_type in self._ignored_instance_container_types: # Ignore certain instance container types Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type) @@ -700,6 +706,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if self._resolve_strategies[changes_container_type] == "new": # Quality changes needs to get a new ID, added to registry and to the right stacks for each_changes_container in quality_and_definition_changes_instance_containers: + # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the + # instance container loading part. old_id = each_changes_container.getId() each_changes_container.setName(self._container_registry.uniqueName(each_changes_container.getName())) # We're not really supposed to change the ID in normal cases, but this is an exception. From 20e46f4d8b093baf488aec5bbb790f5070240da8 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 17 May 2017 11:30:48 +0200 Subject: [PATCH 16/66] Make Extruder setting type update its color properly The binding for the material color would not get updated properly when the model changes. This fixes that. Contributes to CURA-3804 --- resources/qml/Settings/SettingExtruder.qml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/resources/qml/Settings/SettingExtruder.qml b/resources/qml/Settings/SettingExtruder.qml index 92d2da31e8..4a946d16f8 100644 --- a/resources/qml/Settings/SettingExtruder.qml +++ b/resources/qml/Settings/SettingExtruder.qml @@ -17,7 +17,7 @@ SettingItem id: control anchors.fill: parent - model: Cura.ExtrudersModel { } + model: Cura.ExtrudersModel { onModelChanged: control.color = getItem(control.currentIndex).color } textRole: "name" @@ -36,6 +36,17 @@ SettingItem onWheel: wheel.accepted = true; } + property string color: "#fff" + + Binding + { + // We override the color property's value when the ExtruderModel changes. So we need to use an + // explicit binding here otherwise we do not handle value changes after the model changes. + target: control + property: "color" + value: control.currentText != "" ? control.model.getItem(control.currentIndex).color : "" + } + style: ComboBoxStyle { background: Rectangle @@ -83,7 +94,7 @@ SettingItem border.width: UM.Theme.getSize("default_lining").width border.color: enabled ? UM.Theme.getColor("setting_control_border") : UM.Theme.getColor("setting_control_disabled_border") - color: control.currentText != "" ? control.model.getItem(control.currentIndex).color : "" + color: control.color } Label { From 75519d754bdb2f0b68eca6efcfed681d68c2c510 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 17 May 2017 11:49:13 +0200 Subject: [PATCH 17/66] Fix creating materials for 1.75mm machines --- cura/Settings/ContainerManager.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index b5c76f1f17..f253b18444 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -708,6 +708,10 @@ class ContainerManager(QObject): # Ensure all settings are saved. Application.getInstance().saveSettings() + global_stack = Application.getInstance().getGlobalContainerStack() + if not global_stack: + return "" + containers = self._container_registry.findInstanceContainers(id="generic_pla") if not containers: Logger.log("d", "Unable to create a new material by cloning generic_pla, because it doesn't exist.") @@ -729,6 +733,13 @@ class ContainerManager(QObject): duplicated_container.setMetaDataEntry("material", catalog.i18nc("@label", "Custom")) duplicated_container.setName(catalog.i18nc("@label", "Custom Material")) + # Set new material diameter to match the current machine, or it will not be listed + material_diameter = global_stack.getProperty("material_diameter", "value") + properties = duplicated_container.getMetaDataEntry("properties", {}) + properties["diameter"] = str(material_diameter) + duplicated_container.setMetaDataEntry("properties", properties) + duplicated_container.setMetaDataEntry("approximate_diameter", round(material_diameter)) + self._container_registry.addContainer(duplicated_container) return self._getMaterialContainerIdForActiveMachine(new_id) From e2e208261ea5cee03bc69efc62eb271442e4f0a5 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 17 May 2017 11:52:21 +0200 Subject: [PATCH 18/66] Fix renaming quality_changes and definition containers in project loading CURA-3756 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index a2504ddeec..dcfe268665 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -527,9 +527,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader): elif self._resolve_strategies[container_type] == "new": # TODO: how should we handle the case "new" for quality_changes and definition_changes? + instance_container.setName(self._container_registry.uniqueName(instance_container.getName())) new_changes_container_id = self.getNewId(instance_container.getId()) instance_container._id = new_changes_container_id - instance_container.setName(new_changes_container_id) # TODO: we don't know the following is correct or not, need to verify # AND REFACTOR!!! @@ -708,13 +708,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): for each_changes_container in quality_and_definition_changes_instance_containers: # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the # instance container loading part. - old_id = each_changes_container.getId() - each_changes_container.setName(self._container_registry.uniqueName(each_changes_container.getName())) - # We're not really supposed to change the ID in normal cases, but this is an exception. - each_changes_container._id = self.getNewId(each_changes_container.getId()) - - # The container was not added yet, as it didn't have an unique ID. It does now, so add it. - self._container_registry.addContainer(each_changes_container) + new_id = each_changes_container.getId() # Find the old (current) changes container in the global stack if changes_container_type == "quality_changes": @@ -731,7 +725,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Replace the quality/definition changes container if it's in the GlobalStack # NOTE: we can get an empty container here, but the IDs will not match, # so this comparison is fine. - if old_container.getId() == old_id: + if self._id_mapping.get(old_container.getId()) == new_id: if changes_container_type == "quality_changes": global_stack.qualityChanges = each_changes_container elif changes_container_type == "definition_changes": @@ -754,7 +748,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # NOTE: we can get an empty container here, but the IDs will not match, # so this comparison is fine. - if changes_container.getId() == old_id: + if self._id_mapping.get(changes_container.getId()) == new_id: if changes_container_type == "quality_changes": each_extruder_stack.qualityChanges = each_changes_container elif changes_container_type == "definition_changes": From 7b393064aa2546eeb982d0a6c02df6fab5dbbc73 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 17 May 2017 12:20:00 +0200 Subject: [PATCH 19/66] Fix creating materials for 1.75mm machines that have machine/variant materials Instead of cloning the 2.85 mm generic PLA material and setting its diameter, we now clone the generic PLA with the same diameter as the current machine. This will also clone all the machine-specific settings included in that base file. --- cura/Settings/ContainerManager.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index f253b18444..6a7b671be1 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -700,7 +700,7 @@ class ContainerManager(QObject): self._container_registry.addContainer(duplicated_container) return self._getMaterialContainerIdForActiveMachine(new_id) - ## Create a new material by cloning Generic PLA and setting the GUID to something unqiue + ## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue # # \return \type{str} the id of the newly created container. @pyqtSlot(result = str) @@ -712,14 +712,21 @@ class ContainerManager(QObject): if not global_stack: return "" - containers = self._container_registry.findInstanceContainers(id="generic_pla") + approximate_diameter = round(global_stack.getProperty("material_diameter","value")) + containers = self._container_registry.findInstanceContainers(id="generic_pla*", approximate_diameter=approximate_diameter) if not containers: - Logger.log("d", "Unable to create a new material by cloning generic_pla, because it doesn't exist.") + Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.") + return "" + + base_file = containers[0].getMetaDataEntry("base_file") + containers = self._container_registry.findInstanceContainers(id=base_file) + if not containers: + Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.") return "" # Create a new ID & container to hold the data. new_id = self._container_registry.uniqueName("custom_material") - container_type = type(containers[0]) # Could be either a XMLMaterialProfile or a InstanceContainer + container_type = type(containers[0]) # Always XMLMaterialProfile, since we specifically clone the base_file duplicated_container = container_type(new_id) # Instead of duplicating we load the data from the basefile again. @@ -733,13 +740,6 @@ class ContainerManager(QObject): duplicated_container.setMetaDataEntry("material", catalog.i18nc("@label", "Custom")) duplicated_container.setName(catalog.i18nc("@label", "Custom Material")) - # Set new material diameter to match the current machine, or it will not be listed - material_diameter = global_stack.getProperty("material_diameter", "value") - properties = duplicated_container.getMetaDataEntry("properties", {}) - properties["diameter"] = str(material_diameter) - duplicated_container.setMetaDataEntry("properties", properties) - duplicated_container.setMetaDataEntry("approximate_diameter", round(material_diameter)) - self._container_registry.addContainer(duplicated_container) return self._getMaterialContainerIdForActiveMachine(new_id) From e60825215168e23e7224d72b919b07d587b59532 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 May 2017 13:21:11 +0200 Subject: [PATCH 20/66] Fixed missing namechanges for global profiles CURA-3777 --- resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg | 2 +- resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg | 2 +- .../quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg | 2 +- .../quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg index ee35746983..2bca653fb7 100644 --- a/resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg @@ -1,6 +1,6 @@ [general] version = 2 -name = Draft Quality +name = Fast definition = ultimaker3 [metadata] diff --git a/resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg index 274533485e..f8d869b65c 100644 --- a/resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg @@ -1,6 +1,6 @@ [general] version = 2 -name = Fast Quality +name = Normal definition = ultimaker3 [metadata] diff --git a/resources/quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg index d9c8d00eee..1f095a71e4 100755 --- a/resources/quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg @@ -1,6 +1,6 @@ [general] version = 2 -name = Superdraft Quality +name = Sprint definition = ultimaker3 [metadata] diff --git a/resources/quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg index 35a62bd63e..324e007653 100755 --- a/resources/quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg @@ -1,6 +1,6 @@ [general] version = 2 -name = Verydraft Quality +name = Extra Fast definition = ultimaker3 [metadata] From d02d00498677f432965eee5caa705e3f21237daa Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 17 May 2017 13:44:32 +0200 Subject: [PATCH 21/66] Correct profile_translations in VersionUpgrade21to22 CURA-3777 --- .../VersionUpgrade21to22/VersionUpgrade21to22.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py b/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py index d5bb08bb5c..055d28b3ab 100644 --- a/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py +++ b/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py @@ -70,8 +70,8 @@ _printer_translations_profiles = { # as a set for which profiles were built-in. _profile_translations = { "Low Quality": "low", - "Fine": "normal", - "Extra Fine": "high", + "Normal Quality": "normal", + "High Quality": "high", "Ulti Quality": "high", #This one doesn't have an equivalent. Map it to high. "abs_0.25_normal": "um2p_abs_0.25_normal", "abs_0.4_fast": "um2p_abs_0.4_fast", From 63e586ab5ba0d628da01d838ec6d1846c49f4247 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 17 May 2017 13:44:21 +0200 Subject: [PATCH 22/66] Ensure the value of limit_to_extruder is an extruder before accessing it Contributes to CURA-3738 --- cura/Settings/GlobalStack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 33dc9ab2b5..4dba8278f3 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -81,7 +81,7 @@ class GlobalStack(CuraContainerStack): # Handle the "limit_to_extruder" property. limit_to_extruder = super().getProperty(key, "limit_to_extruder") - if limit_to_extruder is not None and limit_to_extruder != "-1": + if limit_to_extruder is not None and limit_to_extruder != "-1" and limit_to_extruder in self._extruders: if super().getProperty(key, "settable_per_extruder"): result = self._extruders[str(limit_to_extruder)].getProperty(key, property_name) if result is not None: From 3b73979a2ca8bb6992941015ce6d9faf0292760a Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 17 May 2017 13:45:51 +0200 Subject: [PATCH 23/66] Do not add an extruder if it has no position set Otherwise we get very weird values in the extruders dict Contributes to CURA-3738 --- cura/Settings/GlobalStack.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 4dba8278f3..6278c02aeb 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -53,7 +53,12 @@ class GlobalStack(CuraContainerStack): if extruder_count and len(self._extruders) + 1 > extruder_count: Logger.log("w", "Adding extruder {meta} to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count, meta = str(extruder.getMetaData()))) - self._extruders[extruder.getMetaDataEntry("position", "0")] = extruder + position = extruder.getMetaDataEntry("position") + if position is None: + Logger.log("w", "No position defined for extruder {extruder}, cannot add it to stack {stack}", extruder = extruder.id, stack = self.id) + return + + self._extruders[position] = extruder ## Overridden from ContainerStack # From be08fab97c1f4d15f91b18e81d8724a128bd9d3a Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 17 May 2017 13:46:51 +0200 Subject: [PATCH 24/66] Fix the addExtruder unit test Extruders now need to provide a "position" entry. Contributes to CURA-3738 --- tests/Settings/TestGlobalStack.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py index 5b28e401d8..6dab02b220 100755 --- a/tests/Settings/TestGlobalStack.py +++ b/tests/Settings/TestGlobalStack.py @@ -69,11 +69,13 @@ def test_addExtruder(global_stack): assert len(global_stack.extruders) == 0 first_extruder = unittest.mock.MagicMock() + first_extruder.getMetaDataEntry = lambda key: 0 if key == "position" else None with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): global_stack.addExtruder(first_extruder) assert len(global_stack.extruders) == 1 assert global_stack.extruders[0] == first_extruder second_extruder = unittest.mock.MagicMock() + second_extruder.getMetaDataEntry = lambda key: 1 if key == "position" else None with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): global_stack.addExtruder(second_extruder) assert len(global_stack.extruders) == 2 From 3b3d9c4dae1d03ccd5d812df835e6e9e17005785 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 17 May 2017 15:53:24 +0200 Subject: [PATCH 25/66] Minor code refactoring in ExtruderManager CURA-3756 --- cura/Settings/ExtruderManager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 10934c2d31..6d9f939091 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -77,8 +77,9 @@ class ExtruderManager(QObject): @pyqtProperty("QVariantMap", notify=extrudersChanged) def extruderIds(self): map = {} - for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]: - map[position] = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position].getId() + global_stack_id = Application.getInstance().getGlobalContainerStack().getId() + for position in self._extruder_trains[global_stack_id]: + map[position] = self._extruder_trains[global_stack_id][position].getId() return map @pyqtSlot(str, result = str) From 386e5db489c87523657db0333e1742ce70acb211 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 17 May 2017 15:54:41 +0200 Subject: [PATCH 26/66] When removing a machine's extruders, also clean up _extruder_trains CURA-3756 --- cura/Settings/ExtruderManager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 6d9f939091..24a7ee1ec4 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -463,6 +463,7 @@ class ExtruderManager(QObject): for extruder in self.getMachineExtruders(machine_id): ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId()) ContainerRegistry.getInstance().removeContainer(extruder.getId()) + del self._extruder_trains[machine_id] ## Returns extruders for a specific machine. # From b027437ef3aafe3a13e92afca39d76f736f1f4db Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 17 May 2017 15:55:52 +0200 Subject: [PATCH 27/66] Activate a new machine (if any) before removing a machine CURA-3756 --- cura/Settings/MachineManager.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 0eafd33e94..ae9e9d329b 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1061,19 +1061,19 @@ class MachineManager(QObject): # If the machine that is being removed is the currently active machine, set another machine as the active machine. activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id) - ExtruderManager.getInstance().removeMachineExtruders(machine_id) + # activate a new machine before removing a machine because this is safer + if activate_new_machine: + machine_stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine") + other_machine_stacks = [s for s in machine_stacks if s.getId() != machine_id] + if other_machine_stacks: + Application.getInstance().setGlobalContainerStack(other_machine_stacks[0]) + ExtruderManager.getInstance().removeMachineExtruders(machine_id) containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id) for container in containers: ContainerRegistry.getInstance().removeContainer(container.getId()) ContainerRegistry.getInstance().removeContainer(machine_id) - if activate_new_machine: - stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine") - if stacks: - Application.getInstance().setGlobalContainerStack(stacks[0]) - - @pyqtProperty(bool, notify = globalContainerChanged) def hasMaterials(self) -> bool: if self._global_container_stack: From e1455a45511237e066289649e219ff3926d12e6d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 17 May 2017 15:56:45 +0200 Subject: [PATCH 28/66] Fix deserializing ExtruderStacks in project loading CURA-3756 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 59 ++++++++++++++++----- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index dcfe268665..c3a628f758 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -393,12 +393,23 @@ class ThreeMFWorkspaceReader(WorkspaceReader): global_stack_id_original = self._stripFileToId(global_stack_file) global_stack_id_new = global_stack_id_original global_stack_need_rename = False + + extruder_stack_id_map = {} # new and old ExtruderStack IDs map if self._resolve_strategies["machine"] == "new": # We need a new id if the id already exists if self._container_registry.findContainerStacks(id = global_stack_id_original): global_stack_id_new = self.getNewId(global_stack_id_original) global_stack_need_rename = True + for each_extruder_stack_file in extruder_stack_files: + old_container_id = self._stripFileToId(each_extruder_stack_file) + new_container_id = old_container_id + if self._container_registry.findContainerStacks(id = old_container_id): + # get a new name for this extruder + new_container_id = self.getNewId(old_container_id) + + extruder_stack_id_map[old_container_id] = new_container_id + # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few # TODO: cases that the container loaded is the same (most notable in materials & definitions). # TODO: It might be possible that we need to add smarter checking in the future. @@ -492,9 +503,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader): instance_container.setDirty(True) elif self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. - extruder_id = instance_container.getMetaDataEntry("extruder", None) - if extruder_id: - new_extruder_id = self.getNewId(extruder_id) + old_extruder_id = instance_container.getMetaDataEntry("extruder", None) + if old_extruder_id: + new_extruder_id = extruder_stack_id_map[old_extruder_id] new_id = new_extruder_id + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) @@ -535,9 +546,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # AND REFACTOR!!! if self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. - extruder_id = instance_container.getMetaDataEntry("extruder", None) - if extruder_id: - new_extruder_id = self.getNewId(extruder_id) + old_extruder_id = instance_container.getMetaDataEntry("extruder", None) + if old_extruder_id: + new_extruder_id = extruder_stack_id_map[old_extruder_id] instance_container.setMetaDataEntry("extruder", new_extruder_id) machine_id = instance_container.getMetaDataEntry("machine", None) @@ -641,9 +652,22 @@ class ThreeMFWorkspaceReader(WorkspaceReader): elif self._resolve_strategies["machine"] == "new": # create a new extruder stack from this one - new_id = self.getNewId(container_id) + new_id = extruder_stack_id_map[container_id] stack = ExtruderStack(new_id) - stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8")) + + # HACK: the global stack can have a new name, so we need to make sure that this extruder stack + # references to the new name instead of the old one. Normally, this can be done after + # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize() + # also does addExtruder() to its machine stack, so we have to make sure that it's pointing + # to the right machine BEFORE deserialization. + extruder_config = configparser.ConfigParser() + extruder_config.read_string(extruder_file_content) + extruder_config.set("metadata", "machine", global_stack_id_new) + tmp_string_io = io.StringIO() + extruder_config.write(tmp_string_io) + extruder_file_content = tmp_string_io.getvalue() + + stack.deserialize(extruder_file_content) # Ensure a unique ID and name stack._id = new_id @@ -660,17 +684,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader): elif self._resolve_strategies["machine"] == "new": # container not found, create a new one stack = ExtruderStack(container_id) - stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8")) + + # HACK: the global stack can have a new name, so we need to make sure that this extruder stack + # references to the new name instead of the old one. Normally, this can be done after + # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize() + # also does addExtruder() to its machine stack, so we have to make sure that it's pointing + # to the right machine BEFORE deserialization. + extruder_config = configparser.ConfigParser() + extruder_config.read_string(extruder_file_content) + extruder_config.set("metadata", "machine", global_stack_id_new) + tmp_string_io = io.StringIO() + extruder_config.write(tmp_string_io) + extruder_file_content = tmp_string_io.getvalue() + + stack.deserialize(extruder_file_content) self._container_registry.addContainer(stack) extruder_stacks_added.append(stack) containers_added.append(stack) else: Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"])) - if global_stack_need_rename: - if stack.getMetaDataEntry("machine"): - stack.setMetaDataEntry("machine", global_stack_id_new) - extruder_stacks.append(stack) except: Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") From f65043ecc8f7b506464b31ba5995fac58b4d0358 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 17 May 2017 16:17:48 +0200 Subject: [PATCH 29/66] Add spaces around binary operators and after comma Just a little code style thing. Contributes to issue CURA-3828. --- cura/Settings/ContainerManager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 6a7b671be1..fc256e37be 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -712,14 +712,14 @@ class ContainerManager(QObject): if not global_stack: return "" - approximate_diameter = round(global_stack.getProperty("material_diameter","value")) - containers = self._container_registry.findInstanceContainers(id="generic_pla*", approximate_diameter=approximate_diameter) + approximate_diameter = round(global_stack.getProperty("material_diameter", "value")) + containers = self._container_registry.findInstanceContainers(id = "generic_pla*", approximate_diameter = approximate_diameter) if not containers: Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.") return "" base_file = containers[0].getMetaDataEntry("base_file") - containers = self._container_registry.findInstanceContainers(id=base_file) + containers = self._container_registry.findInstanceContainers(id = base_file) if not containers: Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.") return "" From 428d0630448787075d42a61228f71ed6ff6e6420 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 May 2017 16:24:13 +0200 Subject: [PATCH 30/66] Added missing variants for um2_extended if you have olson block CURA-3742 --- .../variants/ultimaker2_extended_0.25.inst.cfg | 13 +++++++++++++ resources/variants/ultimaker2_extended_0.4.inst.cfg | 13 +++++++++++++ resources/variants/ultimaker2_extended_0.6.inst.cfg | 13 +++++++++++++ resources/variants/ultimaker2_extended_0.8.inst.cfg | 13 +++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 resources/variants/ultimaker2_extended_0.25.inst.cfg create mode 100644 resources/variants/ultimaker2_extended_0.4.inst.cfg create mode 100644 resources/variants/ultimaker2_extended_0.6.inst.cfg create mode 100644 resources/variants/ultimaker2_extended_0.8.inst.cfg diff --git a/resources/variants/ultimaker2_extended_0.25.inst.cfg b/resources/variants/ultimaker2_extended_0.25.inst.cfg new file mode 100644 index 0000000000..1a53ebc362 --- /dev/null +++ b/resources/variants/ultimaker2_extended_0.25.inst.cfg @@ -0,0 +1,13 @@ +[general] +name = 0.25 mm +version = 2 +definition = ultimaker2_extended + +[metadata] +author = Ultimaker +type = variant +setting_version = 1 + +[values] +machine_nozzle_size = 0.25 +machine_nozzle_tip_outer_diameter = 0.8 diff --git a/resources/variants/ultimaker2_extended_0.4.inst.cfg b/resources/variants/ultimaker2_extended_0.4.inst.cfg new file mode 100644 index 0000000000..e7caaa0772 --- /dev/null +++ b/resources/variants/ultimaker2_extended_0.4.inst.cfg @@ -0,0 +1,13 @@ +[general] +name = 0.4 mm +version = 2 +definition = ultimaker2_extended + +[metadata] +author = Ultimaker +type = variant +setting_version = 1 + +[values] +machine_nozzle_size = 0.4 +machine_nozzle_tip_outer_diameter = 1.05 diff --git a/resources/variants/ultimaker2_extended_0.6.inst.cfg b/resources/variants/ultimaker2_extended_0.6.inst.cfg new file mode 100644 index 0000000000..888dce45e8 --- /dev/null +++ b/resources/variants/ultimaker2_extended_0.6.inst.cfg @@ -0,0 +1,13 @@ +[general] +name = 0.6 mm +version = 2 +definition = ultimaker2_extended + +[metadata] +author = Ultimaker +type = variant +setting_version = 1 + +[values] +machine_nozzle_size = 0.6 +machine_nozzle_tip_outer_diameter = 1.25 diff --git a/resources/variants/ultimaker2_extended_0.8.inst.cfg b/resources/variants/ultimaker2_extended_0.8.inst.cfg new file mode 100644 index 0000000000..3f2e9dc55f --- /dev/null +++ b/resources/variants/ultimaker2_extended_0.8.inst.cfg @@ -0,0 +1,13 @@ +[general] +name = 0.8 mm +version = 2 +definition = ultimaker2_extended + +[metadata] +author = Ultimaker +type = variant +setting_version = 1 + +[values] +machine_nozzle_size = 0.8 +machine_nozzle_tip_outer_diameter = 1.35 From b2f0623ecfebc6b662b71bdd957e594d0c2b5f60 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 17 May 2017 17:06:52 +0200 Subject: [PATCH 31/66] Do not add and register Extruders if they are already there CURA-3756 --- cura/Settings/ExtruderManager.py | 7 +++++++ cura/Settings/GlobalStack.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 24a7ee1ec4..9c45815eb9 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -237,6 +237,13 @@ class ExtruderManager(QObject): if machine_id not in self._extruder_trains: self._extruder_trains[machine_id] = {} changed = True + + # do not register if an extruder has already been registered at the position on this machine + if any(item.getId() == extruder_train.getId() for item in self._extruder_trains[machine_id].values()): + Logger.log("w", "Extruder [%s] has already been registered on machine [%s], not doing anything", + extruder_train.getId(), machine_id) + return + if extruder_train: self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train changed = True diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 6278c02aeb..9dfb8523b3 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -52,12 +52,16 @@ class GlobalStack(CuraContainerStack): extruder_count = self.getProperty("machine_extruder_count", "value") if extruder_count and len(self._extruders) + 1 > extruder_count: Logger.log("w", "Adding extruder {meta} to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count, meta = str(extruder.getMetaData()))) + return position = extruder.getMetaDataEntry("position") if position is None: Logger.log("w", "No position defined for extruder {extruder}, cannot add it to stack {stack}", extruder = extruder.id, stack = self.id) return + if any(item.getId() == extruder.id for item in self._extruders.values()): + Logger.log("w", "Extruder [%s] has already been added to this stack [%s]", extruder.id, self._id) + return self._extruders[position] = extruder ## Overridden from ContainerStack From 18dd8dc6cc3c8471026f6fbe8354ef80a73b477e Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 17 May 2017 17:10:52 +0200 Subject: [PATCH 32/66] Simplify ExtruderStack override code in project loading CURA-3756 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index c3a628f758..0262eba419 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -313,30 +313,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader): ## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack. def _overrideExtruderStack(self, global_stack, extruder_index, extruder_file_content): - extruder_stack = global_stack.extruders[extruder_index] - machine_extruder_count = len(global_stack.extruders) + extruder_index_str = str(extruder_index) + extruder_stack = global_stack.extruders[extruder_index_str] old_extruder_stack_id = extruder_stack.getId() - # HACK: There are two cases: - # - the new ExtruderStack has the same ID as the one we are overriding - # - they don't have the same ID - # In the second case, directly overriding the existing ExtruderStack will leave the old stack file - # in the Cura directory, and this will cause a problem when we restart Cura. So, we always delete - # the existing file first. - self._container_registry._deleteFiles(extruder_stack) - # override the given extruder stack extruder_stack.deserialize(extruder_file_content) - # HACK: The deserialize() of ExtruderStack will add itself to the GlobalStack, which is redundant here. - # So we need to remove the new entries in the GlobalStack. - global_stack._extruders = global_stack._extruders[:machine_extruder_count] - - # HACK: clean and fill the container query cache again - if old_extruder_stack_id in self._container_registry._id_container_cache: - del self._container_registry._id_container_cache[old_extruder_stack_id] - new_extruder_stack_id = extruder_stack.getId() - self._container_registry._id_container_cache[new_extruder_stack_id] = extruder_stack # return the new ExtruderStack return extruder_stack From 29e8325c1253290f66444cbddc11789f7aadc49b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 18 May 2017 09:56:13 +0200 Subject: [PATCH 33/66] Only try to remove extruders if the machine has any --- cura/Settings/ExtruderManager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 9c45815eb9..b7cbf80d65 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -470,7 +470,8 @@ class ExtruderManager(QObject): for extruder in self.getMachineExtruders(machine_id): ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId()) ContainerRegistry.getInstance().removeContainer(extruder.getId()) - del self._extruder_trains[machine_id] + if machine_id in self._extruder_trains: + del self._extruder_trains[machine_id] ## Returns extruders for a specific machine. # From cbdddd0b16a4d8c21c62f57719100df2d37119ec Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 18 May 2017 10:24:52 +0200 Subject: [PATCH 34/66] Ignore "quality" and "variant" containers in project loading CURA-3756 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 0262eba419..2e7977ecda 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -448,13 +448,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader): container_id = self._stripFileToId(instance_container_file) serialized = archive.open(instance_container_file).read().decode("utf-8") - # HACK! we ignore the "metadata/type = quality" instance containers! + # HACK! we ignore "quality" and "variant" instance containers! parser = configparser.ConfigParser() parser.read_string(serialized) if not parser.has_option("metadata", "type"): Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file) continue - if parser.get("metadata", "type") == "quality": + if parser.get("metadata", "type") in self._ignored_instance_container_types: continue instance_container = InstanceContainer(container_id) From 5ed89525fb51eca461035f84124fac26846914f0 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 18 May 2017 10:26:32 +0200 Subject: [PATCH 35/66] selectedObjectExtruders should handle no extruder case CURA-3756 --- cura/Settings/ExtruderManager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index b7cbf80d65..bab4aa2d9e 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -151,14 +151,14 @@ class ExtruderManager(QObject): selected_nodes.append(node) # Then, figure out which nodes are used by those selected nodes. + global_stack = Application.getInstance().getGlobalContainerStack() + current_extruder_trains = self._extruder_trains.get(global_stack.getId()) for node in selected_nodes: extruder = node.callDecoration("getActiveExtruder") if extruder: object_extruders.add(extruder) - else: - global_stack = Application.getInstance().getGlobalContainerStack() - if global_stack.getId() in self._extruder_trains: - object_extruders.add(self._extruder_trains[global_stack.getId()]["0"].getId()) + elif current_extruder_trains: + object_extruders.add(current_extruder_trains["0"].getId()) self._selected_object_extruders = list(object_extruders) From 898da21a7b097e57e4b072ad4945c39067fe119f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 18 May 2017 10:49:05 +0200 Subject: [PATCH 36/66] Added missing super call CURA-3832 --- cura/ZOffsetDecorator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/ZOffsetDecorator.py b/cura/ZOffsetDecorator.py index 66dddfd390..d3ee5c8454 100644 --- a/cura/ZOffsetDecorator.py +++ b/cura/ZOffsetDecorator.py @@ -3,6 +3,7 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator ## A decorator that stores the amount an object has been moved below the platform. class ZOffsetDecorator(SceneNodeDecorator): def __init__(self): + super().__init__() self._z_offset = 0 def setZOffset(self, offset): From 89adc4614204998652f26cf573e3329332e596b9 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 18 May 2017 12:46:06 +0200 Subject: [PATCH 37/66] Make limit_to_extruder update properly when value changes happen Contributes to CURA-3738 --- cura/CuraApplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 2a96cc9ab3..2aa49e44ff 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -138,7 +138,7 @@ class CuraApplication(QtApplication): # From which stack the setting would inherit if not defined per object (handled in the engine) # AND for settings which are not settable_per_mesh: # which extruder is the only extruder this setting is obtained from - SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1") + SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1", depends_on = "value") # For settings which are not settable_per_mesh and not settable_per_extruder: # A function which determines the glabel/meshgroup value by looking at the values of the setting in all (used) extruders From 870d6fe76af89dea81ac968fa6851b35fb977042 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 18 May 2017 14:34:00 +0200 Subject: [PATCH 38/66] Use position number to get extruders for overriding in project loading CURA-3756 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 2e7977ecda..4d668b17ac 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -312,11 +312,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader): return WorkspaceReader.PreReadResult.accepted ## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack. - def _overrideExtruderStack(self, global_stack, extruder_index, extruder_file_content): - extruder_index_str = str(extruder_index) + def _overrideExtruderStack(self, global_stack, extruder_file_content): + # get extruder position first + extruder_config = configparser.ConfigParser() + extruder_config.read_string(extruder_file_content) + if not extruder_config.has_option("metadata", "position"): + msg = "Could not find 'metadata/position' in extruder stack file" + Logger.log("e", "Could not find 'metadata/position' in extruder stack file") + raise RuntimeError(msg) + extruder_position = extruder_config.get("metadata", "position") - extruder_stack = global_stack.extruders[extruder_index_str] - old_extruder_stack_id = extruder_stack.getId() + extruder_stack = global_stack.extruders[extruder_position] # override the given extruder stack extruder_stack.deserialize(extruder_file_content) @@ -631,7 +637,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if self._resolve_strategies["machine"] == "override": # NOTE: This is the same code as those in the lower part # deserialize new extruder stack over the current ones - stack = self._overrideExtruderStack(global_stack, index, extruder_file_content) + stack = self._overrideExtruderStack(global_stack, extruder_file_content) elif self._resolve_strategies["machine"] == "new": # create a new extruder stack from this one @@ -662,7 +668,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # No extruder stack with the same ID can be found if self._resolve_strategies["machine"] == "override": # deserialize new extruder stack over the current ones - stack = self._overrideExtruderStack(global_stack, index, extruder_file_content) + stack = self._overrideExtruderStack(global_stack, extruder_file_content) elif self._resolve_strategies["machine"] == "new": # container not found, create a new one From 7e4f983cd61eb2f8d1c2a808b33f1fb7ab3a12c5 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 18 May 2017 15:34:26 +0200 Subject: [PATCH 39/66] Fix support settings that need their inheritance value from different extruders Contributes to CURA-3738 --- resources/definitions/fdmprinter.def.json | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index be283f134f..c377f58b28 100755 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3156,7 +3156,7 @@ "default_value": 0.1, "type": "float", "enabled": "support_enable", - "value": "support_z_distance", + "value": "extruderValue(support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr, 'support_z_distance')", "limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr", "settable_per_mesh": true }, @@ -3168,7 +3168,7 @@ "minimum_value": "0", "maximum_value_warning": "machine_nozzle_size", "default_value": 0.1, - "value": "support_z_distance if support_type == 'everywhere' else 0", + "value": "extruderValue(support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr, 'support_z_distance') if support_type == 'everywhere' else 0", "limit_to_extruder": "support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr", "type": "float", "enabled": "support_enable and resolveOrValue('support_type') == 'everywhere'", @@ -3287,7 +3287,7 @@ "description": "Generate a dense slab of material between the top of support and the model. This will create a skin between the model and support.", "type": "bool", "default_value": false, - "value": "support_interface_enable", + "value": "extruderValue(support_roof_extruder_nr, 'support_interface_enable')", "limit_to_extruder": "support_roof_extruder_nr", "enabled": "support_enable", "settable_per_mesh": true @@ -3298,7 +3298,7 @@ "description": "Generate a dense slab of material between the bottom of the support and the model. This will create a skin between the model and support.", "type": "bool", "default_value": false, - "value": "support_interface_enable", + "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_enable')", "limit_to_extruder": "support_bottom_extruder_nr", "enabled": "support_enable", "settable_per_mesh": true @@ -3330,7 +3330,7 @@ "minimum_value": "0", "minimum_value_warning": "0.2 + layer_height", "maximum_value_warning": "10", - "value": "support_interface_height", + "value": "extruderValue(support_roof_extruder_nr, 'support_interface_height')", "limit_to_extruder": "support_roof_extruder_nr", "enabled": "support_roof_enable and support_enable", "settable_per_mesh": true @@ -3342,7 +3342,7 @@ "unit": "mm", "type": "float", "default_value": 1, - "value": "support_interface_height", + "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_height')", "minimum_value": "0", "minimum_value_warning": "min(0.2 + layer_height, support_bottom_stair_step_height)", "maximum_value_warning": "10", @@ -3390,6 +3390,7 @@ "maximum_value": "100", "limit_to_extruder": "support_roof_extruder_nr", "enabled": "support_roof_enable and support_enable", + "value": "extruderValue(support_roof_extruder_nr, 'support_interface_density')", "settable_per_mesh": false, "settable_per_extruder": true, "children": @@ -3422,6 +3423,7 @@ "maximum_value": "100", "limit_to_extruder": "support_bottom_extruder_nr", "enabled": "support_bottom_enable and support_enable", + "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_density')", "settable_per_mesh": false, "settable_per_extruder": true, "children": @@ -3481,7 +3483,7 @@ "zigzag": "Zig Zag" }, "default_value": "concentric", - "value": "support_interface_pattern", + "value": "extruderValue(support_roof_extruder_nr, 'support_interface_pattern')", "limit_to_extruder": "support_roof_extruder_nr", "enabled": "support_roof_enable and support_enable", "settable_per_mesh": false, @@ -3502,7 +3504,7 @@ "zigzag": "Zig Zag" }, "default_value": "concentric", - "value": "support_interface_pattern", + "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_pattern')", "limit_to_extruder": "support_bottom_extruder_nr", "enabled": "support_bottom_enable and support_enable", "settable_per_mesh": false, From f2f83750c2e277c32bf0a7099d8329dcb84f5207 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 18 May 2017 16:42:11 +0200 Subject: [PATCH 40/66] Type is now always added when deserializing profiles to g-code CURA-3829 --- plugins/GCodeWriter/GCodeWriter.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index ad730fbe2a..47ccc21bcd 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -85,6 +85,7 @@ class GCodeWriter(MeshWriter): for key in instance_container1.getAllKeys(): flat_container.setProperty(key, "value", instance_container1.getProperty(key, "value")) + return flat_container @@ -106,6 +107,9 @@ class GCodeWriter(MeshWriter): return "" flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile) + # If the quality changes is not set, we need to set type manually + if flat_global_container.getMetaDataEntry("type", None) is None: + flat_global_container.addMetaDataEntry("type", "quality_changes") # Ensure that quality_type is set. (Can happen if we have empty quality changes). if flat_global_container.getMetaDataEntry("quality_type", None) is None: @@ -120,6 +124,9 @@ class GCodeWriter(MeshWriter): Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId()) continue flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality) + # If the quality changes is not set, we need to set type manually + if flat_extruder_quality.getMetaDataEntry("type", None) is None: + flat_extruder_quality.addMetaDataEntry("type", "quality_changes") # Ensure that extruder is set. (Can happen if we have empty quality changes). if flat_extruder_quality.getMetaDataEntry("extruder", None) is None: From 593697e0ed5297a50a4fd1c14f976f0b73976f0b Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 May 2017 10:13:12 +0200 Subject: [PATCH 41/66] Fix XML material upgrade CURA-3756 --- cura/CuraApplication.py | 2 +- plugins/XmlMaterialProfile/Upgrader.py | 62 +++++++++++++++++++ .../XmlMaterialProfile/XmlMaterialProfile.py | 22 ++++++- plugins/XmlMaterialProfile/__init__.py | 30 ++++++++- 4 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 plugins/XmlMaterialProfile/Upgrader.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 2aa49e44ff..32243dae67 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -488,7 +488,7 @@ class CuraApplication(QtApplication): self._plugin_registry.loadPlugins() - if self.getBackend() == None: + if self.getBackend() is None: raise RuntimeError("Could not load the backend plugin!") self._plugins_loaded = True diff --git a/plugins/XmlMaterialProfile/Upgrader.py b/plugins/XmlMaterialProfile/Upgrader.py new file mode 100644 index 0000000000..7597d81748 --- /dev/null +++ b/plugins/XmlMaterialProfile/Upgrader.py @@ -0,0 +1,62 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import xml.etree.ElementTree as ET + +from UM.VersionUpgrade import VersionUpgrade + + +class Upgrader(VersionUpgrade): + def getXmlVersion(self, serialized): + data = ET.fromstring(serialized) + + # get format version + version = None + metadata = data.iterfind("./um:metadata/*") + for entry in metadata: + tag_name = entry.tag + if tag_name == "version": + try: + version = int(entry.text) + except Exception as e: + raise ValueError("Invalid version string '%s': %s" % (entry.text, e)) + break + if version is None: + raise RuntimeError("Missing version in metadata") + + # get setting version + if "version" in data.attrib: + setting_version = self._xmlVersionToSettingVersion(data.attrib["version"]) + else: + setting_version = self._xmlVersionToSettingVersion("1.2") + + if version is None: + raise RuntimeError("Missing version in metadata") + + return version * 1000000 + setting_version + + def _xmlVersionToSettingVersion(self, xml_version: str) -> int: + if xml_version == "1.3": + return 1 + return 0 #Older than 1.3. + + def upgradeMaterial(self, serialised, filename): + data = ET.fromstring(serialised) + + # update version + metadata = data.iterfind("./um:metadata/*", {"um": "http://www.ultimaker.com/material"}) + for entry in metadata: + if _tag_without_namespace(entry) == "version": + entry.text = "2" + break + + data.attrib["version"] = "1.3" + + # this makes sure that the XML header states encoding="utf-8" + new_serialised = ET.tostring(data, encoding="utf-8").decode("utf-8") + + return [filename], [new_serialised] + + +def _tag_without_namespace(element): + return element.tag[element.tag.rfind("}") + 1:] diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 7bdfaf404d..70a329ef32 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -14,9 +14,13 @@ from cura.CuraApplication import CuraApplication import UM.Dictionary from UM.Settings.InstanceContainer import InstanceContainer, InvalidInstanceError from UM.Settings.ContainerRegistry import ContainerRegistry +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry + ## Handles serializing and deserializing material containers from an XML file class XmlMaterialProfile(InstanceContainer): + Version = 1 + def __init__(self, container_id, *args, **kwargs): super().__init__(container_id, *args, **kwargs) self._inherited_files = [] @@ -386,11 +390,13 @@ class XmlMaterialProfile(InstanceContainer): self._path = "" def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]: - return "material" + return "materials" def getVersionFromSerialized(self, serialized: str) -> Optional[int]: - version = None data = ET.fromstring(serialized) + + # get format version + version = None metadata = data.iterfind("./um:metadata/*", self.__namespaces) for entry in metadata: tag_name = _tag_without_namespace(entry) @@ -402,7 +408,17 @@ class XmlMaterialProfile(InstanceContainer): break if version is None: raise InvalidInstanceError("Missing version in metadata") - return version + + # get setting version + if "version" in data.attrib: + setting_version = self.xmlVersionToSettingVersion(data.attrib["version"]) + else: + setting_version = self.xmlVersionToSettingVersion("1.2") + + if version is None: + raise InvalidInstanceError("Missing version in metadata") + + return version * 1000000 + setting_version ## Overridden from InstanceContainer def deserialize(self, serialized): diff --git a/plugins/XmlMaterialProfile/__init__.py b/plugins/XmlMaterialProfile/__init__.py index 213b9a358a..73965ac8b8 100644 --- a/plugins/XmlMaterialProfile/__init__.py +++ b/plugins/XmlMaterialProfile/__init__.py @@ -1,11 +1,16 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from . import XmlMaterialProfile +from . import Upgrader from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase from UM.i18n import i18nCatalog + + catalog = i18nCatalog("cura") +upgrader = Upgrader.Upgrader() + def getMetaData(): return { @@ -19,15 +24,36 @@ def getMetaData(): "settings_container": { "type": "material", "mimetype": "application/x-ultimaker-material-profile" + }, + "version_upgrade": { + ("materials", 1000000): ("materials", 1000001, upgrader.upgradeMaterial), + }, + "sources": { + "materials": { + "get_version": upgrader.getXmlVersion, + "location": {"./materials"} + }, } } + def register(app): + # add Mime type mime_type = MimeType( name = "application/x-ultimaker-material-profile", comment = "Ultimaker Material Profile", suffixes = [ "xml.fdm_material" ] ) MimeTypeDatabase.addMimeType(mime_type) - return { "settings_container": XmlMaterialProfile.XmlMaterialProfile("default_xml_material_profile") } + # add upgrade version + from cura.CuraApplication import CuraApplication + from UM.VersionUpgradeManager import VersionUpgradeManager + VersionUpgradeManager.getInstance().setCurrentVersion( + ("materials", XmlMaterialProfile.XmlMaterialProfile.Version * 1000000 + CuraApplication.SettingVersion), + (CuraApplication.ResourceTypes.MaterialInstanceContainer, "application/x-uranium-instancecontainer") + ) + + return {"version_upgrade": upgrader, + "settings_container": XmlMaterialProfile.XmlMaterialProfile("default_xml_material_profile"), + } From 475455d473b5b887e3f7793015218386e432b256 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 19 May 2017 10:39:37 +0200 Subject: [PATCH 42/66] Definition changes are now also upgraded CURA-3837 --- cura/CuraApplication.py | 3 ++- .../VersionUpgrade/VersionUpgrade25to26/__init__.py | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 2aa49e44ff..f944f4e277 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -178,7 +178,8 @@ class CuraApplication(QtApplication): ("machine_stack", ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"), ("extruder_train", ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"), ("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"), - ("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer") + ("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"), + ("definition_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.DefinitionChangesContainer, "application/x-uranium-instancecontainer"), } ) diff --git a/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py b/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py index a24473f65d..5ff589c4e3 100644 --- a/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade25to26/__init__.py @@ -22,9 +22,10 @@ def getMetaData(): ("preferences", 4000000): ("preferences", 4000001, upgrade.upgradePreferences), # NOTE: All the instance containers share the same general/version, so we have to update all of them # if any is updated. - ("quality_changes", 2000000): ("quality_changes", 2000001, upgrade.upgradeInstanceContainer), - ("user", 2000000): ("user", 2000001, upgrade.upgradeInstanceContainer), - ("quality", 2000000): ("quality", 2000001, upgrade.upgradeInstanceContainer), + ("quality_changes", 2000000): ("quality_changes", 2000001, upgrade.upgradeInstanceContainer), + ("user", 2000000): ("user", 2000001, upgrade.upgradeInstanceContainer), + ("quality", 2000000): ("quality", 2000001, upgrade.upgradeInstanceContainer), + ("definition_changes", 2000000): ("definition_changes", 2000001, upgrade.upgradeInstanceContainer), }, "sources": { "quality_changes": { @@ -39,6 +40,10 @@ def getMetaData(): "get_version": upgrade.getCfgVersion, "location": {"./user"} }, + "definition_changes": { + "get_version": upgrade.getCfgVersion, + "location": {"./machine_instances"} + } } } From 70894608613d736555b131da37a2a61dba905e77 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 May 2017 11:05:14 +0200 Subject: [PATCH 43/66] Minor refactoring for XMLMaterial upgrade CURA-3756 --- .../{Upgrader.py => XmlMaterialUpgrader.py} | 2 +- plugins/XmlMaterialProfile/__init__.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename plugins/XmlMaterialProfile/{Upgrader.py => XmlMaterialUpgrader.py} (95%) diff --git a/plugins/XmlMaterialProfile/Upgrader.py b/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py similarity index 95% rename from plugins/XmlMaterialProfile/Upgrader.py rename to plugins/XmlMaterialProfile/XmlMaterialUpgrader.py index 7597d81748..9f5b3cef9d 100644 --- a/plugins/XmlMaterialProfile/Upgrader.py +++ b/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py @@ -6,7 +6,7 @@ import xml.etree.ElementTree as ET from UM.VersionUpgrade import VersionUpgrade -class Upgrader(VersionUpgrade): +class XmlMaterialUpgrader(VersionUpgrade): def getXmlVersion(self, serialized): data = ET.fromstring(serialized) diff --git a/plugins/XmlMaterialProfile/__init__.py b/plugins/XmlMaterialProfile/__init__.py index 73965ac8b8..d98f2efb3d 100644 --- a/plugins/XmlMaterialProfile/__init__.py +++ b/plugins/XmlMaterialProfile/__init__.py @@ -2,14 +2,14 @@ # Cura is released under the terms of the AGPLv3 or higher. from . import XmlMaterialProfile -from . import Upgrader +from . import XmlMaterialUpgrader from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") -upgrader = Upgrader.Upgrader() +upgrader = XmlMaterialUpgrader.XmlMaterialUpgrader() def getMetaData(): @@ -49,7 +49,7 @@ def register(app): # add upgrade version from cura.CuraApplication import CuraApplication from UM.VersionUpgradeManager import VersionUpgradeManager - VersionUpgradeManager.getInstance().setCurrentVersion( + VersionUpgradeManager.getInstance().registerCurrentVersion( ("materials", XmlMaterialProfile.XmlMaterialProfile.Version * 1000000 + CuraApplication.SettingVersion), (CuraApplication.ResourceTypes.MaterialInstanceContainer, "application/x-uranium-instancecontainer") ) From 5a6049ddeab07f5433ebae39cbef6b1ce4d39710 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 May 2017 11:05:59 +0200 Subject: [PATCH 44/66] Change XmlMaterialUpgrader to use UNIX EOLs CURA-3756 --- .../XmlMaterialProfile/XmlMaterialUpgrader.py | 124 +++++++++--------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py b/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py index 9f5b3cef9d..8f1d1e9ec6 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py +++ b/plugins/XmlMaterialProfile/XmlMaterialUpgrader.py @@ -1,62 +1,62 @@ -# Copyright (c) 2017 Ultimaker B.V. -# Cura is released under the terms of the AGPLv3 or higher. - -import xml.etree.ElementTree as ET - -from UM.VersionUpgrade import VersionUpgrade - - -class XmlMaterialUpgrader(VersionUpgrade): - def getXmlVersion(self, serialized): - data = ET.fromstring(serialized) - - # get format version - version = None - metadata = data.iterfind("./um:metadata/*") - for entry in metadata: - tag_name = entry.tag - if tag_name == "version": - try: - version = int(entry.text) - except Exception as e: - raise ValueError("Invalid version string '%s': %s" % (entry.text, e)) - break - if version is None: - raise RuntimeError("Missing version in metadata") - - # get setting version - if "version" in data.attrib: - setting_version = self._xmlVersionToSettingVersion(data.attrib["version"]) - else: - setting_version = self._xmlVersionToSettingVersion("1.2") - - if version is None: - raise RuntimeError("Missing version in metadata") - - return version * 1000000 + setting_version - - def _xmlVersionToSettingVersion(self, xml_version: str) -> int: - if xml_version == "1.3": - return 1 - return 0 #Older than 1.3. - - def upgradeMaterial(self, serialised, filename): - data = ET.fromstring(serialised) - - # update version - metadata = data.iterfind("./um:metadata/*", {"um": "http://www.ultimaker.com/material"}) - for entry in metadata: - if _tag_without_namespace(entry) == "version": - entry.text = "2" - break - - data.attrib["version"] = "1.3" - - # this makes sure that the XML header states encoding="utf-8" - new_serialised = ET.tostring(data, encoding="utf-8").decode("utf-8") - - return [filename], [new_serialised] - - -def _tag_without_namespace(element): - return element.tag[element.tag.rfind("}") + 1:] +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import xml.etree.ElementTree as ET + +from UM.VersionUpgrade import VersionUpgrade + + +class XmlMaterialUpgrader(VersionUpgrade): + def getXmlVersion(self, serialized): + data = ET.fromstring(serialized) + + # get format version + version = None + metadata = data.iterfind("./um:metadata/*") + for entry in metadata: + tag_name = entry.tag + if tag_name == "version": + try: + version = int(entry.text) + except Exception as e: + raise ValueError("Invalid version string '%s': %s" % (entry.text, e)) + break + if version is None: + raise RuntimeError("Missing version in metadata") + + # get setting version + if "version" in data.attrib: + setting_version = self._xmlVersionToSettingVersion(data.attrib["version"]) + else: + setting_version = self._xmlVersionToSettingVersion("1.2") + + if version is None: + raise RuntimeError("Missing version in metadata") + + return version * 1000000 + setting_version + + def _xmlVersionToSettingVersion(self, xml_version: str) -> int: + if xml_version == "1.3": + return 1 + return 0 #Older than 1.3. + + def upgradeMaterial(self, serialised, filename): + data = ET.fromstring(serialised) + + # update version + metadata = data.iterfind("./um:metadata/*", {"um": "http://www.ultimaker.com/material"}) + for entry in metadata: + if _tag_without_namespace(entry) == "version": + entry.text = "2" + break + + data.attrib["version"] = "1.3" + + # this makes sure that the XML header states encoding="utf-8" + new_serialised = ET.tostring(data, encoding="utf-8").decode("utf-8") + + return [filename], [new_serialised] + + +def _tag_without_namespace(element): + return element.tag[element.tag.rfind("}") + 1:] From bd361e500c9b4dac5608ff03b3cdb8e8b0dfc9c0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 19 May 2017 12:30:00 +0200 Subject: [PATCH 45/66] Don't keep a copy of currently connected printer This QML file was holding a copy of the currently connected printer, stored in connectingToPrinter. This copy got out of sync when you load a project file which had a different connected printer. Instead, ask the manager for the currently connected printer key every time you click the button. Contributes to issue CURA-3839. --- plugins/UM3NetworkPrinting/DiscoverUM3Action.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml index 27271f0d15..f9af47fc7a 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml @@ -12,7 +12,6 @@ Cura.MachineAction anchors.fill: parent; property var selectedPrinter: null property bool completeProperties: true - property var connectingToPrinter: null Connections { @@ -33,9 +32,8 @@ Cura.MachineAction if(base.selectedPrinter && base.completeProperties) { var printerKey = base.selectedPrinter.getKey() - if(connectingToPrinter != printerKey) { - // prevent an infinite loop - connectingToPrinter = printerKey; + if(manager.getStoredKey() != printerKey) + { manager.setKey(printerKey); completed(); } From 75b3f083186add8eed681b701c7da920a23fb3e7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 19 May 2017 13:09:41 +0200 Subject: [PATCH 46/66] Removed unneeded logging THis caused a massive logging spam --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 70a329ef32..071548e7e7 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -560,7 +560,7 @@ class XmlMaterialProfile(InstanceContainer): variant_containers = ContainerRegistry.getInstance().findInstanceContainers(definition = definition.id, name = hotend_id) if not variant_containers: - Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id) + #Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id) continue hotend_compatibility = machine_compatibility From 9ff7852055e81bb50a5615f2b71098e562165088 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 May 2017 13:37:04 +0200 Subject: [PATCH 47/66] Only use limit_to_extruder when the extruder exists CURA-3756 --- cura/Settings/ExtruderStack.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index 70d2b0f6e4..002778038b 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -64,9 +64,10 @@ class ExtruderStack(CuraContainerStack): limit_to_extruder = super().getProperty(key, "limit_to_extruder") if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder): - result = self.getNextStack().extruders[str(limit_to_extruder)].getProperty(key, property_name) - if result is not None: - return result + if str(limit_to_extruder) in self.getNextStack().extruders: + result = self.getNextStack().extruders[str(limit_to_extruder)].getProperty(key, property_name) + if result is not None: + return result return super().getProperty(key, property_name) From 4b826b747be24b1a5bc3c94b2d7537568a114124 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 19 May 2017 13:54:31 +0200 Subject: [PATCH 48/66] Added exception handling to XML parsing CURA-3843 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 071548e7e7..bddb801030 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -425,7 +425,12 @@ class XmlMaterialProfile(InstanceContainer): # update the serialized data first from UM.Settings.Interfaces import ContainerInterface serialized = ContainerInterface.deserialize(self, serialized) - data = ET.fromstring(serialized) + + try: + data = ET.fromstring(serialized) + except: + Logger.logException("e", "An exception occured while parsing the material profile") + return # Reset previous metadata self.clearData() # Ensure any previous data is gone. From 81d03a16831534421a24931a7bfe521a069e657f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 19 May 2017 14:00:40 +0200 Subject: [PATCH 49/66] Fixed order of pva profiles CURA-3845 --- .../quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg index 5bc96c8f08..b244b3b2da 100644 --- a/resources/quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg @@ -4,7 +4,7 @@ name = Extra Fine definition = ultimaker3 [metadata] -weight = 0 +weight = 1 type = quality quality_type = high material = generic_pva_ultimaker3_BB_0.4 From 76a9a9a9ab9e39ba1dc047200ce13b3e6fbef5cc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 19 May 2017 14:39:22 +0200 Subject: [PATCH 50/66] Erase changed hotends/materials after applying changes Otherwise we may only change one of the extruders next time, and it'll still have the other change in this dictionary from the previous time we are syncing from the printer. Contributes to issue CURA-3788. --- cura/Settings/MachineManager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index ae9e9d329b..9564b8a5b0 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -220,6 +220,7 @@ class MachineManager(QObject): if old_index is not None: extruder_manager.setActiveExtruderIndex(old_index) + self._auto_materials_changed = {} #Processed all of them now. def _autoUpdateHotends(self): extruder_manager = ExtruderManager.getInstance() @@ -236,6 +237,7 @@ class MachineManager(QObject): if old_index is not None: extruder_manager.setActiveExtruderIndex(old_index) + self._auto_hotends_changed = {} #Processed all of them now. def _onGlobalContainerChanged(self): if self._global_container_stack: From 430508c18583844b783b136fa4f6f7a0e9fffab9 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 May 2017 14:59:22 +0200 Subject: [PATCH 51/66] Dump stacktrace for all threads in CrashReport CURA-3836 This helps debugging issues like CURA-3836 --- cura/CrashHandler.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 4048b409a7..7008ba64d2 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -2,6 +2,9 @@ import sys import platform import traceback import webbrowser +import faulthandler +import tempfile +import os import urllib from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QCoreApplication @@ -91,6 +94,17 @@ def show(exception_type, value, tb): crash_info = "Version: {0}\nPlatform: {1}\nQt: {2}\nPyQt: {3}\n\nException:\n{4}" crash_info = crash_info.format(version, platform.platform(), QT_VERSION_STR, PYQT_VERSION_STR, trace) + tmp_file_fd, tmp_file_path = tempfile.mkstemp(prefix = "cura-crash", text = True) + os.close(tmp_file_fd) + with open(tmp_file_path, "w") as f: + faulthandler.dump_traceback(f, all_threads=True) + with open(tmp_file_path, "r") as f: + data = f.read() + + msg = "-------------------------\n" + msg += data + crash_info += "\n\n" + msg + textarea.setText(crash_info) buttons = QDialogButtonBox(QDialogButtonBox.Close, dialog) From a386e6d867bb9216413e532c715479b9b7ff02a1 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 May 2017 14:59:22 +0200 Subject: [PATCH 52/66] Dump stacktrace for all threads in CrashReport CURA-3836 This helps debugging issues like CURA-3836 --- cura/CrashHandler.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 4048b409a7..7008ba64d2 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -2,6 +2,9 @@ import sys import platform import traceback import webbrowser +import faulthandler +import tempfile +import os import urllib from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QCoreApplication @@ -91,6 +94,17 @@ def show(exception_type, value, tb): crash_info = "Version: {0}\nPlatform: {1}\nQt: {2}\nPyQt: {3}\n\nException:\n{4}" crash_info = crash_info.format(version, platform.platform(), QT_VERSION_STR, PYQT_VERSION_STR, trace) + tmp_file_fd, tmp_file_path = tempfile.mkstemp(prefix = "cura-crash", text = True) + os.close(tmp_file_fd) + with open(tmp_file_path, "w") as f: + faulthandler.dump_traceback(f, all_threads=True) + with open(tmp_file_path, "r") as f: + data = f.read() + + msg = "-------------------------\n" + msg += data + crash_info += "\n\n" + msg + textarea.setText(crash_info) buttons = QDialogButtonBox(QDialogButtonBox.Close, dialog) From 862cbe0211bf1146144e3b95eaee80da771fb2c2 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 May 2017 15:23:28 +0200 Subject: [PATCH 53/66] Check global_stack before updating in SettingInheritanceManager CURA-3756 --- cura/Settings/SettingInheritanceManager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index ff0d1d81c0..7e9bc2aa0e 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -198,6 +198,10 @@ class SettingInheritanceManager(QObject): def _update(self): self._settings_with_inheritance_warning = [] # Reset previous data. + # Make sure that the GlobalStack is not None. sometimes the globalContainerChanged signal gets here late. + if self._global_container_stack is None: + return + # Check all setting keys that we know of and see if they are overridden. for setting_key in self._global_container_stack.getAllKeys(): override = self._settingIsOverwritingInheritance(setting_key) From df36c7a18b77a02e74db85d0ea60abe3e07fc504 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 19 May 2017 15:23:28 +0200 Subject: [PATCH 54/66] Check global_stack before updating in SettingInheritanceManager CURA-3756 --- cura/Settings/SettingInheritanceManager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index ff0d1d81c0..7e9bc2aa0e 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -198,6 +198,10 @@ class SettingInheritanceManager(QObject): def _update(self): self._settings_with_inheritance_warning = [] # Reset previous data. + # Make sure that the GlobalStack is not None. sometimes the globalContainerChanged signal gets here late. + if self._global_container_stack is None: + return + # Check all setting keys that we know of and see if they are overridden. for setting_key in self._global_container_stack.getAllKeys(): override = self._settingIsOverwritingInheritance(setting_key) From 600428a0a304cfc3980f07b4872668302ebd6a31 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 19 May 2017 15:41:00 +0200 Subject: [PATCH 55/66] Disable Z-hop for all TPU profiles This gives better results, and we don't want to offer dual-extrusion options for TPU at first anyway. --- resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg | 2 +- resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg | 2 +- .../quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg | 2 +- resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg | 1 + .../quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg | 1 + .../quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg | 1 + 6 files changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg index 1982ba2c11..35cb249351 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg @@ -44,7 +44,7 @@ retraction_count_max = 12 retraction_extra_prime_amount = 0.8 retraction_extrusion_window = 1 retraction_hop = 2 -retraction_hop_enabled = True +retraction_hop_enabled = False retraction_hop_only_when_collides = True retraction_min_travel = 0.8 retraction_prime_speed = 15 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg index 8e65408ea4..95f1dfed7d 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg @@ -45,7 +45,7 @@ retraction_count_max = 12 retraction_extra_prime_amount = 0.8 retraction_extrusion_window = 1 retraction_hop = 2 -retraction_hop_enabled = True +retraction_hop_enabled = False retraction_hop_only_when_collides = True retraction_min_travel = 0.8 retraction_prime_speed = 15 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg index 4ab5a3072f..1b22bf9f30 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg @@ -42,7 +42,7 @@ retraction_count_max = 12 retraction_extra_prime_amount = 0.8 retraction_extrusion_window = 1 retraction_hop = 2 -retraction_hop_enabled = True +retraction_hop_enabled = False retraction_hop_only_when_collides = True retraction_min_travel = 0.8 retraction_prime_speed = 15 diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg index eefaeaaa58..9a68db2f11 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg @@ -38,6 +38,7 @@ retract_at_layer_change = False retraction_count_max = 12 retraction_extra_prime_amount = 0.5 retraction_hop = 0.5 +retraction_hop_enabled = False retraction_hop_only_when_collides = False retraction_min_travel = 0.8 retraction_prime_speed = 15 diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg index bd527242d8..f35e6f42ed 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg @@ -39,6 +39,7 @@ retract_at_layer_change = False retraction_count_max = 12 retraction_extra_prime_amount = 0.5 retraction_hop = 0.5 +retraction_hop_enabled = False retraction_hop_only_when_collides = False retraction_min_travel = 0.8 retraction_prime_speed = 15 diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg index 054309c705..6d94b9efff 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg @@ -38,6 +38,7 @@ retract_at_layer_change = False retraction_count_max = 12 retraction_extra_prime_amount = 0.5 retraction_hop = 0.5 +retraction_hop_enabled = False retraction_hop_only_when_collides = False retraction_min_travel = 0.8 retraction_prime_speed = 15 From 39986baa2f50fac145e93baaa1817f7e828c5d28 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 19 May 2017 15:45:34 +0200 Subject: [PATCH 56/66] Remove redundant travel avoid distance settings The UM3 definition sets the setting to 3mm. Nothing else sets it to another value except FDM Printer. I left the UM3 setting in and removed all travel_avoid_distance settings in the UM3 profiles. --- resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg | 1 - resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg | 1 - .../quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg | 1 - .../quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg | 1 - resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg | 1 - resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg | 1 - resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg | 1 - .../quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg | 1 - resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg | 1 - resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg | 1 - .../quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg | 1 - resources/variants/ultimaker3_aa0.8.inst.cfg | 1 - resources/variants/ultimaker3_bb0.8.inst.cfg | 1 - resources/variants/ultimaker3_extended_aa0.8.inst.cfg | 1 - resources/variants/ultimaker3_extended_bb0.8.inst.cfg | 1 - 15 files changed, 15 deletions(-) diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg index 2e99229d95..44cd4540d5 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg @@ -45,6 +45,5 @@ speed_wall = =math.ceil(speed_print * 50 / 50) speed_wall_0 = =math.ceil(speed_wall * 40 / 50) support_bottom_distance = =support_z_distance support_z_distance = =layer_height -travel_avoid_distance = 3 wall_0_inset = 0 wall_thickness = 1 diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg index 57f784a923..a0d09c97c2 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg @@ -45,5 +45,4 @@ speed_wall = =math.ceil(speed_print * 45 / 45) speed_wall_0 = =math.ceil(speed_wall * 35 / 45) support_bottom_distance = =support_z_distance support_z_distance = =layer_height -travel_avoid_distance = 3 wall_0_inset = 0 diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg index 91daa94988..ebd7cd2d07 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg @@ -47,5 +47,4 @@ speed_wall = =math.ceil(speed_print * 35 / 40) speed_wall_0 = =math.ceil(speed_wall * 30 / 35) support_bottom_distance = =support_z_distance support_z_distance = =layer_height -travel_avoid_distance = 3 wall_0_inset = 0 diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg index c11ed30ed4..96cbc59a48 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg @@ -46,5 +46,4 @@ speed_wall = =math.ceil(speed_print * 35 / 40) speed_wall_0 = =math.ceil(speed_wall * 30 / 35) support_bottom_distance = =support_z_distance support_z_distance = =layer_height -travel_avoid_distance = 3 wall_0_inset = 0 diff --git a/resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg index a7d3f76ac6..da9abb519d 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg @@ -59,7 +59,6 @@ support_interface_pattern = lines switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =round(line_width * 0.4 / 0.35, 2) wall_thickness = 1.2 diff --git a/resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg index 7fdcb28d82..01db3adb1f 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg @@ -58,7 +58,6 @@ support_interface_pattern = lines switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =round(line_width * 0.4 / 0.35, 2) wall_thickness = 1.2 diff --git a/resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg index 72e9d918c9..d2dc42b015 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg @@ -59,7 +59,6 @@ support_interface_pattern = lines switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =round(line_width * 0.4 / 0.35, 2) wall_thickness = 1.2 diff --git a/resources/quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg index 6a271f548b..0580271647 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg @@ -56,7 +56,6 @@ support_interface_pattern = lines switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =round(line_width * 0.4 / 0.35, 2) wall_thickness = 1.2 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg index 35cb249351..6e7ca0f814 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg @@ -60,7 +60,6 @@ switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 top_bottom_thickness = 0.7 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =line_width wall_thickness = 0.76 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg index 95f1dfed7d..afef11d58e 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg @@ -61,7 +61,6 @@ switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 top_bottom_thickness = 0.7 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =line_width wall_thickness = 0.76 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg index 1b22bf9f30..6b5f892c5f 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg @@ -58,7 +58,6 @@ switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 top_bottom_thickness = 0.7 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =line_width wall_thickness = 0.76 diff --git a/resources/variants/ultimaker3_aa0.8.inst.cfg b/resources/variants/ultimaker3_aa0.8.inst.cfg index e0486289a1..e94177abc3 100644 --- a/resources/variants/ultimaker3_aa0.8.inst.cfg +++ b/resources/variants/ultimaker3_aa0.8.inst.cfg @@ -60,7 +60,6 @@ support_z_distance = =layer_height * 2 switch_extruder_prime_speed = 20 switch_extruder_retraction_amount = 16.5 top_bottom_thickness = 1.4 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =wall_line_width wall_thickness = 2 diff --git a/resources/variants/ultimaker3_bb0.8.inst.cfg b/resources/variants/ultimaker3_bb0.8.inst.cfg index 4ba2ca8422..aa2042b271 100644 --- a/resources/variants/ultimaker3_bb0.8.inst.cfg +++ b/resources/variants/ultimaker3_bb0.8.inst.cfg @@ -81,7 +81,6 @@ support_z_distance = 0 switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 12 top_bottom_thickness = 1 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =wall_line_width wall_thickness = 1 diff --git a/resources/variants/ultimaker3_extended_aa0.8.inst.cfg b/resources/variants/ultimaker3_extended_aa0.8.inst.cfg index 0fd110c76c..47b0359df7 100644 --- a/resources/variants/ultimaker3_extended_aa0.8.inst.cfg +++ b/resources/variants/ultimaker3_extended_aa0.8.inst.cfg @@ -60,7 +60,6 @@ support_z_distance = =layer_height * 2 switch_extruder_prime_speed = 20 switch_extruder_retraction_amount = 16.5 top_bottom_thickness = 1.4 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =wall_line_width wall_thickness = 2 diff --git a/resources/variants/ultimaker3_extended_bb0.8.inst.cfg b/resources/variants/ultimaker3_extended_bb0.8.inst.cfg index 2e6292ed08..c982d207b0 100644 --- a/resources/variants/ultimaker3_extended_bb0.8.inst.cfg +++ b/resources/variants/ultimaker3_extended_bb0.8.inst.cfg @@ -81,7 +81,6 @@ support_z_distance = 0 switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 12 top_bottom_thickness = 1 -travel_avoid_distance = 3 wall_0_inset = 0 wall_line_width_x = =wall_line_width wall_thickness = 1 From 1db6e354b536622ef6b6d4c27425a9d8e21e9998 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 19 May 2017 15:47:55 +0200 Subject: [PATCH 57/66] Set travel avoid distance for TPU to 0.5mm With TPU the material gets dragged along a lot when moving outside of the object. We want to reduce that. --- resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg | 1 + resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg | 1 + .../quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg | 1 + resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg | 1 + .../quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg | 1 + .../quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg | 1 + 6 files changed, 6 insertions(+) diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg index 6e7ca0f814..410f5be475 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg @@ -60,6 +60,7 @@ switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 top_bottom_thickness = 0.7 +travel_avoid_distance = 0.5 wall_0_inset = 0 wall_line_width_x = =line_width wall_thickness = 0.76 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg index afef11d58e..97b339d642 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg @@ -61,6 +61,7 @@ switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 top_bottom_thickness = 0.7 +travel_avoid_distance = 0.5 wall_0_inset = 0 wall_line_width_x = =line_width wall_thickness = 0.76 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg index 6b5f892c5f..c49edc8c12 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg @@ -58,6 +58,7 @@ switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 top_bottom_thickness = 0.7 +travel_avoid_distance = 0.5 wall_0_inset = 0 wall_line_width_x = =line_width wall_thickness = 0.76 diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg index 9a68db2f11..e14ce8d2f9 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg @@ -57,6 +57,7 @@ switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 45 top_bottom_thickness = 1.2 +travel_avoid_distance = 0.5 travel_compensate_overlapping_walls_0_enabled = False wall_0_wipe_dist = =line_width * 2 wall_line_width_x = =round(line_width * 0.6 / 0.8, 2) diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg index f35e6f42ed..6bde0b3f31 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg @@ -58,6 +58,7 @@ switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 45 top_bottom_thickness = 1.2 +travel_avoid_distance = 0.5 travel_compensate_overlapping_walls_0_enabled = False wall_0_wipe_dist = =line_width * 2 wall_line_width_x = =round(line_width * 0.6 / 0.8, 2) diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg index 6d94b9efff..c2c395a670 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg @@ -57,6 +57,7 @@ switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 45 top_bottom_thickness = 1.2 +travel_avoid_distance = 0.5 travel_compensate_overlapping_walls_0_enabled = False wall_0_wipe_dist = =line_width * 2 wall_line_width_x = =round(line_width * 0.6 / 0.8, 2) From 07f5b9b5bb93f3445a8a6200c214c218e0ca7b3a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 19 May 2017 15:55:39 +0200 Subject: [PATCH 58/66] Set TPU skin overlap to 5% For the 0.8mm profiles the variants specify 5%, so we remove it from those profiles. The 0.4mm variants specify 15% so for those we need to override it. --- resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg | 1 + resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg | 1 + .../quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg | 1 + resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg | 1 - .../quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg | 1 - .../quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg | 1 - 6 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg index 410f5be475..3f815fac3d 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg @@ -56,6 +56,7 @@ speed_travel = 300 speed_wall = =math.ceil(speed_print * 25 / 25) speed_wall_0 = =math.ceil(speed_wall * 25 / 25) support_angle = 50 +skin_overlap = 5 switch_extruder_prime_speed = 15 switch_extruder_retraction_amount = 20 switch_extruder_retraction_speeds = 35 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg index 97b339d642..e45ec15dec 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg @@ -49,6 +49,7 @@ retraction_hop_enabled = False retraction_hop_only_when_collides = True retraction_min_travel = 0.8 retraction_prime_speed = 15 +skin_overlap = 5 speed_equalize_flow_enabled = True speed_layer_0 = 18 speed_print = 25 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg index c49edc8c12..707e535947 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg @@ -46,6 +46,7 @@ retraction_hop_enabled = False retraction_hop_only_when_collides = True retraction_min_travel = 0.8 retraction_prime_speed = 15 +skin_overlap = 5 speed_equalize_flow_enabled = True speed_layer_0 = 18 speed_print = 25 diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg index e14ce8d2f9..41ac43a840 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg @@ -43,7 +43,6 @@ retraction_hop_only_when_collides = False retraction_min_travel = 0.8 retraction_prime_speed = 15 skin_line_width = =round(line_width * 0.78 / 0.8, 2) -skin_overlap = 15 speed_print = 30 speed_topbottom = =math.ceil(speed_print * 25 / 30) speed_travel = 300 diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg index 6bde0b3f31..66c0281b78 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg @@ -44,7 +44,6 @@ retraction_hop_only_when_collides = False retraction_min_travel = 0.8 retraction_prime_speed = 15 skin_line_width = =round(line_width * 0.78 / 0.8, 2) -skin_overlap = 15 speed_print = 30 speed_topbottom = =math.ceil(speed_print * 20 / 30) speed_travel = 300 diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg index c2c395a670..1274f43eea 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg @@ -43,7 +43,6 @@ retraction_hop_only_when_collides = False retraction_min_travel = 0.8 retraction_prime_speed = 15 skin_line_width = =round(line_width * 0.78 / 0.8, 2) -skin_overlap = 15 speed_print = 30 speed_topbottom = =math.ceil(speed_print * 23 / 30) speed_travel = 300 From 4d853d5d520099a9b7108f77b1b7f7b53d61a3f0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 May 2017 13:22:03 +0200 Subject: [PATCH 59/66] Build volume no longer gives issue if -1 is provided as an int CURA-3814 --- cura/BuildVolume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index c87e07a974..290b8ee2de 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -871,7 +871,7 @@ class BuildVolume(SceneNode): else: extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value") - if extruder_index == "-1": # If extruder index is -1 use global instead + if str(extruder_index) == "-1": # If extruder index is -1 use global instead stack = self._global_container_stack else: extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)] From 20cb3ea01f99c20bf28b89e70b0456f62b190773 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 May 2017 13:24:41 +0200 Subject: [PATCH 60/66] Added "optional_extruder" setting type CURA-3814 --- cura/CuraApplication.py | 1 + cura/Settings/ExtrudersModel.py | 24 +++ .../qml/Settings/SettingOptionalExtruder.qml | 153 ++++++++++++++++++ resources/qml/Settings/SettingView.qml | 4 +- 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 resources/qml/Settings/SettingOptionalExtruder.qml diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index ba42dd2aec..b5aecb9b56 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -145,6 +145,7 @@ class CuraApplication(QtApplication): SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None, depends_on = "value") SettingDefinition.addSettingType("extruder", None, str, Validator) + SettingDefinition.addSettingType("optional_extruder", None, str, None) SettingDefinition.addSettingType("[int]", None, str, None) diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 7491f8aa93..fadc259f34 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -67,6 +67,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self._simple_names = False self._active_extruder_stack = None + self._use_optional_extruder = False #Listen to changes. Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders) @@ -89,6 +90,18 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): def addGlobal(self): return self._add_global + useOptionalExtruderChanged = pyqtSignal() + + def setUseOptionalExtruder(self, use_optional_extruder): + if use_optional_extruder != self._use_optional_extruder: + self._use_optional_extruder = use_optional_extruder + self.useOptionalExtruderChanged.emit() + self._updateExtruders() + + @pyqtProperty(bool, fset = setUseOptionalExtruder, notify = useOptionalExtruderChanged) + def useOptionalExtruder(self): + return self._use_optional_extruder + ## Set the simpleNames property. def setSimpleNames(self, simple_names): if simple_names != self._simple_names: @@ -184,5 +197,16 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): if changed: items.sort(key = lambda i: i["index"]) + # We need optional extruder to be last, so add it after we do sorting. + # This way we can simply intrepret the -1 of the index as the last item (which it now always is) + if self._use_optional_extruder: + item = { + "id": "zomg", + "name": "Not overridden", + "color": self.defaultColors[0], + "index": -1, + "definition": "" + } + items.append(item) self.setItems(items) self.modelChanged.emit() diff --git a/resources/qml/Settings/SettingOptionalExtruder.qml b/resources/qml/Settings/SettingOptionalExtruder.qml new file mode 100644 index 0000000000..07b15e404d --- /dev/null +++ b/resources/qml/Settings/SettingOptionalExtruder.qml @@ -0,0 +1,153 @@ +// Copyright (c) 2016 Ultimaker B.V. +// Uranium is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.1 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 + +import UM 1.1 as UM +import Cura 1.0 as Cura + +SettingItem +{ + id: base + + contents: ComboBox + { + id: control + anchors.fill: parent + + model: Cura.ExtrudersModel + { + onModelChanged: control.color = getItem(control.currentIndex).color + useOptionalExtruder: true + } + + textRole: "name" + + onActivated: + { + forceActiveFocus(); + propertyProvider.setPropertyValue("value", model.getItem(index).index); + } + + Binding + { + target: control + property: "currentIndex" + value: + { + if(propertyProvider.properties.value == -1) + { + return control.model.items.length - 1 + } + return propertyProvider.properties.value + } + // Sometimes when the value is already changed, the model is still being built. + // The when clause ensures that the current index is not updated when this happens. + when: control.model.items.length > 0 + } + + MouseArea + { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: wheel.accepted = true; + } + + property string color: "#fff" + + Binding + { + // We override the color property's value when the ExtruderModel changes. So we need to use an + // explicit binding here otherwise we do not handle value changes after the model changes. + target: control + property: "color" + value: control.currentText != "" ? control.model.getItem(control.currentIndex).color : "" + } + + style: ComboBoxStyle + { + background: Rectangle + { + color: + { + if (!enabled) + { + return UM.Theme.getColor("setting_control_disabled"); + } + if(control.hovered || base.activeFocus) + { + return UM.Theme.getColor("setting_control_highlight"); + } + else + { + return UM.Theme.getColor("setting_control"); + } + } + border.width: UM.Theme.getSize("default_lining").width + border.color: + { + if(!enabled) + { + return UM.Theme.getColor("setting_control_disabled_border"); + } + if(control.hovered || base.activeFocus) + { + UM.Theme.getColor("setting_control_border_highlight") + } + + return UM.Theme.getColor("setting_control_border") + } + } + label: Item + { + Rectangle + { + id: swatch + height: UM.Theme.getSize("setting_control").height / 2 + width: height + + anchors.verticalCenter: parent.verticalCenter + + border.width: UM.Theme.getSize("default_lining").width + border.color: enabled ? UM.Theme.getColor("setting_control_border") : UM.Theme.getColor("setting_control_disabled_border") + + color: control.color + } + Label + { + anchors + { + left: swatch.right; + right: arrow.left; + verticalCenter: parent.verticalCenter + margins: UM.Theme.getSize("default_lining").width + } + width: parent.width - swatch.width; + + text: control.currentText + font: UM.Theme.getFont("default") + color: enabled ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text") + + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + UM.RecolorImage + { + id: arrow + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + source: UM.Theme.getIcon("arrow_bottom") + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + sourceSize.width: width + 5 + sourceSize.height: width + 5 + + color: UM.Theme.getColor("setting_control_text") + } + } + } + } +} diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 85d19b0cf0..7cb45ff75f 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -195,7 +195,7 @@ Item //Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989 //In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes, //causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely. - asynchronous: model.type != "enum" && model.type != "extruder" + asynchronous: model.type != "enum" && model.type != "extruder" && model.type != "optional_extruder" active: model.type != undefined source: @@ -218,6 +218,8 @@ Item return "SettingTextField.qml" case "category": return "SettingCategory.qml" + case "optional_extruder": + return "SettingOptionalExtruder.qml" default: return "SettingUnknown.qml" } From b69ced5766b47e9cb005de9e1ad8931082e297c9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 May 2017 13:45:28 +0200 Subject: [PATCH 61/66] Set color of "Not Overriden" option to white CURA-3814 --- cura/Settings/ExtrudersModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index fadc259f34..4b36e79889 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -203,7 +203,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): item = { "id": "zomg", "name": "Not overridden", - "color": self.defaultColors[0], + "color": "#ffffff", "index": -1, "definition": "" } From 8e5cfd9566f89e85909158a003cf6b874384a688 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 22 May 2017 13:55:43 +0200 Subject: [PATCH 62/66] Add last missing settings that should still use extruderValue Contributes to CURA-3738 --- resources/definitions/fdmprinter.def.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 5f27f161c7..bea23144c1 100755 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -773,7 +773,7 @@ "type": "float", "enabled": "support_enable and support_roof_enable", "limit_to_extruder": "support_roof_extruder_nr", - "value": "support_interface_line_width", + "value": "extruderValue(support_roof_extruder_nr, 'support_interface_line_width')", "settable_per_mesh": false, "settable_per_extruder": true }, @@ -789,7 +789,7 @@ "type": "float", "enabled": "support_enable and support_bottom_enable", "limit_to_extruder": "support_bottom_extruder_nr", - "value": "support_interface_line_width", + "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_line_width')", "settable_per_mesh": false, "settable_per_extruder": true } @@ -1972,7 +1972,7 @@ "maximum_value_warning": "150", "enabled": "support_roof_enable and support_enable", "limit_to_extruder": "support_roof_extruder_nr", - "value": "speed_support_interface", + "value": "extruderValue(support_roof_extruder_nr, 'speed_support_interface')", "settable_per_mesh": false, "settable_per_extruder": true }, @@ -1988,7 +1988,7 @@ "maximum_value_warning": "150", "enabled": "support_bottom_enable and support_enable", "limit_to_extruder": "support_bottom_extruder_nr", - "value": "speed_support_interface", + "value": "extruderValue(support_bottom_extruder_nr, 'speed_support_interface')", "settable_per_mesh": false, "settable_per_extruder": true } @@ -2288,11 +2288,11 @@ "unit": "mm/s²", "type": "float", "default_value": 3000, - "value": "acceleration_support_interface", + "value": "extruderValue(support_roof_extruder_nr, 'acceleration_support_interface')", "minimum_value": "0.1", "minimum_value_warning": "100", "maximum_value_warning": "10000", - "enabled": "resolveOrValue('acceleration_enabled') and support_roof_enable and support_enable", + "enabled": "acceleration_enabled and support_roof_enable and support_enable", "limit_to_extruder": "support_roof_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true @@ -2304,11 +2304,11 @@ "unit": "mm/s²", "type": "float", "default_value": 3000, - "value": "acceleration_support_interface", + "value": "extruderValue(support_bottom_extruder_nr, 'acceleration_support_interface')", "minimum_value": "0.1", "minimum_value_warning": "100", "maximum_value_warning": "10000", - "enabled": "resolveOrValue('acceleration_enabled') and support_bottom_enable and support_enable", + "enabled": "acceleration_enabled and support_bottom_enable and support_enable", "limit_to_extruder": "support_bottom_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true @@ -2553,7 +2553,7 @@ "unit": "mm/s", "type": "float", "default_value": 20, - "value": "jerk_support_interface", + "value": "extruderValue(support_roof_extruder_nr, 'jerk_support_interface')", "minimum_value": "0.1", "maximum_value_warning": "50", "enabled": "resolveOrValue('jerk_enabled') and support_roof_enable and support_enable", @@ -2568,7 +2568,7 @@ "unit": "mm/s", "type": "float", "default_value": 20, - "value": "jerk_support_interface", + "value": "extruderValue(support_roof_extruder_nr, 'jerk_support_interface')", "minimum_value": "0.1", "maximum_value_warning": "50", "enabled": "resolveOrValue('jerk_enabled') and support_bottom_enable and support_enable", From 6315947156c0abfc01e83abac318ba62c80e6e7b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 22 May 2017 12:48:40 +0200 Subject: [PATCH 63/66] Modify parameter types from DefinitionContainer to DefinitionContainerInterface It may also be any other class that implements the Definition Container Interface. Not really related to CURA-3803 but I'm putting it there anyway as I found it during that development. --- cura/Settings/ExtruderManager.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index bab4aa2d9e..ce6fd47de6 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -15,7 +15,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.SettingFunction import SettingFunction from UM.Settings.ContainerStack import ContainerStack -from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.Interfaces import DefinitionContainerInterface from typing import Optional, List, TYPE_CHECKING, Union if TYPE_CHECKING: @@ -74,7 +74,7 @@ class ExtruderManager(QObject): except KeyError: return 0 - @pyqtProperty("QVariantMap", notify=extrudersChanged) + @pyqtProperty("QVariantMap", notify = extrudersChanged) def extruderIds(self): map = {} global_stack_id = Application.getInstance().getGlobalContainerStack().getId() @@ -203,7 +203,7 @@ class ExtruderManager(QObject): # \param machine_definition The machine definition to add the extruders for. # \param machine_id The machine_id to add the extruders for. @deprecated("Use CuraStackBuilder", "2.6") - def addMachineExtruders(self, machine_definition: DefinitionContainer, machine_id: str) -> None: + def addMachineExtruders(self, machine_definition: DefinitionContainerInterface, machine_id: str) -> None: changed = False machine_definition_id = machine_definition.getId() if machine_id not in self._extruder_trains: @@ -263,7 +263,7 @@ class ExtruderManager(QObject): # \param position The position of this extruder train in the extruder slots of the machine. # \param machine_id The id of the "global" stack this extruder is linked to. @deprecated("Use CuraStackBuilder::createExtruderStack", "2.6") - def createExtruderTrain(self, extruder_definition: DefinitionContainer, machine_definition: DefinitionContainer, + def createExtruderTrain(self, extruder_definition: DefinitionContainerInterface, machine_definition: DefinitionContainerInterface, position, machine_id: str) -> None: # Cache some things. container_registry = ContainerRegistry.getInstance() @@ -525,7 +525,7 @@ class ExtruderManager(QObject): # # This is exposed to SettingFunction so it can be used in value functions. # - # \param key The key of the setting to retieve values for. + # \param key The key of the setting to retrieve values for. # # \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list. # If no extruder has the value, the list will contain the global value. From 5ae02fb4c6a09145cca3fd3014399f8b486fb524 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 22 May 2017 12:50:53 +0200 Subject: [PATCH 64/66] Listen to changes on all extruders Not just the active extruder. The non-active extruder may change when loading a project file which happened to have the same printer (so no new printer is created) and the same material and variant in the active extruder but not the same material or variant in another extruder. Contributes to issue CURA-3803. --- cura/Settings/ExtrudersModel.py | 51 +++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 7491f8aa93..918c674d3d 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -1,12 +1,15 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer +from typing import Iterable import UM.Qt.ListModel from UM.Application import Application import UM.FlameProfiler from cura.Settings.ExtruderManager import ExtruderManager +from cura.Settings.ExtruderStack import ExtruderStack #To listen to changes on the extruders. +from cura.Settings.MachineManager import MachineManager #To listen to changes on the extruders of the currently active machine. ## Model that holds extruders. # @@ -66,16 +69,12 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self._add_global = False self._simple_names = False - self._active_extruder_stack = None + self._active_machine_extruders = [] # type: Iterable[ExtruderStack] #Listen to changes. - Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders) - manager = ExtruderManager.getInstance() - - self._updateExtruders() - - manager.activeExtruderChanged.connect(self._onActiveExtruderChanged) - self._onActiveExtruderChanged() + Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) #When the machine is swapped we must update the active machine extruders. + ExtruderManager.getInstance().extrudersChanged.connect(self._extrudersChanged) #When the extruders change we must link to the stack-changed signal of the new extruder. + self._extrudersChanged() #Also calls _updateExtruders. def setAddGlobal(self, add): if add != self._add_global: @@ -104,17 +103,31 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): def simpleNames(self): return self._simple_names - def _onActiveExtruderChanged(self): - manager = ExtruderManager.getInstance() - active_extruder_stack = manager.getActiveExtruderStack() - if self._active_extruder_stack != active_extruder_stack: - if self._active_extruder_stack: - self._active_extruder_stack.containersChanged.disconnect(self._onExtruderStackContainersChanged) + ## Links to the stack-changed signal of the new extruders when an extruder + # is swapped out or added in the current machine. + # + # \param machine_id The machine for which the extruders changed. This is + # filled by the ExtruderManager.extrudersChanged signal when coming from + # that signal. Application.globalContainerStackChanged doesn't fill this + # signal; it's assumed to be the current printer in that case. + def _extrudersChanged(self, machine_id = None): + if machine_id is not None: + if Application.getInstance().getGlobalContainerStack() is None: + return #No machine, don't need to update the current machine's extruders. + if machine_id != Application.getInstance().getGlobalContainerStack().getId(): + return #Not the current machine. + #Unlink from old extruders. + for extruder in self._active_machine_extruders: + extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged) - if active_extruder_stack: - # Update the model when the material container is changed - active_extruder_stack.containersChanged.connect(self._onExtruderStackContainersChanged) - self._active_extruder_stack = active_extruder_stack + #Link to new extruders. + self._active_machine_extruders = [] + extruder_manager = ExtruderManager.getInstance() + for extruder in extruder_manager.getExtruderStacks(): + extruder.containersChanged.connect(self._onExtruderStackContainersChanged) + self._active_machine_extruders.append(extruder) + + self._updateExtruders() #Since the new extruders may have different properties, update our own model. def _onExtruderStackContainersChanged(self, container): # Update when there is an empty container or material change From 8c166ea7708438bf8c55233eaa6bbb7bb502f64e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 22 May 2017 12:58:15 +0200 Subject: [PATCH 65/66] Use CuraContainerStack.definition instead of ContainerStack.getBottom() We know now that it must be a Cura Container Stack. This should be a bit faster, and makes the error message I was getting a bit more clear. Contributes to issue CURA-3803. --- cura/Settings/SettingInheritanceManager.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index 7e9bc2aa0e..d3a1655889 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal @@ -35,7 +35,7 @@ class SettingInheritanceManager(QObject): ## Get the keys of all children settings with an override. @pyqtSlot(str, result = "QStringList") def getChildrenKeysWithOverride(self, key): - definitions = self._global_container_stack.getBottom().findDefinitions(key=key) + definitions = self._global_container_stack.definition.findDefinitions(key=key) if not definitions: Logger.log("w", "Could not find definition for key [%s]", key) return [] @@ -55,7 +55,7 @@ class SettingInheritanceManager(QObject): Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index) return [] - definitions = self._global_container_stack.getBottom().findDefinitions(key=key) + definitions = self._global_container_stack.definition.findDefinitions(key=key) if not definitions: Logger.log("w", "Could not find definition for key [%s] (2)", key) return [] @@ -93,7 +93,7 @@ class SettingInheritanceManager(QObject): def _onPropertyChanged(self, key, property_name): if (property_name == "value" or property_name == "enabled") and self._global_container_stack: - definitions = self._global_container_stack.getBottom().findDefinitions(key = key) + definitions = self._global_container_stack.definition.findDefinitions(key = key) if not definitions: return @@ -209,7 +209,7 @@ class SettingInheritanceManager(QObject): self._settings_with_inheritance_warning.append(setting_key) # Check all the categories if any of their children have their inheritance overwritten. - for category in self._global_container_stack.getBottom().findDefinitions(type = "category"): + for category in self._global_container_stack.definition.findDefinitions(type = "category"): if self._recursiveCheck(category): self._settings_with_inheritance_warning.append(category.key) From 5f0fb3f9bdf0d3e8cfdf8c36eac31c3c49d90872 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 22 May 2017 14:01:41 +0200 Subject: [PATCH 66/66] Don't squash signals updating materials and variants These may have different parameters, such as which machine and extruder they are updating the material and variant of. If we only pass the last signal on, then we're missing the update of other extruders. Contributes to issue CURA-3803. --- cura/Settings/MachineManager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 9564b8a5b0..6f1b82a7b8 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -705,7 +705,7 @@ class MachineManager(QObject): # Depending on from/to material+current variant, a quality profile is chosen and set. @pyqtSlot(str) def setActiveMaterial(self, material_id: str): - with postponeSignals(*self._getContainerChangedSignals(), compress = True): + with postponeSignals(*self._getContainerChangedSignals()): containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id) if not containers or not self._active_container_stack: return @@ -770,7 +770,7 @@ class MachineManager(QObject): @pyqtSlot(str) def setActiveVariant(self, variant_id: str): - with postponeSignals(*self._getContainerChangedSignals(), compress = True): + with postponeSignals(*self._getContainerChangedSignals()): containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id) if not containers or not self._active_container_stack: return