Просмотр исходного кода

CURA-4525 solved merge conflicts

Jack Ha 7 лет назад
Родитель
Сommit
bfa33c721c

+ 36 - 34
Jenkinsfile

@@ -1,45 +1,47 @@
-parallel_nodes(['linux && cura', 'windows && cura']) {
-    // Prepare building
-    stage('Prepare') {
-        // Ensure we start with a clean build directory.
-        step([$class: 'WsCleanup'])
+timeout(time: 2, unit: "HOURS") {
+    parallel_nodes(['linux && cura', 'windows && cura']) {
+        // Prepare building
+        stage('Prepare') {
+            // Ensure we start with a clean build directory.
+            step([$class: 'WsCleanup'])
 
-        // Checkout whatever sources are linked to this pipeline.
-        checkout scm
-    }
+            // Checkout whatever sources are linked to this pipeline.
+            checkout scm
+        }
 
-    // If any error occurs during building, we want to catch it and continue with the "finale" stage.
-    catchError {
-        // Building and testing should happen in a subdirectory.
-        dir('build') {
-            // Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
-            stage('Build') {
-                def branch = env.BRANCH_NAME
-                if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}")) {
-                    branch = "master"
-                }
+        // If any error occurs during building, we want to catch it and continue with the "finale" stage.
+        catchError {
+            // Building and testing should happen in a subdirectory.
+            dir('build') {
+                // Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
+                stage('Build') {
+                    def branch = env.BRANCH_NAME
+                    if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}")) {
+                        branch = "master"
+                    }
 
-                // Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
-                def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
-                cmake("..", "-DCMAKE_PREFIX_PATH=\"${env.CURA_ENVIRONMENT_PATH}/${branch}\" -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=\"${uranium_dir}\"")
-            }
+                    // Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
+                    def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
+                    cmake("..", "-DCMAKE_PREFIX_PATH=\"${env.CURA_ENVIRONMENT_PATH}/${branch}\" -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=\"${uranium_dir}\"")
+                }
 
-            // Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
-            stage('Unit Test') {
-                try {
-                    make('test')
-                } catch(e) {
-                    currentBuild.result = "UNSTABLE"
+                // Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
+                stage('Unit Test') {
+                    try {
+                        make('test')
+                    } catch(e) {
+                        currentBuild.result = "UNSTABLE"
+                    }
                 }
             }
         }
-    }
 
-    // Perform any post-build actions like notification and publishing of unit tests.
-    stage('Finalize') {
-        // Publish the test results to Jenkins.
-        junit allowEmptyResults: true, testResults: 'build/junit*.xml'
+        // Perform any post-build actions like notification and publishing of unit tests.
+        stage('Finalize') {
+            // Publish the test results to Jenkins.
+            junit allowEmptyResults: true, testResults: 'build/junit*.xml'
 
-        notify_build_result(env.CURA_EMAIL_RECIPIENTS, '#cura-dev', ['master', '2.'])
+            notify_build_result(env.CURA_EMAIL_RECIPIENTS, '#cura-dev', ['master', '2.'])
+        }
     }
 }

+ 13 - 6
cmake/CuraTests.cmake

@@ -24,16 +24,23 @@ function(cura_add_test)
     
     if(WIN32)
         string(REPLACE "|" "\\;" _PYTHONPATH ${_PYTHONPATH})
+        set(_PYTHONPATH "${_PYTHONPATH}\\;$ENV{PYTHONPATH}")
     else()
         string(REPLACE "|" ":" _PYTHONPATH ${_PYTHONPATH})
+        set(_PYTHONPATH "${_PYTHONPATH}:$ENV{PYTHONPATH}")
     endif()
 
-    add_test(
-        NAME ${_NAME}
-        COMMAND ${PYTHON_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
-    )
-    set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
-    set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
+    get_test_property(${_NAME} ENVIRONMENT test_exists) #Find out if the test exists by getting a property from it that always exists (such as ENVIRONMENT because we set that ourselves).
+    if (NOT ${test_exists})
+        add_test(
+            NAME ${_NAME}
+            COMMAND ${PYTHON_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
+        )
+        set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
+        set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
+    else()
+        message(WARNING "Duplicate test ${_NAME}!")
+    endif()
 endfunction()
 
 cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")

+ 11 - 3
cura/CuraApplication.py

@@ -329,7 +329,7 @@ class CuraApplication(QtApplication):
 
         preferences.addPreference("view/invert_zoom", False)
         preferences.addPreference("view/filter_current_build_plate", False)
-        preferences.addPreference("cura/sidebar_collapse", False)
+        preferences.addPreference("cura/sidebar_collapsed", False)
 
         self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
 
@@ -422,6 +422,14 @@ class CuraApplication(QtApplication):
         else:
             self.exit(0)
 
+    ##  Signal to connect preferences action in QML
+    showPreferencesWindow = pyqtSignal()
+
+    ##  Show the preferences window
+    @pyqtSlot()
+    def showPreferences(self):
+        self.showPreferencesWindow.emit()
+
     ## A reusable dialogbox
     #
     showMessageBox = pyqtSignal(str, str, str, str, int, int, arguments = ["title", "text", "informativeText", "detailedText", "buttons", "icon"])
@@ -703,7 +711,7 @@ class CuraApplication(QtApplication):
         self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
         self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
 
-        run_without_gui = self.getCommandLineOption("headless", False) or self.getCommandLineOption("invisible", False)
+        run_without_gui = self.getCommandLineOption("headless", False)
         if not run_without_gui:
             self.initializeEngine()
             controller.setActiveStage("PrepareStage")
@@ -1449,7 +1457,7 @@ class CuraApplication(QtApplication):
             if arrange_objects_on_load:
                 if node.callDecoration("isSliceable"):
                     # Only check position if it's not already blatantly obvious that it won't fit.
-                    if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
+                    if node.getBoundingBox() is None or self._volume.getBoundingBox() is None or node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
                         # Find node location
                         offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
 

+ 28 - 8
cura/PlatformPhysics.py

@@ -34,6 +34,7 @@ class PlatformPhysics:
         self._change_timer.timeout.connect(self._onChangeTimerFinished)
         self._move_factor = 1.1  # By how much should we multiply overlap to calculate a new spot?
         self._max_overlap_checks = 10  # How many times should we try to find a new spot per tick?
+        self._minimum_gap = 2 # It is a minimum distance between two models, applicable for small models
 
         Preferences.getInstance().addPreference("physics/automatic_push_free", True)
         Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
@@ -77,7 +78,8 @@ class PlatformPhysics:
             if not node.getDecorator(ConvexHullDecorator):
                 node.addDecorator(ConvexHullDecorator())
 
-            if Preferences.getInstance().getValue("physics/automatic_push_free"):
+            # only push away objects if this node is a printing mesh
+            if not node.callDecoration("isNonPrintingMesh") and Preferences.getInstance().getValue("physics/automatic_push_free"):
                 # Check for collisions between convex hulls
                 for other_node in BreadthFirstIterator(root):
                     # Ignore root, ourselves and anything that is not a normal SceneNode.
@@ -99,6 +101,9 @@ class PlatformPhysics:
                     if other_node in transformed_nodes:
                         continue  # Other node is already moving, wait for next pass.
 
+                    if other_node.callDecoration("isNonPrintingMesh"):
+                        continue
+
                     overlap = (0, 0)  # Start loop with no overlap
                     current_overlap_checks = 0
                     # Continue to check the overlap until we no longer find one.
@@ -113,26 +118,41 @@ class PlatformPhysics:
                                     overlap = node.callDecoration("getConvexHull").translate(move_vector.x, move_vector.z).intersectsPolygon(other_head_hull)
                                     if overlap:
                                         # Moving ensured that overlap was still there. Try anew!
-                                        move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor,
-                                                                      z=move_vector.z + overlap[1] * self._move_factor)
+                                        move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor,
+                                                                      z = move_vector.z + overlap[1] * self._move_factor)
                             else:
                                 # Moving ensured that overlap was still there. Try anew!
-                                move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor,
-                                                              z=move_vector.z + overlap[1] * self._move_factor)
+                                move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor,
+                                                              z = move_vector.z + overlap[1] * self._move_factor)
                         else:
                             own_convex_hull = node.callDecoration("getConvexHull")
                             other_convex_hull = other_node.callDecoration("getConvexHull")
                             if own_convex_hull and other_convex_hull:
                                 overlap = own_convex_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_convex_hull)
                                 if overlap:  # Moving ensured that overlap was still there. Try anew!
