Browse Source

Merge branch 'master' into network_rewrite

ChrisTerBeke 7 years ago
parent
commit
182e7de07d

+ 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)
     if(WIN32)
         string(REPLACE "|" "\\;" _PYTHONPATH ${_PYTHONPATH})
         string(REPLACE "|" "\\;" _PYTHONPATH ${_PYTHONPATH})
+        set(_PYTHONPATH "${_PYTHONPATH}\\;$ENV{PYTHONPATH}")
     else()
     else()
         string(REPLACE "|" ":" _PYTHONPATH ${_PYTHONPATH})
         string(REPLACE "|" ":" _PYTHONPATH ${_PYTHONPATH})
+        set(_PYTHONPATH "${_PYTHONPATH}:$ENV{PYTHONPATH}")
     endif()
     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()
 endfunction()
 
 
 cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
 cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")

+ 11 - 3
cura/CuraApplication.py

@@ -316,7 +316,7 @@ class CuraApplication(QtApplication):
         preferences.addPreference("cura/material_settings", "{}")
         preferences.addPreference("cura/material_settings", "{}")
 
 
         preferences.addPreference("view/invert_zoom", False)
         preferences.addPreference("view/invert_zoom", 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")
         self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
 
 
@@ -409,6 +409,14 @@ class CuraApplication(QtApplication):
         else:
         else:
             self.exit(0)
             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
     ## A reusable dialogbox
     #
     #
     showMessageBox = pyqtSignal(str, str, str, str, int, int, arguments = ["title", "text", "informativeText", "detailedText", "buttons", "icon"])
     showMessageBox = pyqtSignal(str, str, str, str, int, int, arguments = ["title", "text", "informativeText", "detailedText", "buttons", "icon"])
@@ -683,7 +691,7 @@ class CuraApplication(QtApplication):
         self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
         self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
         self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
         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:
         if not run_without_gui:
             self.initializeEngine()
             self.initializeEngine()
             controller.setActiveStage("PrepareStage")
             controller.setActiveStage("PrepareStage")
@@ -1379,7 +1387,7 @@ class CuraApplication(QtApplication):
 
 
             if node.callDecoration("isSliceable"):
             if node.callDecoration("isSliceable"):
                 # Only check position if it's not already blatantly obvious that it won't fit.
                 # 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
                     # Find node location
                     offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
                     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._change_timer.timeout.connect(self._onChangeTimerFinished)
         self._move_factor = 1.1  # By how much should we multiply overlap to calculate a new spot?
         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._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_push_free", True)
         Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
         Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
@@ -76,7 +77,8 @@ class PlatformPhysics:
             if not node.getDecorator(ConvexHullDecorator):
             if not node.getDecorator(ConvexHullDecorator):
                 node.addDecorator(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
                 # Check for collisions between convex hulls
                 for other_node in BreadthFirstIterator(root):
                 for other_node in BreadthFirstIterator(root):
                     # Ignore root, ourselves and anything that is not a normal SceneNode.
                     # Ignore root, ourselves and anything that is not a normal SceneNode.
@@ -98,6 +100,9 @@ class PlatformPhysics:
                     if other_node in transformed_nodes:
                     if other_node in transformed_nodes:
                         continue  # Other node is already moving, wait for next pass.
                         continue  # Other node is already moving, wait for next pass.
 
 
+                    if other_node.callDecoration("isNonPrintingMesh"):
+                        continue
+
                     overlap = (0, 0)  # Start loop with no overlap
                     overlap = (0, 0)  # Start loop with no overlap
                     current_overlap_checks = 0
                     current_overlap_checks = 0
                     # Continue to check the overlap until we no longer find one.
                     # Continue to check the overlap until we no longer find one.
@@ -112,26 +117,41 @@ class PlatformPhysics:
                                     overlap = node.callDecoration("getConvexHull").translate(move_vector.x, move_vector.z).intersectsPolygon(other_head_hull)
                                     overlap = node.callDecoration("getConvexHull").translate(move_vector.x, move_vector.z).intersectsPolygon(other_head_hull)
                                     if overlap:
                                     if overlap:
                                         # Moving ensured that overlap was still there. Try anew!
                                         # 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:
                             else:
                                 # Moving ensured that overlap was still there. Try anew!
                                 # 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:
                         else:
                             own_convex_hull = node.callDecoration("getConvexHull")
                             own_convex_hull = node.callDecoration("getConvexHull")
                             other_convex_hull = other_node.callDecoration("getConvexHull")
                             other_convex_hull = other_node.callDecoration("getConvexHull")
                             if own_convex_hull and other_convex_hull:
                             if own_convex_hull and other_convex_hull:
                                 overlap = own_convex_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(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!
                                 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:
                             else:
                                 # This can happen in some cases if the object is not yet done with being loaded.
                                 # 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.
                                 #  Simply waiting for the next tick seems to resolve this correctly.
                                 overlap = None
                                 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)
                 transformed_nodes.append(node)
                 op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
                 op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
                 op.push()
                 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.ContainerRegistry import ContainerRegistry
 from UM.Settings.ContainerStack import ContainerStack
 from UM.Settings.ContainerStack import ContainerStack
 from UM.Settings.InstanceContainer import InstanceContainer
 from UM.Settings.InstanceContainer import InstanceContainer
+from UM.Settings.SettingInstance import SettingInstance
 from UM.Application import Application
 from UM.Application import Application
 from UM.Logger import Logger
 from UM.Logger import Logger
 from UM.Message import Message
 from UM.Message import Message
@@ -224,7 +225,7 @@ class CuraContainerRegistry(ContainerRegistry):
                         # This is assumed to be the global profile
                         # This is assumed to be the global profile
                         profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
                         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
                         # This is assumed to be an extruder profile
                         extruder_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_extruders[profile_index - 1].getBottom())
                         extruder_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_extruders[profile_index - 1].getBottom())
                         if not profile.getMetaDataEntry("extruder"):
                         if not profile.getMetaDataEntry("extruder"):
