Exemple #1
0
    def _convertContainerStack(
        self, container: ContainerStack
    ) -> Union[ExtruderStack.ExtruderStack, GlobalStack.GlobalStack]:
        assert type(container) == ContainerStack

        container_type = container.getMetaDataEntry("type")
        if container_type not in ("extruder_train", "machine"):
            # It is not an extruder or machine, so do nothing with the stack
            return container

        Logger.log("d",
                   "Converting ContainerStack {stack} to {type}",
                   stack=container.getId(),
                   type=container_type)

        if container_type == "extruder_train":
            new_stack = ExtruderStack.ExtruderStack(container.getId())
        else:
            new_stack = GlobalStack.GlobalStack(container.getId())

        container_contents = container.serialize()
        new_stack.deserialize(container_contents)

        # Delete the old configuration file so we do not get double stacks
        if os.path.isfile(container.getPath()):
            os.remove(container.getPath())

        return new_stack
Exemple #2
0
class Script:
    def __init__(self) -> None:
        super().__init__()
        self._stack = None  # type: Optional[ContainerStack]
        self._definition = None  # type: Optional[DefinitionContainerInterface]
        self._instance = None  # type: Optional[InstanceContainer]

    def initialize(self) -> None:
        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id=str(id(self)))
        self._stack.setDirty(False)  # This stack does not need to be saved.

        ## Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance().findDefinitionContainers(id=setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                try:
                    self._definition.deserialize(json.dumps(setting_data))
                    ContainerRegistry.getInstance().addContainer(self._definition)
                except ContainerFormatError:
                    self._definition = None
                    return
        if self._definition is None:
            return
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.setMetaDataEntry("setting_version",
                                        self._definition.getMetaDataEntry("setting_version", default=0))
        self._stack.addContainer(self._instance)
        self._stack.propertyChanged.connect(self._onPropertyChanged)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()
    valueChanged = Signal()  # Signal emitted whenever a value of a setting is changed

    def _onPropertyChanged(self, key: str, property_name: str) -> None:
        if property_name == "value":
            self.valueChanged.emit()

            # Property changed: trigger reslice
            # To do this we use the global container stack propertyChanged.
            # Re-slicing is necessary for setting changes in this plugin, because the changes
            # are applied only once per "fresh" gcode
            global_container_stack = Application.getInstance().getGlobalContainerStack()
            if global_container_stack is not None:
                global_container_stack.propertyChanged.emit(key, property_name)

    ##  Needs to return a dict that can be used to construct a settingcategory file.
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    #   Scripts can either override getSettingData directly, or use getSettingDataString
    #   to return a string that will be parsed as json. The latter has the benefit over
    #   returning a dict in that the order of settings is maintained.
    def getSettingData(self) -> Dict[str, Any]:
        setting_data_as_string = self.getSettingDataString()
        setting_data = json.loads(setting_data_as_string, object_pairs_hook = collections.OrderedDict)
        return setting_data

    def getSettingDataString(self) -> str:
        raise NotImplementedError()

    def getDefinitionId(self) -> Optional[str]:
        if self._stack:
            bottom = self._stack.getBottom()
            if bottom is not None:
                return bottom.getId()
        return None

    def getStackId(self) -> Optional[str]:
        if self._stack:
            return self._stack.getId()
        return None

    ##  Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key: str) -> Any:
        if self._stack is not None:
            return self._stack.getProperty(key, "value")
        return None

    ##  Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line: str, key: str, default = None) -> Any:
        if not key in line or (';' in line and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return int(m.group(0))
        except ValueError: #Not an integer.
            try:
                return float(m.group(0))
            except ValueError: #Not a number at all.
                return default

    ##  Convenience function to produce a line of g-code.
    #
    #   You can put in an original g-code line and it'll re-use all the values
    #   in that line.
    #   All other keyword parameters are put in the result in g-code's format.
    #   For instance, if you put ``G=1`` in the parameters, it will output
    #   ``G1``. If you put ``G=1, X=100`` in the parameters, it will output
    #   ``G1 X100``. The parameters G and M will always be put first. The
    #   parameters T and S will be put second (or first if there is no G or M).
    #   The rest of the parameters will be put in arbitrary order.
    #   \param line The original g-code line that must be modified. If not
    #   provided, an entirely new g-code line will be produced.
    #   \return A line of g-code with the desired parameters filled in.
    def putValue(self, line: str = "", **kwargs) -> str:
        #Strip the comment.
        comment = ""
        if ";" in line:
            comment = line[line.find(";"):]
            line = line[:line.find(";")] #Strip the comment.

        #Parse the original g-code line.
        for part in line.split(" "):
            if part == "":
                continue
            parameter = part[0]
            if parameter in kwargs:
                continue #Skip this one. The user-provided parameter overwrites the one in the line.
            value = part[1:]
            kwargs[parameter] = value

        #Write the new g-code line.
        result = ""
        priority_parameters = ["G", "M", "T", "S", "F", "X", "Y", "Z", "E"] #First some parameters that get priority. In order of priority!
        for priority_key in priority_parameters:
            if priority_key in kwargs:
                if result != "":
                    result += " "
                result += priority_key + str(kwargs[priority_key])
                del kwargs[priority_key]
        for key, value in kwargs.items():
            if result != "":
                result += " "
            result += key + str(value)

        #Put the comment back in.
        if comment != "":
            if result != "":
                result += " "
            result += ";" + comment

        return result

    ##  This is called when the script is executed. 
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data: List[str]) -> List[str]:
        raise NotImplementedError()