-                                    move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor,
-                                                                  z=move_vector.z + overlap[1] * self._move_factor)
+                                    temp_move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor,
+                                                                  z = move_vector.z + overlap[1] * self._move_factor)
+
+                                    # if the distance between two models less than 2mm then try to find a new factor
+                                    if abs(temp_move_vector.x - overlap[0]) < self._minimum_gap and abs(temp_move_vector.y - overlap[1]) < self._minimum_gap:
+                                        temp_scale_factor = self._move_factor
+                                        temp_x_factor = (abs(overlap[0]) + self._minimum_gap) / overlap[0] if overlap[0] != 0 else 0 # find x move_factor, like (3.4 + 2) / 3.4 = 1.58
+                                        temp_y_factor = (abs(overlap[1]) + self._minimum_gap) / overlap[1] if overlap[1] != 0 else 0 # find y move_factor
+                                        if abs(temp_x_factor) > abs(temp_y_factor):
+                                            temp_scale_factor = temp_x_factor
+                                        else:
+                                            temp_scale_factor = temp_y_factor
+
+                                        move_vector = move_vector.set(x = move_vector.x + overlap[0] * temp_scale_factor,
+                                                                      z = move_vector.z + overlap[1] * temp_scale_factor)
+                                    else:
+                                        move_vector = temp_move_vector
                             else:
                                 # This can happen in some cases if the object is not yet done with being loaded.
                                 #  Simply waiting for the next tick seems to resolve this correctly.
                                 overlap = None
 