@@ -430,11 +431,42 @@ class CuraContainerRegistry(ContainerRegistry):
         extruder_stack.setDefinition(extruder_definition)
         extruder_stack.setDefinition(extruder_definition)
         extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
         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
         # 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("type", "user")
         user_container.addMetaDataEntry("machine", extruder_stack.getId())
         user_container.addMetaDataEntry("machine", extruder_stack.getId())
-        from cura.CuraApplication import CuraApplication
         user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
         user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
         user_container.setDefinition(machine.definition.getId())
         user_container.setDefinition(machine.definition.getId())
 
 
@@ -444,7 +476,15 @@ class CuraContainerRegistry(ContainerRegistry):
             for user_setting_key in machine.userChanges.getAllKeys():
             for user_setting_key in machine.userChanges.getAllKeys():
                 settable_per_extruder = machine.getProperty(user_setting_key, "settable_per_extruder")
                 settable_per_extruder = machine.getProperty(user_setting_key, "settable_per_extruder")
                 if 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)
                     machine.userChanges.removeInstance(user_setting_key, postpone_emit = True)
 
 
         self.addContainer(user_container)
         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.ContainerStack import ContainerStack
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
 from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
+from UM.Settings.SettingInstance import SettingInstance
 
 
 from . import Exceptions
 from . import Exceptions
 from .CuraContainerStack import CuraContainerStack
 from .CuraContainerStack import CuraContainerStack
@@ -16,6 +17,11 @@ from .ExtruderManager import ExtruderManager
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from cura.Settings.GlobalStack import GlobalStack
     from cura.Settings.GlobalStack import GlobalStack
 
 
+
+_EXTRUDER_SPECIFIC_DEFINITION_CHANGES_SETTINGS = ["machine_nozzle_size",
+                                                  "material_diameter"]
+
+
 ##  Represents an Extruder and its related containers.
 ##  Represents an Extruder and its related containers.
 #
 #
 #
 #
@@ -39,6 +45,29 @@ class ExtruderStack(CuraContainerStack):
         # For backward compatibility: Register the extruder with the Extruder Manager
         # For backward compatibility: Register the extruder with the Extruder Manager
         ExtruderManager.getInstance().registerExtruder(self, stack.id)
         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)
     @override(ContainerStack)
     def getNextStack(self) -> Optional["GlobalStack"]:
     def getNextStack(self) -> Optional["GlobalStack"]:
         return super().getNextStack()
         return super().getNextStack()

+ 3 - 4
cura/ShapeArray.py

@@ -43,13 +43,12 @@ class ShapeArray:
         transform_x = transform._data[0][3]
         transform_x = transform._data[0][3]
         transform_y = transform._data[2][3]
         transform_y = transform._data[2][3]
         hull_verts = node.callDecoration("getConvexHull")
         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.
         # For one_at_a_time printing you need the convex hull head.
         hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts
         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_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
         offset_points = copy.deepcopy(offset_verts._points)  # x, y
         offset_points = copy.deepcopy(offset_verts._points)  # x, y
         offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x)
         offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x)

+ 2 - 1
plugins/CuraEngineBackend/StartSliceJob.py

@@ -143,10 +143,11 @@ class StartSliceJob(Job):
                         if per_object_stack:
                         if per_object_stack:
                             is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
                             is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
 
 
-                        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)
                             temp_list.append(node)
                             if not is_non_printing_mesh:
                             if not is_non_printing_mesh:
                                 has_printing_mesh = True
                                 has_printing_mesh = True