Exemple #3
0
class Script:
    def __init__(self):
        super().__init__()
        self._settings = None
        self._stack = None

        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id=str(id(self)))
        self._stack.setDirty(False)  # This stack does not need to be saved.

        ## Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance(
            ).findDefinitionContainers(id=setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                try:
                    self._definition.deserialize(json.dumps(setting_data))
                    ContainerRegistry.getInstance().addContainer(
                        self._definition)
                except ContainerFormatError:
                    self._definition = None
                    return
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(
            container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.addMetaDataEntry(
            "setting_version",
            self._definition.getMetaDataEntry("setting_version", default=0))
        self._stack.addContainer(self._instance)
        self._stack.propertyChanged.connect(self._onPropertyChanged)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()
    valueChanged = Signal(
    )  # Signal emitted whenever a value of a setting is changed

    def _onPropertyChanged(self, key, property_name):
        if property_name == "value":
            self.valueChanged.emit()

            # Property changed: trigger reslice
            # To do this we use the global container stack propertyChanged.
            # Reslicing is necessary for setting changes in this plugin, because the changes
            # are applied only once per "fresh" gcode
            global_container_stack = Application.getInstance(
            ).getGlobalContainerStack()
            global_container_stack.propertyChanged.emit(key, property_name)

    ##  Needs to return a dict that can be used to construct a settingcategory file.
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    #   Scripts can either override getSettingData directly, or use getSettingDataString
    #   to return a string that will be parsed as json. The latter has the benefit over
    #   returning a dict in that the order of settings is maintained.
    def getSettingData(self):
        setting_data = self.getSettingDataString()
        if type(setting_data) == str:
            setting_data = json.loads(
                setting_data, object_pairs_hook=collections.OrderedDict)
        return setting_data

    def getSettingDataString(self):
        raise NotImplementedError()

    def getDefinitionId(self):
        if self._stack:
            return self._stack.getBottom().getId()

    def getStackId(self):
        if self._stack:
            return self._stack.getId()

    ##  Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key):
        return self._stack.getProperty(key, "value")

    ##  Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line, key, default=None):
        if not key in line or (';' in line
                               and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return float(m.group(0))
        except:
            return default

    ##  Convenience function to produce a line of g-code.
    #
    #   You can put in an original g-code line and it'll re-use all the values
    #   in that line.
    #   All other keyword parameters are put in the result in g-code's format.
    #   For instance, if you put ``G=1`` in the parameters, it will output
    #   ``G1``. If you put ``G=1, X=100`` in the parameters, it will output
    #   ``G1 X100``. The parameters G and M will always be put first. The
    #   parameters T and S will be put second (or first if there is no G or M).
    #   The rest of the parameters will be put in arbitrary order.
    #   \param line The original g-code line that must be modified. If not
    #   provided, an entirely new g-code line will be produced.
    #   \return A line of g-code with the desired parameters filled in.
    def putValue(self, line="", **kwargs):
        #Strip the comment.
        comment = ""
        if ";" in line:
            comment = line[line.find(";"):]
            line = line[:line.find(";")]  #Strip the comment.

        #Parse the original g-code line.
        for part in line.split(" "):
            if part == "":
                continue
            parameter = part[0]
            if parameter in kwargs:
                continue  #Skip this one. The user-provided parameter overwrites the one in the line.
            value = part[1:]
            kwargs[parameter] = value

        #Write the new g-code line.
        result = ""
        priority_parameters = [
            "G", "M", "T", "S", "F", "X", "Y", "Z", "E"
        ]  #First some parameters that get priority. In order of priority!
        for priority_key in priority_parameters:
            if priority_key in kwargs:
                if result != "":
                    result += " "
                result += priority_key + str(kwargs[priority_key])
                del kwargs[priority_key]
        for key, value in kwargs.items():
            if result != "":
                result += " "
            result += key + str(value)

        #Put the comment back in.
        if comment != "":
            if result != "":
                result += " "
            result += ";" + comment

        return result

    ##  This is called when the script is executed.
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data):
        raise NotImplementedError()
class Script:
    def __init__(self):
        super().__init__()
        self._settings = None
        self._stack = None

        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id=id(self))
        self._stack.setDirty(False)  # This stack does not need to be saved.


        ## Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                self._definition.deserialize(json.dumps(setting_data))
                ContainerRegistry.getInstance().addContainer(self._definition)
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition)
        self._stack.addContainer(self._instance)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()

    ##  Needs to return a dict that can be used to construct a settingcategory file. 
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    def getSettingData(self):
        raise NotImplementedError()

    def getDefinitionId(self):
        if self._stack:
            return self._stack.getBottom().getId()

    def getStackId(self):
        if self._stack:
            return self._stack.getId()

    ##  Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key):
        return self._stack.getProperty(key, "value")

    ##  Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line, key, default = None):
        if not key in line or (';' in line and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return float(m.group(0))
        except:
            return default

    ##  This is called when the script is executed. 
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data):
        raise NotImplementedError()