-            if not Vector.Null.equals(move_vector, epsilon=1e-5):
+            if not Vector.Null.equals(move_vector, epsilon = 1e-5):
                 transformed_nodes.append(node)
                 op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
                 op.push()

+ 44 - 4
cura/Settings/CuraContainerRegistry.py

@@ -14,6 +14,7 @@ from UM.Decorators import override
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Settings.ContainerStack import ContainerStack
 from UM.Settings.InstanceContainer import InstanceContainer
+from UM.Settings.SettingInstance import SettingInstance
 from UM.Application import Application
 from UM.Logger import Logger
 from UM.Message import Message
@@ -224,7 +225,7 @@ class CuraContainerRegistry(ContainerRegistry):
                         # This is assumed to be the global profile
                         profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
 
-                    elif len(machine_extruders) > profile_index:
+                    elif profile_index < len(machine_extruders) + 1:
                         # This is assumed to be an extruder profile
                         extruder_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_extruders[profile_index - 1].getBottom())
                         if not profile.getMetaDataEntry("extruder"):
@@ -430,11 +431,42 @@ class CuraContainerRegistry(ContainerRegistry):
         extruder_stack.setDefinition(extruder_definition)
         extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
 
+        from cura.CuraApplication import CuraApplication
+
+        # create a new definition_changes container for the extruder stack
+        definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings")
+        definition_changes_name = definition_changes_id
+        definition_changes = InstanceContainer(definition_changes_id)
+        definition_changes.setName(definition_changes_name)
+        definition_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
+        definition_changes.addMetaDataEntry("type", "definition_changes")
+        definition_changes.addMetaDataEntry("definition", extruder_definition.getId())
+
+        # move definition_changes settings if exist
+        for setting_key in definition_changes.getAllKeys():
+            if machine.definition.getProperty(setting_key, "settable_per_extruder"):
+                setting_value = machine.definitionChanges.getProperty(setting_key, "value")
+                if setting_value is not None:
+                    # move it to the extruder stack's definition_changes
+                    setting_definition = machine.getSettingDefinition(setting_key)
+                    new_instance = SettingInstance(setting_definition, definition_changes)
+                    new_instance.setProperty("value", setting_value)
+                    new_instance.resetState()  # Ensure that the state is not seen as a user state.
+                    definition_changes.addInstance(new_instance)
+                    definition_changes.setDirty(True)
+
+                    machine.definitionChanges.removeInstance(setting_key, postpone_emit = True)
+
+        self.addContainer(definition_changes)
+        extruder_stack.setDefinitionChanges(definition_changes)
+
         # create empty user changes container otherwise
-        user_container = InstanceContainer(extruder_stack.id + "_user")
+        user_container_id = self.uniqueName(extruder_stack.getId() + "_user")
+        user_container_name = user_container_id
+        user_container = InstanceContainer(user_container_id)
+        user_container.setName(user_container_name)
         user_container.addMetaDataEntry("type", "user")
         user_container.addMetaDataEntry("machine", extruder_stack.getId())
-        from cura.CuraApplication import CuraApplication
         user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
         user_container.setDefinition(machine.definition.getId())
 