+
                     Job.yieldThread()
                     Job.yieldThread()
 
 
                 #If the list doesn't have any model with suitable settings then clean the list
                 #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 cura.MachineAction import MachineAction
 
 
 from UM.Application import Application
 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.ContainerRegistry import ContainerRegistry
 from UM.Settings.DefinitionContainer import DefinitionContainer
 from UM.Settings.DefinitionContainer import DefinitionContainer
 from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
 from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
 from UM.Logger import Logger
 from UM.Logger import Logger
 
 
-from cura.CuraApplication import CuraApplication
 from cura.Settings.ExtruderManager import ExtruderManager
 from cura.Settings.ExtruderManager import ExtruderManager
 from cura.Settings.CuraStackBuilder import CuraStackBuilder
 from cura.Settings.CuraStackBuilder import CuraStackBuilder
 
 
@@ -36,7 +33,6 @@ class MachineSettingsAction(MachineAction):
         self._container_registry.containerAdded.connect(self._onContainerAdded)
         self._container_registry.containerAdded.connect(self._onContainerAdded)
         self._container_registry.containerRemoved.connect(self._onContainerRemoved)
         self._container_registry.containerRemoved.connect(self._onContainerRemoved)
         Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
         Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
-        ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
 
 
         self._empty_container = self._container_registry.getEmptyInstanceContainer()
         self._empty_container = self._container_registry.getEmptyInstanceContainer()
 
 
@@ -67,7 +63,9 @@ class MachineSettingsAction(MachineAction):
                 self._global_container_stack, self._global_container_stack.getName() + "_settings")
                 self._global_container_stack, self._global_container_stack.getName() + "_settings")
 
 
         # Notify the UI in which container to store the machine settings data
         # 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:
         if container_index != self._container_index:
             self._container_index = container_index
             self._container_index = container_index
             self.containerIndexChanged.emit()
             self.containerIndexChanged.emit()
@@ -82,17 +80,6 @@ class MachineSettingsAction(MachineAction):
         if self._backend and self._backend.determineAutoSlicing():
         if self._backend and self._backend.determineAutoSlicing():
             self._backend.tickle()
             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()
     containerIndexChanged = pyqtSignal()
 
 
     @pyqtProperty(int, notify = containerIndexChanged)
     @pyqtProperty(int, notify = containerIndexChanged)
@@ -217,8 +204,8 @@ class MachineSettingsAction(MachineAction):
 
 
         Application.getInstance().globalContainerStackChanged.emit()
         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
         # Updates the material container to a material that matches the material diameter set for the printer
         if not self._global_container_stack:
         if not self._global_container_stack:
             return
             return
@@ -226,24 +213,22 @@ class MachineSettingsAction(MachineAction):
         if not self._global_container_stack.getMetaDataEntry("has_materials", False):
         if not self._global_container_stack.getMetaDataEntry("has_materials", False):
             return
             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:
         if not material_diameter:
             # in case of "empty" material
             # in case of "empty" material
             material_diameter = 0
             material_diameter = 0
 
 
         material_approximate_diameter = str(round(material_diameter))
         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:
         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))
         machine_approximate_diameter = str(round(machine_diameter))
 
 
         if material_approximate_diameter != machine_approximate_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.")
             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):
             if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
                 materials_definition = self._global_container_stack.definition.getId()
                 materials_definition = self._global_container_stack.definition.getId()
                 has_material_variants = self._global_container_stack.getMetaDataEntry("has_variants", False)
                 has_material_variants = self._global_container_stack.getMetaDataEntry("has_variants", False)
@@ -251,45 +236,44 @@ class MachineSettingsAction(MachineAction):
                 materials_definition = "fdmprinter"
                 materials_definition = "fdmprinter"
                 has_material_variants = False
                 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)
                 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]

+ 31 - 27
plugins/MachineSettingsAction/MachineSettingsAction.qml

@@ -292,18 +292,6 @@ Cura.MachineAction
                                     }
                                     }
                                 }
                                 }
                             }
                             }
-
-                            Loader
-                            {
-                                id: materialDiameterField
-                                visible: Cura.MachineManager.hasMaterials
-                                sourceComponent: numericTextFieldWithUnit
-                                property string settingKey: "material_diameter"
-                                property string unit: catalog.i18nc("@label", "mm")
-                                property string tooltip: catalog.i18nc("@tooltip", "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile.")
-                                property var afterOnEditingFinished: manager.updateMaterialForDiameter
-                                property string label: catalog.i18nc("@label", "Material diameter")
-                            }
                         }
                         }
                     }
                     }
 
 