Exemple #5
0
class Script:
    def __init__(self):
        super().__init__()
        self._settings = None
        self._stack = None

        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id = str(id(self)))
        self._stack.setDirty(False)  # This stack does not need to be saved.


        ## Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                self._definition.deserialize(json.dumps(setting_data))
                ContainerRegistry.getInstance().addContainer(self._definition)
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.addMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0))
        self._stack.addContainer(self._instance)
        self._stack.propertyChanged.connect(self._onPropertyChanged)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()
    valueChanged = Signal()  # Signal emitted whenever a value of a setting is changed

    def _onPropertyChanged(self, key, property_name):
        if property_name == "value":
            self.valueChanged.emit()

            # Property changed: trigger reslice
            # To do this we use the global container stack propertyChanged.
            # Reslicing is necessary for setting changes in this plugin, because the changes
            # are applied only once per "fresh" gcode
            global_container_stack = Application.getInstance().getGlobalContainerStack()
            global_container_stack.propertyChanged.emit(key, property_name)

    ##  Needs to return a dict that can be used to construct a settingcategory file.
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    #   Scripts can either override getSettingData directly, or use getSettingDataString
    #   to return a string that will be parsed as json. The latter has the benefit over
    #   returning a dict in that the order of settings is maintained.
    def getSettingData(self):
        setting_data = self.getSettingDataString()
        if type(setting_data) == str:
            setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict)
        return setting_data

    def getSettingDataString(self):
        raise NotImplementedError()

    def getDefinitionId(self):
        if self._stack:
            return self._stack.getBottom().getId()

    def getStackId(self):
        if self._stack:
            return self._stack.getId()

    ##  Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key):
        return self._stack.getProperty(key, "value")

    ##  Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line, key, default = None):
        if not key in line or (';' in line and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return float(m.group(0))
        except:
            return default

    ##  This is called when the script is executed. 
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data):
        raise NotImplementedError()