@@ -444,7 +476,15 @@ class CuraContainerRegistry(ContainerRegistry):
             for user_setting_key in machine.userChanges.getAllKeys():
                 settable_per_extruder = machine.getProperty(user_setting_key, "settable_per_extruder")
                 if settable_per_extruder:
-                    user_container.addInstance(machine.userChanges.getInstance(user_setting_key))
+                    setting_value = machine.getProperty(user_setting_key, "value")
+
+                    setting_definition = machine.getSettingDefinition(user_setting_key)
+                    new_instance = SettingInstance(setting_definition, definition_changes)
+                    new_instance.setProperty("value", setting_value)
+                    new_instance.resetState()  # Ensure that the state is not seen as a user state.
+                    user_container.addInstance(new_instance)
+                    user_container.setDirty(True)
+
                     machine.userChanges.removeInstance(user_setting_key, postpone_emit = True)
 
         self.addContainer(user_container)

+ 29 - 0
cura/Settings/ExtruderStack.py

@@ -8,6 +8,7 @@ from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
 from UM.Settings.ContainerStack import ContainerStack
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
+from UM.Settings.SettingInstance import SettingInstance
 
 from . import Exceptions
 from .CuraContainerStack import CuraContainerStack
@@ -16,6 +17,11 @@ from .ExtruderManager import ExtruderManager
 if TYPE_CHECKING:
     from cura.Settings.GlobalStack import GlobalStack
 
+
+_EXTRUDER_SPECIFIC_DEFINITION_CHANGES_SETTINGS = ["machine_nozzle_size",
+                                                  "material_diameter"]
+
+
 ##  Represents an Extruder and its related containers.
 #
 #
@@ -39,6 +45,29 @@ class ExtruderStack(CuraContainerStack):
         # For backward compatibility: Register the extruder with the Extruder Manager
         ExtruderManager.getInstance().registerExtruder(self, stack.id)
 
+        # Now each machine will have at least one extruder stack. If this is the first extruder, the extruder-specific
+        # settings such as nozzle size and material diameter should be moved from the machine's definition_changes to
+        # the this extruder's definition_changes.
+        #
+        # We do this here because it is tooooo expansive to do it in the version upgrade: During the version upgrade,
+        # when we are upgrading a definition_changes container file, there is NO guarantee that other files such as
+        # machine an extruder stack files are upgraded before this, so we cannot read those files assuming they are in
+        # the latest format.
+        if self.getMetaDataEntry("position") == "0":
+            for key in _EXTRUDER_SPECIFIC_DEFINITION_CHANGES_SETTINGS:
+                setting_value = stack.definitionChanges.getProperty(key, "value")
+                if setting_value is None:
+                    continue
+
+                setting_definition = stack.getSettingDefinition(key)
+                new_instance = SettingInstance(setting_definition, self.definitionChanges)
+                new_instance.setProperty("value", setting_value)
+                new_instance.resetState()  # Ensure that the state is not seen as a user state.
+                self.definitionChanges.addInstance(new_instance)
+                self.definitionChanges.setDirty(True)
+
+                stack.definitionChanges.removeInstance(key, postpone_emit = True)
+
     @override(ContainerStack)
     def getNextStack(self) -> Optional["GlobalStack"]:
         return super().getNextStack()

+ 3 - 4
cura/ShapeArray.py

@@ -43,13 +43,12 @@ class ShapeArray:
         transform_x = transform._data[0][3]
         transform_y = transform._data[2][3]
         hull_verts = node.callDecoration("getConvexHull")
+        # If a model is too small then it will not contain any points
+        if hull_verts is None or not hull_verts.getPoints().any():
+            return None, None
         # For one_at_a_time printing you need the convex hull head.
         hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts
 
-        # If a model is to small then it will not contain any points
-        if not hull_verts.getPoints().any():
-            return None, None
-
         offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
         offset_points = copy.deepcopy(offset_verts._points)  # x, y
         offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x)

+ 2 - 7
plugins/3MFReader/ThreeMFWorkspaceReader.py

@@ -682,12 +682,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
                                   file_name = global_stack_file)
 
                 # Ensure a unique ID and name
-                stack._id = global_stack_id_new
-
-                # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
-                # bound machine also needs to change.
-                if stack.getMetaDataEntry("machine", None):
-                    stack.setMetaDataEntry("machine", global_stack_id_new)
+                stack.setMetaDataEntry("id", global_stack_id_new)
 
                 # Only machines need a new name, stacks may be non-unique
                 stack.setName(global_stack_name_new)