@@ -360,7 +348,6 @@ Cura.MachineAction
                 if(currentIndex > 0)
                 if(currentIndex > 0)
                 {
                 {
                     contentItem.forceActiveFocus();
                     contentItem.forceActiveFocus();
-                    Cura.ExtruderManager.setActiveExtruderIndex(currentIndex - 1);
                 }
                 }
             }
             }
 
 
@@ -397,6 +384,25 @@ Cura.MachineAction
                             property bool isExtruderSetting: true
                             property bool isExtruderSetting: true
                         }
                         }
 
 
+                        Loader
+                        {
+                            id: materialDiameterField
+                            visible: Cura.MachineManager.hasMaterials
+                            sourceComponent: numericTextFieldWithUnit
+                            property string settingKey: "material_diameter"
+                            property string label: catalog.i18nc("@label", "Material diameter")
+                            property string unit: catalog.i18nc("@label", "mm")
+                            property string tooltip: catalog.i18nc("@tooltip", "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile.")
+                            property var afterOnEditingFinished:
+                            {
+                                if (settingsTabs.currentIndex > 0)
+                                {
+                                    manager.updateMaterialForDiameter(settingsTabs.currentIndex - 1);
+                                }
+                            }
+                            property bool isExtruderSetting: true
+                        }
+
                         Loader
                         Loader
                         {
                         {
                             id: extruderOffsetXField
                             id: extruderOffsetXField
@@ -495,7 +501,7 @@ Cura.MachineAction
                     {
                     {
                         if(settingsTabs.currentIndex > 0)
                         if(settingsTabs.currentIndex > 0)
                         {
                         {
-                            return Cura.MachineManager.activeStackId;
+                            return Cura.ExtruderManager.extruderIds[String(settingsTabs.currentIndex - 1)];
                         }
                         }
                         return "";
                         return "";
                     }
                     }
@@ -513,11 +519,11 @@ Cura.MachineAction
                 checked: String(propertyProvider.properties.value).toLowerCase() != 'false'
                 checked: String(propertyProvider.properties.value).toLowerCase() != 'false'
                 onClicked:
                 onClicked:
                 {
                 {
-                        propertyProvider.setPropertyValue("value", checked);
-                        if(_forceUpdateOnChange)
-                        {
-                            manager.forceUpdate();
-                        }
+                    propertyProvider.setPropertyValue("value", checked);
+                    if(_forceUpdateOnChange)
+                    {
+                        manager.forceUpdate();
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -548,7 +554,7 @@ Cura.MachineAction
                     {
                     {
                         if(settingsTabs.currentIndex > 0)
                         if(settingsTabs.currentIndex > 0)
                         {
                         {
-                            return Cura.MachineManager.activeStackId;
+                            return Cura.ExtruderManager.extruderIds[String(settingsTabs.currentIndex - 1)];
                         }
                         }
                         return "";
                         return "";
                     }
                     }
@@ -581,7 +587,10 @@ Cura.MachineAction
                     TextField
                     TextField
                     {
                     {
                         id: textField
                         id: textField
-                        text: (propertyProvider.properties.value) ? propertyProvider.properties.value : ""
+                        text: {
+                            const value = propertyProvider.properties.value;
+                            return value ? value : "";
+                        }
                         validator: RegExpValidator { regExp: _allowNegative ? /-?[0-9\.]{0,6}/ : /[0-9\.]{0,6}/ }
                         validator: RegExpValidator { regExp: _allowNegative ? /-?[0-9\.]{0,6}/ : /[0-9\.]{0,6}/ }
                         onEditingFinished:
                         onEditingFinished:
                         {
                         {
@@ -590,12 +599,7 @@ Cura.MachineAction
                                 propertyProvider.setPropertyValue("value", text);
                                 propertyProvider.setPropertyValue("value", text);
                                 if(_forceUpdateOnChange)
                                 if(_forceUpdateOnChange)
                                 {
                                 {
-                                    var extruderIndex = Cura.ExtruderManager.activeExtruderIndex;
                                     manager.forceUpdate();
                                     manager.forceUpdate();
-                                    if(Cura.ExtruderManager.activeExtruderIndex != extruderIndex)
-                                    {
-                                        Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex)
-                                    }
                                 }
                                 }
                                 if(_afterOnEditingFinished)
                                 if(_afterOnEditingFinished)
                                 {
                                 {
@@ -641,7 +645,7 @@ Cura.MachineAction
                     {
                     {
                         if(settingsTabs.currentIndex > 0)
                         if(settingsTabs.currentIndex > 0)
                         {
                         {
-                            return Cura.MachineManager.activeStackId;
+                            return Cura.ExtruderManager.extruderIds[String(settingsTabs.currentIndex - 1)];
                         }
                         }
                         return "";
                         return "";
                     }
                     }

Some files were not shown because too many files changed in this diff