Exemple #6
0
class Script:
    def __init__(self) -> None:
        super().__init__()
        self._stack = None  # type: Optional[ContainerStack]
        self._definition = None  # type: Optional[DefinitionContainerInterface]
        self._instance = None  # type: Optional[InstanceContainer]

    def initialize(self) -> None:
        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id=str(id(self)))
        self._stack.setDirty(False)  # This stack does not need to be saved.

        #  Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance(
            ).findDefinitionContainers(id=setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                try:
                    self._definition.deserialize(json.dumps(setting_data))
                    ContainerRegistry.getInstance().addContainer(
                        self._definition)
                except ContainerFormatError:
                    self._definition = None
                    return
        if self._definition is None:
            return
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(
            container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.setMetaDataEntry(
            "setting_version",
            self._definition.getMetaDataEntry("setting_version", default=0))
        self._stack.addContainer(self._instance)
        self._stack.propertyChanged.connect(self._onPropertyChanged)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()
    valueChanged = Signal(
    )  # Signal emitted whenever a value of a setting is changed

    def _onPropertyChanged(self, key: str, property_name: str) -> None:
        if property_name == "value":
            self.valueChanged.emit()

            # Property changed: trigger reslice
            # To do this we use the global container stack propertyChanged.
            # Re-slicing is necessary for setting changes in this plugin, because the changes
            # are applied only once per "fresh" gcode
            global_container_stack = Application.getInstance(
            ).getGlobalContainerStack()
            if global_container_stack is not None:
                global_container_stack.propertyChanged.emit(key, property_name)

    #   Needs to return a dict that can be used to construct a settingcategory file.
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    #   Scripts can either override getSettingData directly, or use getSettingDataString
    #   to return a string that will be parsed as json. The latter has the benefit over
    #   returning a dict in that the order of settings is maintained.
    def getSettingData(self) -> Dict[str, Any]:
        setting_data_as_string = self.getSettingDataString()
        setting_data = json.loads(setting_data_as_string,
                                  object_pairs_hook=collections.OrderedDict)
        return setting_data

    def getSettingDataString(self) -> str:
        raise NotImplementedError()

    def getDefinitionId(self) -> Optional[str]:
        if self._stack:
            bottom = self._stack.getBottom()
            if bottom is not None:
                return bottom.getId()
        return None

    def getStackId(self) -> Optional[str]:
        if self._stack:
            return self._stack.getId()
        return None

    #   Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key: str) -> Any:
        if self._stack is not None:
            return self._stack.getProperty(key, "value")
        return None

    #   Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line: str, key: str, default=None) -> Any:
        if key not in line or (';' in line
                               and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return int(m.group(0))
        except ValueError:  # Not an integer.
            try:
                return float(m.group(0))
            except ValueError:  # Not a number at all.
                return default

    #   Convenience function to produce a line of g-code.
    #
    #   You can put in an original g-code line and it will re-use all the values in that line.
    #   All other keyword parameters are put in the result in g-code's format.
    #   For instance, if you put "G=1" in the parameters, it will output "G1".
    #   If you put "G=1", X=100" in the parameters, it will output "G1 X100".
    #   The parameters will be put in this order "G", "M", "T", "S", "F", "X", "Y", "Z", "E"
    #   followed by any other parameters
    #   \param line The original g-code line that must be modified. If not provided, an entirely new g-code line will be produced.
    #   \return A line of g-code with the desired parameters filled in.
    def putValue(self, line: str = "", **kwargs) -> str:
        # Strip the comment.
        comment = ""
        if ";" in line:
            comment = line[line.find(";"):]
            line = line[:line.find(";")]  # Strip the comment.

        # Parse the original g-code line and add them to kwargs.
        for part in line.split(" "):
            if part == "":
                continue
            parameter = part[0]
            if parameter not in kwargs:
                value = part[1:]
                kwargs[parameter] = value

        # Start writing the new g-code line.
        line_parts = list()
        # First add these parameters in order
        for parameter in ["G", "M", "T", "S", "F", "X", "Y", "Z", "E"]:
            if parameter in kwargs:
                line_parts.append(parameter + str(kwargs.pop(parameter)))
        # Then add the rest of the parameters
        for parameter, value in kwargs.items():
            line_parts.append(parameter + str(value))

        # If there was a comment, put it back in.
        if comment != "":
            line_parts.append(comment)

        # Construct the new line
        return " ".join(line_parts)

    #   This is called when the script is executed.
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data: List[str]) -> List[str]:
        raise NotImplementedError()
Exemple #7
0
    def setNextStack(self, stack: ContainerStack) -> None:
        super().setNextStack(stack)
        stack.addExtruder(self)
        self.addMetaDataEntry("machine", stack.id)

        # 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.
        #
        # MORE:
        # For single-extrusion machines, nozzle size is saved in the global stack, so the nozzle size value should be
        # carried to the first extruder.
        # For material diameter, it was supposed to be applied to all extruders, so its value should be copied to all
        # extruders.

        keys_to_copy = ["material_diameter", "machine_nozzle_size"
                        ]  # these will be copied over to all extruders

        for key in keys_to_copy:
            # Only copy the value when this extruder doesn't have the value.
            if self.definitionChanges.hasProperty(key, "value"):
                continue

            # WARNING: this might be very dangerous and should be refactored ASAP!
            #
            # We cannot add a setting definition of "material_diameter" into the extruder's definition at runtime
            # because all other machines which uses "fdmextruder" as the extruder definition will be affected.
            #
            # The problem is that single extrusion machines have their default material diameter defined in the global
            # definitions. Now we automatically create an extruder stack for those machines using "fdmextruder"
            # definition, which doesn't have the specific "material_diameter" and "machine_nozzle_size" defined for
            # each machine. This results in wrong values which can be found in the MachineSettings dialog.
            #
            # To solve this, we put "material_diameter" back into the "fdmextruder" definition because modifying it in
            # the extruder definition will affect all machines which uses the "fdmextruder" definition. Moreover, now
            # we also check the value defined in the machine definition. If present, the value defined in the global
            # stack's definition changes container will be copied. Otherwise, we will check if the default values in the
            # machine definition and the extruder definition are the same, and if not, the default value in the machine
            # definition will be copied to the extruder stack's definition changes.
            #
            setting_value_in_global_def_changes = stack.definitionChanges.getProperty(
                key, "value")
            setting_value_in_global_def = stack.definition.getProperty(
                key, "value")
            setting_value = setting_value_in_global_def
            if setting_value_in_global_def_changes is not None:
                setting_value = setting_value_in_global_def_changes
            if setting_value == self.definition.getProperty(key, "value"):
                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)

            # Make sure the material diameter is up to date for the extruder stack.
            if key == "material_diameter":
                from cura.CuraApplication import CuraApplication
                machine_manager = CuraApplication.getInstance(
                ).getMachineManager()
                position = self.getMetaDataEntry("position", "0")
                func = lambda p=position: CuraApplication.getInstance(
                ).getExtruderManager().updateMaterialForDiameter(p)
                machine_manager.machine_extruder_material_update_dict[
                    stack.getId()].append(func)