@@ -741,7 +736,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
                     stack.deserialize(extruder_file_content, file_name = extruder_stack_file)
 
                     # Ensure a unique ID and name
-                    stack._id = new_id
+                    stack.setMetaDataEntry("id", new_id)
 
                     self._container_registry.addContainer(stack)
                     extruder_stacks_added.append(stack)

+ 2 - 1
plugins/CuraEngineBackend/StartSliceJob.py

@@ -153,10 +153,11 @@ class StartSliceJob(Job):
                             is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
 
                         if (node.callDecoration("getBuildPlateNumber") == self._build_plate_number):
-                            if not getattr(node, "_outside_buildarea", False) or not is_non_printing_mesh:
+                            if not getattr(node, "_outside_buildarea", False) or is_non_printing_mesh:
                                 temp_list.append(node)
                                 if not is_non_printing_mesh:
                                     has_printing_mesh = True
+
                     Job.yieldThread()
 
                 #If the list doesn't have any model with suitable settings then clean the list

+ 48 - 64
plugins/MachineSettingsAction/MachineSettingsAction.py

@@ -7,14 +7,11 @@ from UM.FlameProfiler import pyqtSlot
 from cura.MachineAction import MachineAction
 
 from UM.Application import Application
-from UM.Preferences import Preferences
-from UM.Settings.InstanceContainer import InstanceContainer
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Settings.DefinitionContainer import DefinitionContainer
 from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
 from UM.Logger import Logger
 
-from cura.CuraApplication import CuraApplication
 from cura.Settings.ExtruderManager import ExtruderManager
 from cura.Settings.CuraStackBuilder import CuraStackBuilder
 
@@ -36,7 +33,6 @@ class MachineSettingsAction(MachineAction):
         self._container_registry.containerAdded.connect(self._onContainerAdded)
         self._container_registry.containerRemoved.connect(self._onContainerRemoved)
         Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
-        ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
 
         self._empty_container = self._container_registry.getEmptyInstanceContainer()
 
@@ -67,7 +63,9 @@ class MachineSettingsAction(MachineAction):
                 self._global_container_stack, self._global_container_stack.getName() + "_settings")
 
         # Notify the UI in which container to store the machine settings data
-        container_index = self._global_container_stack.getContainerIndex(definition_changes_container)
+        from cura.Settings.CuraContainerStack import CuraContainerStack, _ContainerIndexes
+
+        container_index = _ContainerIndexes.DefinitionChanges
         if container_index != self._container_index:
             self._container_index = container_index
             self.containerIndexChanged.emit()
@@ -82,17 +80,6 @@ class MachineSettingsAction(MachineAction):
         if self._backend and self._backend.determineAutoSlicing():
             self._backend.tickle()
 
-    def _onActiveExtruderStackChanged(self):
-        extruder_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
-        if not self._global_container_stack or not extruder_container_stack:
-            return
-
-        # Make sure there is a definition_changes container to store the machine settings
-        definition_changes_container = extruder_container_stack.definitionChanges
-        if definition_changes_container == self._empty_container:
-            definition_changes_container = CuraStackBuilder.createDefinitionChangesContainer(
-                extruder_container_stack, extruder_container_stack.getId() + "_settings")
-
     containerIndexChanged = pyqtSignal()
 
     @pyqtProperty(int, notify = containerIndexChanged)
@@ -217,8 +204,8 @@ class MachineSettingsAction(MachineAction):
 
         Application.getInstance().globalContainerStackChanged.emit()
 
-    @pyqtSlot()
-    def updateMaterialForDiameter(self):
+    @pyqtSlot(int)
+    def updateMaterialForDiameter(self, extruder_position: int):
         # Updates the material container to a material that matches the material diameter set for the printer
         if not self._global_container_stack:
             return
@@ -226,24 +213,22 @@ class MachineSettingsAction(MachineAction):
         if not self._global_container_stack.getMetaDataEntry("has_materials", False):
             return
 
-        material = ExtruderManager.getInstance().getActiveExtruderStack().material
-        material_diameter = material.getProperty("material_diameter", "value")
+        extruder_stack = self._global_container_stack.extruders[str(extruder_position)]
+
+        material_diameter = extruder_stack.material.getProperty("material_diameter", "value")
         if not material_diameter:
             # in case of "empty" material
             material_diameter = 0
 
         material_approximate_diameter = str(round(material_diameter))
-        definition_changes = self._global_container_stack.definitionChanges
-        machine_diameter = definition_changes.getProperty("material_diameter", "value")
+        machine_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
         if not machine_diameter:
-            machine_diameter = self._global_container_stack.definition.getProperty("material_diameter", "value")
+            machine_diameter = extruder_stack.definition.getProperty("material_diameter", "value")
         machine_approximate_diameter = str(round(machine_diameter))
 
         if material_approximate_diameter != machine_approximate_diameter:
             Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
 
-            stacks = ExtruderManager.getInstance().getExtruderStacks()
-
             if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
                 materials_definition = self._global_container_stack.definition.getId()
                 has_material_variants = self._global_container_stack.getMetaDataEntry("has_variants", False)
@@ -251,45 +236,44 @@ class MachineSettingsAction(MachineAction):
                 materials_definition = "fdmprinter"
                 has_material_variants = False
 
-            for stack in stacks:
-                old_material = stack.material
-                search_criteria = {
-                    "type": "material",
-                    "approximate_diameter": machine_approximate_diameter,
-                    "material": old_material.getMetaDataEntry("material", "value"),
-                    "supplier": old_material.getMetaDataEntry("supplier", "value"),
-                    "color_name": old_material.getMetaDataEntry("color_name", "value"),
-                    "definition": materials_definition
-                }
-                if has_material_variants:
-                    search_criteria["variant"] = stack.variant.getId()
-
-                if old_material == self._empty_container:
-                    search_criteria.pop("material", None)
-                    search_criteria.pop("supplier", None)
-                    search_criteria.pop("definition", None)
-                    search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
-
+            old_material = extruder_stack.material
+            search_criteria = {
+                "type": "material",
+                "approximate_diameter": machine_approximate_diameter,
+                "material": old_material.getMetaDataEntry("material", "value"),
+                "supplier": old_material.getMetaDataEntry("supplier", "value"),
+                "color_name": old_material.getMetaDataEntry("color_name", "value"),
+                "definition": materials_definition
+            }
+            if has_material_variants:
+                search_criteria["variant"] = extruder_stack.variant.getId()
+
+            if old_material == self._empty_container:
+                search_criteria.pop("material", None)
+                search_criteria.pop("supplier", None)
+                search_criteria.pop("definition", None)
+                search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
+
+            materials = self._container_registry.findInstanceContainers(**search_criteria)
+            if not materials:
+                # Same material with new diameter is not found, search for generic version of the same material type
+                search_criteria.pop("supplier", None)
+                search_criteria["color_name"] = "Generic"
                 materials = self._container_registry.findInstanceContainers(**search_criteria)
-                if not materials:
-                    # Same material with new diameter is not found, search for generic version of the same material type
-                    search_criteria.pop("supplier", None)
-                    search_criteria["color_name"] = "Generic"
-                    materials = self._container_registry.findInstanceContainers(**search_criteria)
-                if not materials:
-                    # Generic material with new diameter is not found, search for preferred material
-                    search_criteria.pop("color_name", None)
-                    search_criteria.pop("material", None)
-                    search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
-                    materials = self._container_registry.findInstanceContainers(**search_criteria)
-                if not materials:
-                    # Preferred material with new diameter is not found, search for any material
-                    search_criteria.pop("id", None)
-                    materials = self._container_registry.findInstanceContainers(**search_criteria)
-                if not materials:
-                    # Just use empty material as a final fallback
-                    materials = [self._empty_container]
+            if not materials:
+                # Generic material with new diameter is not found, search for preferred material
+                search_criteria.pop("color_name", None)
+                search_criteria.pop("material", None)
+                search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
+                materials = self._container_registry.findInstanceContainers(**search_criteria)
+            if not materials:
+                # Preferred material with new diameter is not found, search for any material
+                search_criteria.pop("id", None)
+                materials = self._container_registry.findInstanceContainers(**search_criteria)
+            if not materials:
+                # Just use empty material as a final fallback
+                materials = [self._empty_container]
 
-                Logger.log("i", "Selecting new material: %s" % materials[0].getId())
+            Logger.log("i", "Selecting new material: %s" % materials[0].getId())
 
-                stack.material = materials[0]
+            extruder_stack.material = materials[0]

Некоторые файлы не были показаны из-за большого количества измененных файлов