예제 #1
0
    def _materialRemoved(self, container: ContainerInterface) -> None:
        if container.getMetaDataEntry("type") != "material":
            return  # Only interested in materials.
        base_file = container.getMetaDataEntry("base_file")
        if base_file not in self.materials:
            return  # We don't track this material anyway. No need to remove it.

        original_node = self.materials[base_file]
        del self.materials[base_file]
        self.materialsChanged.emit(original_node)

        # Now a different material from the same base file may have been hidden because it was not as specific as the one we deleted.
        # Search for any submaterials from that base file that are still left.
        materials_same_base_file = ContainerRegistry.getInstance().findContainersMetadata(base_file = base_file)
        if materials_same_base_file:
            most_specific_submaterial = None
            for submaterial in materials_same_base_file:
                if submaterial["definition"] == self.machine.container_id:
                    if submaterial.get("variant_name", "empty") == self.variant_name:
                        most_specific_submaterial = submaterial
                        break  # most specific match possible
                    if submaterial.get("variant_name", "empty") == "empty":
                        most_specific_submaterial = submaterial

            if most_specific_submaterial is None:
                Logger.log("w", "Material %s removed, but no suitable replacement found", base_file)
            else:
                Logger.log("i", "Material %s (%s) overridden by %s", base_file, self.variant_name, most_specific_submaterial.get("id"))
                self.materials[base_file] = MaterialNode(most_specific_submaterial["id"], variant = self)
                self.materialsChanged.emit(self.materials[base_file])

        if not self.materials:  # The last available material just got deleted and there is nothing with the same base file to replace it.
            self.materials["empty_material"] = MaterialNode("empty_material", variant = self)
            self.materialsChanged.emit(self.materials["empty_material"])
예제 #2
0
    def _onMetadataChanged(self, container: ContainerInterface,
                           **kwargs: Any) -> None:
        """Triggered when any metadata changed in any container, but only handles it when the metadata of this node is
        changed.

        :param container: The container whose metadata changed.
        :param kwargs: Key-word arguments provided when changing the metadata. These are ignored. As far as I know they
        are never provided to this call.
        """

        if container.getId() != self.container_id:
            return

        new_metadata = container.getMetaData()
        old_base_file = self.base_file
        if new_metadata["base_file"] != old_base_file:
            self.base_file = new_metadata["base_file"]
            if old_base_file in self.variant.materials:  # Move in parent node.
                del self.variant.materials[old_base_file]
            self.variant.materials[self.base_file] = self

        old_material_type = self.material_type
        self.material_type = new_metadata["material"]
        old_guid = self.guid
        self.guid = new_metadata["GUID"]
        if self.base_file != old_base_file or self.material_type != old_material_type or self.guid != old_guid:  # List of quality profiles could've changed.
            self.qualities = {}
            self._loadAll()  # Re-load the quality profiles for this node.
        self.materialChanged.emit(self)
예제 #3
0
    def replaceContainer(self,
                         index: int,
                         container: ContainerInterface,
                         postpone_emit: bool = False) -> None:
        """Overridden from ContainerStack

        Replaces the container at the specified index with another container.
        This version performs checks to make sure the new container has the expected metadata and type.

        :throws Exception.InvalidContainerError Raised when trying to replace a container with a container that has an incorrect type.
        """

        expected_type = _ContainerIndexes.IndexTypeMap[index]
        if expected_type == "definition":
            if not isinstance(container, DefinitionContainer):
                raise Exceptions.InvalidContainerError(
                    "Cannot replace container at index {index} with a container that is not a DefinitionContainer"
                    .format(index=index))
        elif container != self._empty_instance_container and container.getMetaDataEntry(
                "type") != expected_type:
            raise Exceptions.InvalidContainerError(
                "Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type."
                .format(index=index,
                        type=expected_type,
                        actual_type=container.getMetaDataEntry("type")))

        current_container = self._containers[index]
        if current_container.getId() == container.getId():
            return

        super().replaceContainer(index, container, postpone_emit)
예제 #4
0
    def addContainer(self, container: ContainerInterface) -> None:
        containers = self.findContainers(container_type = container.__class__, id = container.getId())
        if containers:
            Logger.log("w", "Container of type %s and id %s already added", repr(container.__class__), container.getId())
            return

        if hasattr(container, "metaDataChanged"):
            container.metaDataChanged.connect(self._onContainerMetaDataChanged)

        self._containers.append(container)
        self._id_container_cache[container.getId()] = container
        self._clearQueryCache()
        self.containerAdded.emit(container)
예제 #5
0
    def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None:
        expected_type = _ContainerIndexes.IndexTypeMap[index]
        if expected_type == "definition":
            if not isinstance(container, DefinitionContainer):
                raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not a DefinitionContainer".format(index = index))
        elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
            raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type")))

        current_container = self._containers[index]
        if current_container.getId() == container.getId():
            return

        super().replaceContainer(index, container, postpone_emit)
예제 #6
0
    def addContainer(self, container: ContainerInterface) -> None:
        containers = self.findContainers(container_type = container.__class__, id = container.getId())
        if containers:
            Logger.log("w", "Container of type %s and id %s already added", repr(container.__class__), container.getId())
            return

        if hasattr(container, "metaDataChanged"):
            container.metaDataChanged.connect(self._onContainerMetaDataChanged)

        self._containers.append(container)
        self._id_container_cache[container.getId()] = container
        self._clearQueryCache()
        self.containerAdded.emit(container)
예제 #7
0
    def _onContainerAdded(self, container: ContainerInterface) -> None:
        # We don't have all the machines loaded in the beginning, so in order to add the missing extruder stack
        # for single extrusion machines, we subscribe to the containerAdded signal, and whenever a global stack
        # is added, we check to see if an extruder stack needs to be added.
        if not isinstance(container, ContainerStack) or container.getMetaDataEntry("type") != "machine":
            return

        machine_extruder_trains = container.getMetaDataEntry("machine_extruder_trains")
        if machine_extruder_trains is not None and machine_extruder_trains != {"0": "fdmextruder"}:
            return

        extruder_stacks = self.findContainerStacks(type = "extruder_train", machine = container.getId())
        if not extruder_stacks:
            self.addExtruderStackForSingleExtrusionMachine(container, "fdmextruder")
예제 #8
0
    def addContainer(self, container: ContainerInterface) -> None:
        containers = self.findContainers(container_type = container.__class__, id = container.getId())
        if containers:
            Logger.log("w", "Container of type %s and id %s already added", repr(container.__class__), container.getId())
            return

        if hasattr(container, "metaDataChanged"):
            # Since queries are based on metadata, we need to make sure to clear the cache when a container's metadata changes.
            container.metaDataChanged.connect(self._clearQueryCache)

        self._containers.append(container)
        self._id_container_cache[container.getId()] = container
        self._clearQueryCache()
        self.containerAdded.emit(container)
예제 #9
0
    def __call__(
        self,
        value_provider: ContainerInterface,
        context: Optional[PropertyEvaluationContext] = None
    ) -> Optional[ValidatorState]:
        if not value_provider:
            return None

        state = ValidatorState.Unknown
        try:
            minimum = value_provider.getProperty(self._key, "minimum_value")
            maximum = value_provider.getProperty(self._key, "maximum_value")
            minimum_warning = value_provider.getProperty(
                self._key, "minimum_value_warning")
            maximum_warning = value_provider.getProperty(
                self._key, "maximum_value_warning")

            if minimum is not None and maximum is not None and minimum > maximum:
                raise ValueError(
                    "Cannot validate a state of setting {0} with minimum > maximum"
                    .format(self._key))

            if context is not None:
                value_provider = context.rootStack()

            value = value_provider.getProperty(self._key, "value")
            if value is None or value != value:
                raise ValueError(
                    "Cannot validate None, NaN or similar values in setting {0}, actual value: {1}"
                    .format(self._key, value))

            if minimum is not None and value < minimum:
                state = ValidatorState.MinimumError
            elif maximum is not None and value > maximum:
                state = ValidatorState.MaximumError
            elif minimum_warning is not None and value < minimum_warning:
                state = ValidatorState.MinimumWarning
            elif maximum_warning is not None and value > maximum_warning:
                state = ValidatorState.MaximumWarning
            else:
                state = ValidatorState.Valid
        except:
            Logger.logException(
                "w", "Could not validate setting %s, an exception was raised",
                self._key)
            state = ValidatorState.Exception

        return state
예제 #10
0
    def __call__(self, value_provider: ContainerInterface) -> Any:
        if not value_provider:
            return None

        if not self._valid:
            return None

        locals = {}  # type: Dict[str, Any]
        for name in self._settings:
            value = value_provider.getProperty(name, "value")
            if value is None:
                continue

            locals[name] = value

        g = {}  # type: Dict[str, Any]
        g.update(globals())
        g.update(self.__operators)

        try:
            return eval(self._compiled, g, locals)
        except Exception as e:
            Logger.logException(
                "d", "An exception occurred in inherit function %s", self)
            return 0  # Settings may be used in calculations and they need a value
예제 #11
0
    def addContainer(self, container: ContainerInterface) -> None:
        container_id = container.getId()
        if container_id in self._containers:
            Logger.log("w", "Container with ID %s was already added.", container_id)
            return

        if hasattr(container, "metaDataChanged"):
            container.metaDataChanged.connect(self._onContainerMetaDataChanged)

        self.metadata[container_id] = container.getMetaData()
        self._containers[container_id] = container
        if container_id not in self.source_provider:
            self.source_provider[container_id] = None #Added during runtime.
        self._clearQueryCacheByContainer(container)
        self.containerAdded.emit(container)
        Logger.log("d", "Container [%s] added.", container_id)
예제 #12
0
    def __call__(self,
                 value_provider: ContainerInterface,
                 context: Optional[PropertyEvaluationContext] = None) -> Any:
        if not value_provider:
            return None

        if not self._valid:
            return None

        locals = {}  # type: Dict[str, Any]
        # if there is a context, evaluate the values from the perspective of the original caller
        if context is not None:
            value_provider = context.rootStack()
        for name in self._used_values:
            value = value_provider.getProperty(name, "value", context)
            if value is None:
                continue

            locals[name] = value

        g = {}  # type: Dict[str, Any]
        g.update(globals())
        g.update(self.__operators)
        # override operators if there is any in the context
        if context is not None:
            g.update(context.context.get("override_operators", {}))

        try:
            return eval(self._compiled, g, locals)
        except Exception as e:
            Logger.logException(
                "d", "An exception occurred in inherit function %s", self)
            return 0  # Settings may be used in calculations and they need a value
예제 #13
0
    def __call__(self, value_provider: ContainerInterface, context: Optional[PropertyEvaluationContext] = None) -> Any:
        if not value_provider:
            return None

        if not self._valid:
            return None

        locals = {} # type: Dict[str, Any]
        # if there is a context, evaluate the values from the perspective of the original caller
        if context is not None:
            value_provider = context.rootStack()
        for name in self._used_values:
            value = value_provider.getProperty(name, "value", context)
            if value is None:
                continue

            locals[name] = value

        g = {}  # type: Dict[str, Any]
        g.update(globals())
        g.update(self.__operators)
        # override operators if there is any in the context
        if context is not None:
            g.update(context.context.get("override_operators", {}))

        try:
            if self._compiled:
                return eval(self._compiled, g, locals)
            Logger.log("e", "An error ocurred evaluating the function {0}.".format(self))
            return 0
        except Exception as e:
            Logger.logException("d", "An exception occurred in inherit function {0}: {1}".format(self, str(e)))
            return 0  # Settings may be used in calculations and they need a value
예제 #14
0
 def _onRemoved(self, container: ContainerInterface) -> None:
     if container.getId() == self.container_id:
         # Remove myself from my parent.
         if self.base_file in self.variant.materials:
             del self.variant.materials[self.base_file]
             if not self.variant.materials:
                 self.variant.materials["empty_material"] = MaterialNode("empty_material", variant = self.variant)
         self.materialChanged.emit(self)
예제 #15
0
    def _materialAdded(self, container: ContainerInterface) -> None:
        if container.getMetaDataEntry("type") != "material":
            return  # Not interested.
        if not self.machine.has_materials:
            return  # We won't add any materials.
        material_definition = container.getMetaDataEntry("definition")

        base_file = container.getMetaDataEntry("base_file")
        if base_file in self.machine.exclude_materials:
            return  # Material is forbidden for this printer.
        if base_file not in self.materials:  # Completely new base file. Always better than not having a file as long as it matches our set-up.
            if material_definition != "fdmprinter" and material_definition != self.machine.container_id:
                return
            material_variant = container.getMetaDataEntry("variant_name", "empty")
            if material_variant != "empty" and material_variant != self.variant_name:
                return
        else:  # We already have this base profile. Replace the base profile if the new one is more specific.
            new_definition = container.getMetaDataEntry("definition")
            if new_definition == "fdmprinter":
                return  # Just as unspecific or worse.
            if new_definition != self.machine.container_id:
                return  # Doesn't match this set-up.
            original_metadata = ContainerRegistry.getInstance().findContainersMetadata(id = self.materials[base_file].container_id)[0]
            original_variant = original_metadata.get("variant_name", "empty")
            if original_variant != "empty" or container.getMetaDataEntry("variant_name", "empty") == "empty":
                return  # Original was already specific or just as unspecific as the new one.

        if "empty_material" in self.materials:
            del self.materials["empty_material"]
        self.materials[base_file] = MaterialNode(container.getId(), variant = self)
        self.materials[base_file].materialChanged.connect(self.materialsChanged)
        self.materialsChanged.emit(self.materials[base_file])
예제 #16
0
    def _materialAdded(self, container: ContainerInterface) -> None:
        if container.getMetaDataEntry("type") != "material":
            return  # Not interested.
        if not ContainerRegistry.getInstance().findContainersMetadata(
                id=container.getId()):
            # CURA-6889
            # containerAdded and removed signals may be triggered in the next event cycle. If a container gets added
            # and removed in the same event cycle, in the next cycle, the connections should just ignore the signals.
            # The check here makes sure that the container in the signal still exists.
            Logger.log(
                "d",
                "Got container added signal for container [%s] but it no longer exists, do nothing.",
                container.getId())
            return
        if not self.machine.has_materials:
            return  # We won't add any materials.
        material_definition = container.getMetaDataEntry("definition")

        base_file = container.getMetaDataEntry("base_file")
        if base_file in self.machine.exclude_materials:
            return  # Material is forbidden for this printer.
        if base_file not in self.materials:  # Completely new base file. Always better than not having a file as long as it matches our set-up.
            if material_definition != "fdmprinter" and material_definition != self.machine.container_id:
                return
            material_variant = container.getMetaDataEntry("variant_name")
            if material_variant is not None and material_variant != self.variant_name:
                return
        else:  # We already have this base profile. Replace the base profile if the new one is more specific.
            new_definition = container.getMetaDataEntry("definition")
            if new_definition == "fdmprinter":
                return  # Just as unspecific or worse.
            material_variant = container.getMetaDataEntry("variant_name")
            if new_definition != self.machine.container_id or material_variant != self.variant_name:
                return  # Doesn't match this set-up.
            original_metadata = ContainerRegistry.getInstance(
            ).findContainersMetadata(
                id=self.materials[base_file].container_id)[0]
            if "variant_name" in original_metadata or material_variant is None:
                return  # Original was already specific or just as unspecific as the new one.

        if "empty_material" in self.materials:
            del self.materials["empty_material"]
        self.materials[base_file] = MaterialNode(container.getId(),
                                                 variant=self)
        self.materials[base_file].materialChanged.connect(
            self.materialsChanged)
        self.materialsChanged.emit(self.materials[base_file])
예제 #17
0
  def _on_printer_container_removed(self,
                                    container: ContainerInterface) -> None:
    """Removes device if it is managed by this plugin.

    Called when the user deletes a printer.

    Args:
      container: deleted container.
    """
    device_id = container.getMetaDataEntry(_METADATA_MPSM2_KEY)
    self.remove_device(device_id, _get_address(device_id))
예제 #18
0
    def addContainer(self, container: ContainerInterface) -> None:
        container_id = container.getId()
        if container_id in self._containers:
            return

        if hasattr(container, "metaDataChanged"):
            container.metaDataChanged.connect(self._onContainerMetaDataChanged)

        self.metadata[container_id] = container.getMetaData()
        self._containers[container_id] = container
        if container_id not in self.source_provider:
            self.source_provider[container_id] = None #Added during runtime.
        self._clearQueryCacheByContainer(container)

        # containerAdded is a custom signal and can trigger direct calls to its subscribers. This should be avoided
        # because with the direct calls, the subscribers need to know everything about what it tries to do to avoid
        # triggering this signal again, which eventually can end up exceeding the max recursion limit.
        # We avoid the direct calls here to make sure that the subscribers do not need to take into account any max
        # recursion problem.
        self._application.callLater(self.containerAdded.emit, container)
예제 #19
0
    def _onMetadataChanged(self, container: ContainerInterface, **kwargs: Any) -> None:
        if container.getId() != self.container_id:
            return

        new_metadata = container.getMetaData()
        old_base_file = self.base_file
        if new_metadata["base_file"] != old_base_file:
            self.base_file = new_metadata["base_file"]
            if old_base_file in self.variant.materials:  # Move in parent node.
                del self.variant.materials[old_base_file]
            self.variant.materials[self.base_file] = self

        old_material_type = self.material_type
        self.material_type = new_metadata["material"]
        old_guid = self.guid
        self.guid = new_metadata["GUID"]
        if self.base_file != old_base_file or self.material_type != old_material_type or self.guid != old_guid:  # List of quality profiles could've changed.
            self.qualities = {}
            self._loadAll()  # Re-load the quality profiles for this node.
        self.materialChanged.emit(self)
예제 #20
0
    def __call__(self, value_provider: ContainerInterface, context: Optional[PropertyEvaluationContext] = None) -> Optional[ValidatorState]:
        if not value_provider:
            return None

        state = ValidatorState.Unknown
        try:
            allow_empty = value_provider.getProperty(self._key, "allow_empty")  # For string only
            minimum = value_provider.getProperty(self._key, "minimum_value")
            maximum = value_provider.getProperty(self._key, "maximum_value")
            minimum_warning = value_provider.getProperty(self._key, "minimum_value_warning")
            maximum_warning = value_provider.getProperty(self._key, "maximum_value_warning")

            if minimum is not None and maximum is not None and minimum > maximum:
                raise ValueError("Cannot validate a state of setting {0} with minimum > maximum".format(self._key))

            if context is not None:
                value_provider = context.rootStack()

            value = value_provider.getProperty(self._key, "value")
            if value is None or value != value:
                raise ValueError("Cannot validate None, NaN or similar values in setting {0}, actual value: {1}".format(self._key, value))

            # "allow_empty is not None" is not necessary here because of "allow_empty is False", but it states
            # explicitly that we should not do this check when "allow_empty is None".
            if allow_empty is not None and allow_empty is False and str(value) == "":
                state = ValidatorState.Invalid
            elif minimum is not None and value < minimum:
                state = ValidatorState.MinimumError
            elif maximum is not None and value > maximum:
                state = ValidatorState.MaximumError
            elif minimum_warning is not None and value < minimum_warning:
                state = ValidatorState.MinimumWarning
            elif maximum_warning is not None and value > maximum_warning:
                state = ValidatorState.MaximumWarning
            else:
                state = ValidatorState.Valid
        except:
            Logger.logException("w", "Could not validate setting %s, an exception was raised", self._key)
            state = ValidatorState.Exception

        return state
예제 #21
0
    def _clearQueryCacheByContainer(self, container: ContainerInterface) -> None:
        # Remove all case-insensitive matches since we won't find those with the below "<=" subset check.
        # TODO: Properly check case-insensitively in the dict's values.
        for key in list(ContainerQuery.ContainerQuery.cache):
            if not key[0]:
                del ContainerQuery.ContainerQuery.cache[key]

        # Remove all cache items that this container could fall in.
        for key in list(ContainerQuery.ContainerQuery.cache):
            query_metadata = dict(zip(key[1::2], key[2::2]))
            if query_metadata.items() <= container.getMetaData().items():
                del ContainerQuery.ContainerQuery.cache[key]
예제 #22
0
    def _printerRemoved(self, container: ContainerInterface) -> None:
        """
        Callback connected to the containerRemoved signal. Invoked when a cloud printer is removed from Cura to remove
        the printer's reference from the _remote_clusters.

        :param container: The ContainerInterface passed to this function whenever the ContainerRemoved signal is emitted
        :return: None
        """
        if isinstance(container, GlobalStack):
            container_cluster_id = container.getMetaDataEntry(self.META_CLUSTER_ID, None)
            if container_cluster_id in self._remote_clusters.keys():
                del self._remote_clusters[container_cluster_id]
예제 #23
0
    def addContainer(self, container: ContainerInterface) -> None:
        container_id = container.getId()
        if container_id in self._containers:
            Logger.log("w", "Container with ID %s was already added.", container_id)
            return

        if hasattr(container, "metaDataChanged"):
            container.metaDataChanged.connect(self._onContainerMetaDataChanged)

        self.metadata[container_id] = container.getMetaData()
        self._containers[container_id] = container
        if container_id not in self.source_provider:
            self.source_provider[container_id] = None #Added during runtime.
        self._clearQueryCacheByContainer(container)
        Logger.log("d", "Container [%s] added.", container_id)

        # containerAdded is a custom signal and can trigger direct calls to its subscribers. This should be avoided
        # because with the direct calls, the subscribers need to know everything about what it tries to do to avoid
        # triggering this signal again, which eventually can end up exceeding the max recursion limit.
        # We avoid the direct calls here to make sure that the subscribers do not need to take into account any max
        # recursion problem.
        self._application.callLater(self.containerAdded.emit, container)
예제 #24
0
    def addContainer(self, container: ContainerInterface) -> None:
        # Note: Intentional check with type() because we want to ignore subclasses
        if type(container) == ContainerStack:
            container = self._convertContainerStack(
                cast(ContainerStack, container))

        if isinstance(container,
                      InstanceContainer) and type(container) != type(
                          self.getEmptyInstanceContainer()):
            # Check against setting version of the definition.
            required_setting_version = cura.CuraApplication.CuraApplication.SettingVersion
            actual_setting_version = int(
                container.getMetaDataEntry("setting_version", default=0))
            if required_setting_version != actual_setting_version:
                Logger.log(
                    "w",
                    "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}."
                    .format(container_id=container.getId(),
                            actual_setting_version=actual_setting_version,
                            required_setting_version=required_setting_version))
                return  # Don't add.

        super().addContainer(container)
예제 #25
0
    def setProperty(self,
                    name: str,
                    value: Any,
                    container: ContainerInterface = None):
        if SettingDefinition.hasProperty(name):
            if SettingDefinition.isReadOnlyProperty(name):
                Logger.log(
                    "e",
                    "Tried to set property %s which is a read-only property",
                    name)
                return

            if name not in self.__property_values or value != self.__property_values[
                    name]:
                if isinstance(value, str) and value.strip().startswith("="):
                    value = SettingFunction.SettingFunction(value[1:])

                self.__property_values[name] = value
                if name == "value":
                    if not container:
                        container = self._container
                    ## If state changed, emit the signal
                    if self._state != InstanceState.User and container.getMetaDataEntry(
                            "type") == "user":
                        self._state = InstanceState.User
                        self.propertyChanged.emit(self._definition.key,
                                                  "state")

                    self.updateRelations(container)

                if self._validator:
                    self.propertyChanged.emit(self._definition.key,
                                              "validationState")

                self.propertyChanged.emit(self._definition.key, name)
                for property_name in self._definition.getPropertyNames():
                    if self._definition.dependsOnProperty(
                            property_name) == name:
                        self.propertyChanged.emit(self._definition.key,
                                                  property_name)
        else:
            if name == "state":
                if value == "InstanceState.Calculated":
                    if self._state != InstanceState.Calculated:
                        self._state = InstanceState.Calculated
                        self.propertyChanged.emit(self._definition.key,
                                                  "state")
            else:
                raise AttributeError("No property {0} defined".format(name))
예제 #26
0
    def __call__(self, value_provider: ContainerInterface, context: Optional[PropertyEvaluationContext] = None) -> Optional[ValidatorState]:
        if not value_provider:
            return None

        state = ValidatorState.Unknown
        try:
            minimum = value_provider.getProperty(self._key, "minimum_value")
            maximum = value_provider.getProperty(self._key, "maximum_value")
            minimum_warning = value_provider.getProperty(self._key, "minimum_value_warning")
            maximum_warning = value_provider.getProperty(self._key, "maximum_value_warning")

            if minimum is not None and maximum is not None and minimum > maximum:
                raise ValueError("Cannot validate a state of setting {0} with minimum > maximum".format(self._key))

            if context is not None:
                value_provider = context.rootStack()

            value = value_provider.getProperty(self._key, "value")
            if value is None or value != value:
                raise ValueError("Cannot validate None, NaN or similar values in setting {0}, actual value: {1}".format(self._key, value))

            if minimum is not None and value < minimum:
                state = ValidatorState.MinimumError
            elif maximum is not None and value > maximum:
                state = ValidatorState.MaximumError
            elif minimum_warning is not None and value < minimum_warning:
                state = ValidatorState.MinimumWarning
            elif maximum_warning is not None and value > maximum_warning:
                state = ValidatorState.MaximumWarning
            else:
                state = ValidatorState.Valid
        except:
            Logger.logException("w", "Could not validate setting %s, an exception was raised", self._key)
            state = ValidatorState.Exception

        return state
예제 #27
0
    def _onRemoved(self, container: ContainerInterface) -> None:
        """Triggered when any container is removed, but only handles it when the container is removed that this node
        represents.

        :param container: The container that was allegedly removed.
        """

        if container.getId() == self.container_id:
            # Remove myself from my parent.
            if self.base_file in self.variant.materials:
                del self.variant.materials[self.base_file]
                if not self.variant.materials:
                    self.variant.materials["empty_material"] = MaterialNode(
                        "empty_material", variant=self.variant)
            self.materialChanged.emit(self)
예제 #28
0
    def __call__(self,
                 value_provider: ContainerInterface,
                 context: Optional[PropertyEvaluationContext] = None) -> Any:
        """Call the actual function to calculate the value.

        :param value_provider: The container from which to get setting values in the formula.
        :param context: The context in which the call needs to be executed
        """

        if not value_provider:
            return None

        if not self._valid:
            return None

        locals = {}  # type: Dict[str, Any]
        # If there is a context, evaluate the values from the perspective of the original caller
        if context is not None:
            value_provider = context.rootStack()
        for name in self._used_values:
            value = value_provider.getProperty(name, "value", context)
            if value is None:
                continue

            locals[name] = value

        g = {}  # type: Dict[str, Any]
        g.update(globals())
        g.update(self.__operators)
        # Override operators if there is any in the context
        if context is not None:
            g.update(context.context.get("override_operators", {}))

        try:
            if self._compiled:
                return eval(self._compiled, g, locals)
            Logger.log(
                "e",
                "An error occurred evaluating the function {0}.".format(self))
            return 0
        except Exception as e:
            Logger.logException(
                "d",
                "An exception occurred in inherit function {0}: {1}".format(
                    self, str(e)))
            return 0  # Settings may be used in calculations and they need a value
예제 #29
0
    def _clearQueryCacheByContainer(self, container: ContainerInterface) -> None:
        """Clear the query cache by using container type.
        This is a slightly smarter way of clearing the cache. Only queries that are of the same type (or without one)
        are cleared.
        """

        # Remove all case-insensitive matches since we won't find those with the below "<=" subset check.
        # TODO: Properly check case-insensitively in the dict's values.
        for key in list(ContainerQuery.ContainerQuery.cache):
            if not key[0]:
                del ContainerQuery.ContainerQuery.cache[key]

        # Remove all cache items that this container could fall in.
        for key in list(ContainerQuery.ContainerQuery.cache):
            query_metadata = dict(zip(key[1::2], key[2::2]))
            if query_metadata.items() <= container.getMetaData().items():
                del ContainerQuery.ContainerQuery.cache[key]
예제 #30
0
    def __call__(self, value_provider: ContainerInterface) -> Any:
        if not value_provider:
            return None

        if not self._valid:
            return None

        locals = {} # type: Dict[str, Any]
        for name in self._used_values:
            value = value_provider.getProperty(name, "value")
            if value is None:
                continue

            locals[name] = value

        g = {}  # type: Dict[str, Any]
        g.update(globals())
        g.update(self.__operators)

        try:
            return eval(self._compiled, g, locals)
        except Exception as e:
            Logger.logException("d", "An exception occurred in inherit function %s", self)
            return 0  # Settings may be used in calculations and they need a value
예제 #31
0
    def deserialize(self, serialized):
        # update the serialized data first
        from UM.Settings.Interfaces import ContainerInterface
        serialized = ContainerInterface.deserialize(self, serialized)

        try:
            data = ET.fromstring(serialized)
        except:
            Logger.logException(
                "e", "An exception occured while parsing the material profile")
            return

        # Reset previous metadata
        self.clearData()  # Ensure any previous data is gone.
        meta_data = {}
        meta_data["type"] = "material"
        meta_data["base_file"] = self.id
        meta_data["status"] = "unknown"  # TODO: Add material verfication

        common_setting_values = {}

        inherits = data.find("./um:inherits", self.__namespaces)
        if inherits is not None:
            inherited = self._resolveInheritance(inherits.text)
            data = self._mergeXML(inherited, data)

        if "version" in data.attrib:
            meta_data["setting_version"] = self.xmlVersionToSettingVersion(
                data.attrib["version"])
        else:
            meta_data["setting_version"] = self.xmlVersionToSettingVersion(
                "1.2"
            )  #1.2 and lower didn't have that version number there yet.
        metadata = data.iterfind("./um:metadata/*", self.__namespaces)
        for entry in metadata:
            tag_name = _tag_without_namespace(entry)

            if tag_name == "name":
                brand = entry.find("./um:brand", self.__namespaces)
                material = entry.find("./um:material", self.__namespaces)
                color = entry.find("./um:color", self.__namespaces)
                label = entry.find("./um:label", self.__namespaces)

                if label is not None:
                    self._name = label.text
                else:
                    self._name = self._profile_name(material.text, color.text)
                meta_data["brand"] = brand.text
                meta_data["material"] = material.text
                meta_data["color_name"] = color.text
                continue
            meta_data[tag_name] = entry.text

            if tag_name in self.__material_metadata_setting_map:
                common_setting_values[self.__material_metadata_setting_map[
                    tag_name]] = entry.text

        if "description" not in meta_data:
            meta_data["description"] = ""

        if "adhesion_info" not in meta_data:
            meta_data["adhesion_info"] = ""

        property_values = {}
        properties = data.iterfind("./um:properties/*", self.__namespaces)
        for entry in properties:
            tag_name = _tag_without_namespace(entry)
            property_values[tag_name] = entry.text

            if tag_name in self.__material_properties_setting_map:
                common_setting_values[self.__material_properties_setting_map[
                    tag_name]] = entry.text

        meta_data["approximate_diameter"] = str(
            round(float(property_values.get("diameter", 2.85))))  # In mm
        meta_data["properties"] = property_values

        self.setDefinition(
            ContainerRegistry.getInstance().findDefinitionContainers(
                id="fdmprinter")[0])

        common_compatibility = True
        settings = data.iterfind("./um:settings/um:setting", self.__namespaces)
        for entry in settings:
            key = entry.get("key")
            if key in self.__material_settings_setting_map:
                common_setting_values[
                    self.__material_settings_setting_map[key]] = entry.text
            elif key in self.__unmapped_settings:
                if key == "hardware compatible":
                    common_compatibility = parseBool(entry.text)
            else:
                Logger.log("d", "Unsupported material setting %s", key)
        self._cached_values = common_setting_values  # from InstanceContainer ancestor

        meta_data["compatible"] = common_compatibility
        self.setMetaData(meta_data)
        self._dirty = False

        machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
        for machine in machines:
            machine_compatibility = common_compatibility
            machine_setting_values = {}
            settings = machine.iterfind("./um:setting", self.__namespaces)
            for entry in settings:
                key = entry.get("key")
                if key in self.__material_settings_setting_map:
                    machine_setting_values[
                        self.__material_settings_setting_map[key]] = entry.text
                elif key in self.__unmapped_settings:
                    if key == "hardware compatible":
                        machine_compatibility = parseBool(entry.text)
                else:
                    Logger.log("d", "Unsupported material setting %s", key)

            cached_machine_setting_properties = common_setting_values.copy()
            cached_machine_setting_properties.update(machine_setting_values)

            identifiers = machine.iterfind("./um:machine_identifier",
                                           self.__namespaces)
            for identifier in identifiers:
                machine_id = self.__product_id_map.get(
                    identifier.get("product"), None)
                if machine_id is None:
                    # Lets try again with some naive heuristics.
                    machine_id = identifier.get("product").replace(" ",
                                                                   "").lower()

                definitions = ContainerRegistry.getInstance(
                ).findDefinitionContainers(id=machine_id)
                if not definitions:
                    Logger.log("w", "No definition found for machine ID %s",
                               machine_id)
                    continue

                definition = definitions[0]

                if machine_compatibility:
                    new_material_id = self.id + "_" + machine_id

                    new_material = XmlMaterialProfile(new_material_id)

                    # Update the private directly, as we want to prevent the lookup that is done when using setName
                    new_material._name = self.getName()
                    new_material.setMetaData(copy.deepcopy(self.getMetaData()))
                    new_material.setDefinition(definition)
                    # Don't use setMetadata, as that overrides it for all materials with same base file
                    new_material.getMetaData(
                    )["compatible"] = machine_compatibility

                    new_material.setCachedValues(
                        cached_machine_setting_properties)

                    new_material._dirty = False

                    ContainerRegistry.getInstance().addContainer(new_material)

                hotends = machine.iterfind("./um:hotend", self.__namespaces)
                for hotend in hotends:
                    hotend_id = hotend.get("id")
                    if hotend_id is None:
                        continue

                    variant_containers = ContainerRegistry.getInstance(
                    ).findInstanceContainers(id=hotend_id)
                    if not variant_containers:
                        # It is not really properly defined what "ID" is so also search for variants by name.
                        variant_containers = ContainerRegistry.getInstance(
                        ).findInstanceContainers(definition=definition.id,
                                                 name=hotend_id)

                    if not variant_containers:
                        #Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id)
                        continue

                    hotend_compatibility = machine_compatibility
                    hotend_setting_values = {}
                    settings = hotend.iterfind("./um:setting",
                                               self.__namespaces)
                    for entry in settings:
                        key = entry.get("key")
                        if key in self.__material_settings_setting_map:
                            hotend_setting_values[
                                self.__material_settings_setting_map[
                                    key]] = entry.text
                        elif key in self.__unmapped_settings:
                            if key == "hardware compatible":
                                hotend_compatibility = parseBool(entry.text)
                        else:
                            Logger.log("d", "Unsupported material setting %s",
                                       key)

                    new_hotend_id = self.id + "_" + machine_id + "_" + hotend_id.replace(
                        " ", "_")

                    new_hotend_material = XmlMaterialProfile(new_hotend_id)

                    # Update the private directly, as we want to prevent the lookup that is done when using setName
                    new_hotend_material._name = self.getName()
                    new_hotend_material.setMetaData(
                        copy.deepcopy(self.getMetaData()))
                    new_hotend_material.setDefinition(definition)
                    new_hotend_material.addMetaDataEntry(
                        "variant", variant_containers[0].id)
                    # Don't use setMetadata, as that overrides it for all materials with same base file
                    new_hotend_material.getMetaData(
                    )["compatible"] = hotend_compatibility

                    cached_hotend_setting_properties = cached_machine_setting_properties.copy(
                    )
                    cached_hotend_setting_properties.update(
                        hotend_setting_values)

                    new_hotend_material.setCachedValues(
                        cached_hotend_setting_properties)

                    new_hotend_material._dirty = False

                    ContainerRegistry.getInstance().addContainer(
                        new_hotend_material)
예제 #32
0
    def deserialize(self, serialized):
        # update the serialized data first
        from UM.Settings.Interfaces import ContainerInterface
        serialized = ContainerInterface.deserialize(self, serialized)
        data = ET.fromstring(serialized)

        # Reset previous metadata
        self.clearData() # Ensure any previous data is gone.
        meta_data = {}
        meta_data["type"] = "material"
        meta_data["base_file"] = self.id
        meta_data["status"] = "unknown"  # TODO: Add material verfication

        inherits = data.find("./um:inherits", self.__namespaces)
        if inherits is not None:
            inherited = self._resolveInheritance(inherits.text)
            data = self._mergeXML(inherited, data)

        metadata = data.iterfind("./um:metadata/*", self.__namespaces)
        for entry in metadata:
            tag_name = _tag_without_namespace(entry)

            if tag_name == "name":
                brand = entry.find("./um:brand", self.__namespaces)
                material = entry.find("./um:material", self.__namespaces)
                color = entry.find("./um:color", self.__namespaces)
                label = entry.find("./um:label", self.__namespaces)

                if label is not None:
                    self._name = label.text
                else:
                    self._name = self._profile_name(material.text, color.text)
                meta_data["brand"] = brand.text
                meta_data["material"] = material.text
                meta_data["color_name"] = color.text
                continue
            meta_data[tag_name] = entry.text

        if "description" not in meta_data:
            meta_data["description"] = ""

        if "adhesion_info" not in meta_data:
            meta_data["adhesion_info"] = ""

        property_values = {}
        properties = data.iterfind("./um:properties/*", self.__namespaces)
        for entry in properties:
            tag_name = _tag_without_namespace(entry)
            property_values[tag_name] = entry.text

        diameter = float(property_values.get("diameter", 2.85)) # In mm
        density = float(property_values.get("density", 1.3)) # In g/cm3
        meta_data["properties"] = property_values

        self.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0])

        global_compatibility = True
        global_setting_values = {}
        settings = data.iterfind("./um:settings/um:setting", self.__namespaces)
        for entry in settings:
            key = entry.get("key")
            if key in self.__material_property_setting_map:
                global_setting_values[self.__material_property_setting_map[key]] = entry.text
            elif key in self.__unmapped_settings:
                if key == "hardware compatible":
                    global_compatibility = parseBool(entry.text)
            else:
                Logger.log("d", "Unsupported material setting %s", key)
        self._cached_values = global_setting_values

        meta_data["approximate_diameter"] = round(diameter)
        meta_data["compatible"] = global_compatibility
        self.setMetaData(meta_data)
        self._dirty = False

        machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
        for machine in machines:
            machine_compatibility = global_compatibility
            machine_setting_values = {}
            settings = machine.iterfind("./um:setting", self.__namespaces)
            for entry in settings:
                key = entry.get("key")
                if key in self.__material_property_setting_map:
                    machine_setting_values[self.__material_property_setting_map[key]] = entry.text
                elif key in self.__unmapped_settings:
                    if key == "hardware compatible":
                        machine_compatibility = parseBool(entry.text)
                else:
                    Logger.log("d", "Unsupported material setting %s", key)

            cached_machine_setting_properties = global_setting_values.copy()
            cached_machine_setting_properties.update(machine_setting_values)

            identifiers = machine.iterfind("./um:machine_identifier", self.__namespaces)
            for identifier in identifiers:
                machine_id = self.__product_id_map.get(identifier.get("product"), None)
                if machine_id is None:
                    # Lets try again with some naive heuristics.
                    machine_id = identifier.get("product").replace(" ", "").lower()

                definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id)
                if not definitions:
                    Logger.log("w", "No definition found for machine ID %s", machine_id)
                    continue

                definition = definitions[0]

                if machine_compatibility:
                    new_material_id = self.id + "_" + machine_id

                    new_material = XmlMaterialProfile(new_material_id)

                    # Update the private directly, as we want to prevent the lookup that is done when using setName
                    new_material._name = self.getName()
                    new_material.setMetaData(copy.deepcopy(self.getMetaData()))
                    new_material.setDefinition(definition)
                    # Don't use setMetadata, as that overrides it for all materials with same base file
                    new_material.getMetaData()["compatible"] = machine_compatibility

                    new_material.setCachedValues(cached_machine_setting_properties)

                    new_material._dirty = False

                    ContainerRegistry.getInstance().addContainer(new_material)

                hotends = machine.iterfind("./um:hotend", self.__namespaces)
                for hotend in hotends:
                    hotend_id = hotend.get("id")
                    if hotend_id is None:
                        continue

                    variant_containers = ContainerRegistry.getInstance().findInstanceContainers(id = hotend_id)
                    if not variant_containers:
                        # It is not really properly defined what "ID" is so also search for variants by name.
                        variant_containers = ContainerRegistry.getInstance().findInstanceContainers(definition = definition.id, name = hotend_id)

                    if not variant_containers:
                        Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id)
                        continue

                    hotend_compatibility = machine_compatibility
                    hotend_setting_values = {}
                    settings = hotend.iterfind("./um:setting", self.__namespaces)
                    for entry in settings:
                        key = entry.get("key")
                        if key in self.__material_property_setting_map:
                            hotend_setting_values[self.__material_property_setting_map[key]] = entry.text
                        elif key in self.__unmapped_settings:
                            if key == "hardware compatible":
                                hotend_compatibility = parseBool(entry.text)
                        else:
                            Logger.log("d", "Unsupported material setting %s", key)

                    new_hotend_id = self.id + "_" + machine_id + "_" + hotend_id.replace(" ", "_")

                    new_hotend_material = XmlMaterialProfile(new_hotend_id)

                    # Update the private directly, as we want to prevent the lookup that is done when using setName
                    new_hotend_material._name = self.getName()
                    new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
                    new_hotend_material.setDefinition(definition)
                    new_hotend_material.addMetaDataEntry("variant", variant_containers[0].id)
                    # Don't use setMetadata, as that overrides it for all materials with same base file
                    new_hotend_material.getMetaData()["compatible"] = hotend_compatibility

                    cached_hotend_setting_properties = cached_machine_setting_properties.copy()
                    cached_hotend_setting_properties.update(hotend_setting_values)

                    new_hotend_material.setCachedValues(cached_hotend_setting_properties)

                    new_hotend_material._dirty = False

                    ContainerRegistry.getInstance().addContainer(new_hotend_material)
예제 #33
0
    def deserialize(self, serialized, file_name=None):
        containers_to_add = []
        # update the serialized data first
        from UM.Settings.Interfaces import ContainerInterface
        serialized = ContainerInterface.deserialize(self, serialized,
                                                    file_name)

        try:
            data = ET.fromstring(serialized)
        except:
            Logger.logException(
                "e", "An exception occured while parsing the material profile")
            return

        # Reset previous metadata
        self.clearData()  # Ensure any previous data is gone.
        meta_data = {}
        meta_data["type"] = "material"
        meta_data["base_file"] = self.id
        meta_data["status"] = "unknown"  # TODO: Add material verification

        common_setting_values = {}

        inherits = data.find("./um:inherits", self.__namespaces)
        if inherits is not None:
            inherited = self._resolveInheritance(inherits.text)
            data = self._mergeXML(inherited, data)

        # set setting_version in metadata
        if "version" in data.attrib:
            meta_data["setting_version"] = self.xmlVersionToSettingVersion(
                data.attrib["version"])
        else:
            meta_data["setting_version"] = self.xmlVersionToSettingVersion(
                "1.2"
            )  #1.2 and lower didn't have that version number there yet.

        metadata = data.iterfind("./um:metadata/*", self.__namespaces)
        for entry in metadata:
            tag_name = _tag_without_namespace(entry)

            if tag_name == "name":
                brand = entry.find("./um:brand", self.__namespaces)
                material = entry.find("./um:material", self.__namespaces)
                color = entry.find("./um:color", self.__namespaces)
                label = entry.find("./um:label", self.__namespaces)

                if label is not None:
                    self._name = label.text
                else:
                    self._name = self._profile_name(material.text, color.text)
                meta_data["brand"] = brand.text
                meta_data["material"] = material.text
                meta_data["color_name"] = color.text
                continue

            # setting_version is derived from the "version" tag in the schema earlier, so don't set it here
            if tag_name == "setting_version":
                continue

            meta_data[tag_name] = entry.text

            if tag_name in self.__material_metadata_setting_map:
                common_setting_values[self.__material_metadata_setting_map[
                    tag_name]] = entry.text

        if "description" not in meta_data:
            meta_data["description"] = ""

        if "adhesion_info" not in meta_data:
            meta_data["adhesion_info"] = ""

        property_values = {}
        properties = data.iterfind("./um:properties/*", self.__namespaces)
        for entry in properties:
            tag_name = _tag_without_namespace(entry)
            property_values[tag_name] = entry.text

            if tag_name in self.__material_properties_setting_map:
                common_setting_values[self.__material_properties_setting_map[
                    tag_name]] = entry.text

        meta_data["approximate_diameter"] = str(
            round(float(property_values.get("diameter", 2.85))))  # In mm
        meta_data["properties"] = property_values

        self.setDefinition(
            ContainerRegistry.getInstance().findDefinitionContainers(
                id="fdmprinter")[0])

        common_compatibility = True
        settings = data.iterfind("./um:settings/um:setting", self.__namespaces)
        for entry in settings:
            key = entry.get("key")
            if key in self.__material_settings_setting_map:
                common_setting_values[
                    self.__material_settings_setting_map[key]] = entry.text
            elif key in self.__unmapped_settings:
                if key == "hardware compatible":
                    common_compatibility = self._parseCompatibleValue(
                        entry.text)
        self._cached_values = common_setting_values  # from InstanceContainer ancestor

        meta_data["compatible"] = common_compatibility
        self.setMetaData(meta_data)
        self._dirty = False

        # Map machine human-readable names to IDs
        product_id_map = {}
        for container in ContainerRegistry.getInstance(
        ).findDefinitionContainers(type="machine"):
            product_id_map[container.getName()] = container.getId()

        machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
        for machine in machines:
            machine_compatibility = common_compatibility
            machine_setting_values = {}
            settings = machine.iterfind("./um:setting", self.__namespaces)
            for entry in settings:
                key = entry.get("key")
                if key in self.__material_settings_setting_map:
                    machine_setting_values[
                        self.__material_settings_setting_map[key]] = entry.text
                elif key in self.__unmapped_settings:
                    if key == "hardware compatible":
                        machine_compatibility = self._parseCompatibleValue(
                            entry.text)
                else:
                    Logger.log("d", "Unsupported material setting %s", key)

            cached_machine_setting_properties = common_setting_values.copy()
            cached_machine_setting_properties.update(machine_setting_values)

            identifiers = machine.iterfind("./um:machine_identifier",
                                           self.__namespaces)
            for identifier in identifiers:
                machine_id = product_id_map.get(identifier.get("product"),
                                                None)
                if machine_id is None:
                    # Lets try again with some naive heuristics.
                    machine_id = identifier.get("product").replace(" ",
                                                                   "").lower()

                definitions = ContainerRegistry.getInstance(
                ).findDefinitionContainers(id=machine_id)
                if not definitions:
                    # Logger.log("w", "No definition found for machine ID %s", machine_id)
                    continue

                definition = definitions[0]

                machine_manufacturer = identifier.get(
                    "manufacturer",
                    definition.getMetaDataEntry("manufacturer", "Unknown")
                )  #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.

                if machine_compatibility:
                    new_material_id = self.id + "_" + machine_id

                    # The child or derived material container may already exist. This can happen when a material in a
                    # project file and the a material in Cura have the same ID.
                    # In the case if a derived material already exists, override that material container because if
                    # the data in the parent material has been changed, the derived ones should be updated too.
                    found_materials = ContainerRegistry.getInstance(
                    ).findInstanceContainers(id=new_material_id)
                    is_new_material = False
                    if found_materials:
                        new_material = found_materials[0]
                    else:
                        new_material = XmlMaterialProfile(new_material_id)
                        is_new_material = True

                    # Update the private directly, as we want to prevent the lookup that is done when using setName
                    new_material._name = self.getName()
                    new_material.setMetaData(copy.deepcopy(self.getMetaData()))
                    new_material.setDefinition(definition)
                    # Don't use setMetadata, as that overrides it for all materials with same base file
                    new_material.getMetaData(
                    )["compatible"] = machine_compatibility
                    new_material.getMetaData(
                    )["machine_manufacturer"] = machine_manufacturer

                    new_material.setCachedValues(
                        cached_machine_setting_properties)

                    new_material._dirty = False

                    if is_new_material:
                        containers_to_add.append(new_material)

                hotends = machine.iterfind("./um:hotend", self.__namespaces)
                for hotend in hotends:
                    hotend_id = hotend.get("id")
                    if hotend_id is None:
                        continue

                    variant_containers = ContainerRegistry.getInstance(
                    ).findInstanceContainers(id=hotend_id)
                    if not variant_containers:
                        # It is not really properly defined what "ID" is so also search for variants by name.
                        variant_containers = ContainerRegistry.getInstance(
                        ).findInstanceContainers(definition=definition.id,
                                                 name=hotend_id)

                    if not variant_containers:
                        #Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id)
                        continue

                    hotend_compatibility = machine_compatibility
                    hotend_setting_values = {}
                    settings = hotend.iterfind("./um:setting",
                                               self.__namespaces)
                    for entry in settings:
                        key = entry.get("key")
                        if key in self.__material_settings_setting_map:
                            hotend_setting_values[
                                self.__material_settings_setting_map[
                                    key]] = entry.text
                        elif key in self.__unmapped_settings:
                            if key == "hardware compatible":
                                hotend_compatibility = self._parseCompatibleValue(
                                    entry.text)
                        else:
                            Logger.log("d", "Unsupported material setting %s",
                                       key)

                    new_hotend_id = self.id + "_" + machine_id + "_" + hotend_id.replace(
                        " ", "_")

                    # Same as machine compatibility, keep the derived material containers consistent with the parent
                    # material
                    found_materials = ContainerRegistry.getInstance(
                    ).findInstanceContainers(id=new_hotend_id)
                    is_new_material = False
                    if found_materials:
                        new_hotend_material = found_materials[0]
                    else:
                        new_hotend_material = XmlMaterialProfile(new_hotend_id)
                        is_new_material = True

                    # Update the private directly, as we want to prevent the lookup that is done when using setName
                    new_hotend_material._name = self.getName()
                    new_hotend_material.setMetaData(
                        copy.deepcopy(self.getMetaData()))
                    new_hotend_material.setDefinition(definition)
                    new_hotend_material.addMetaDataEntry(
                        "variant", variant_containers[0].id)
                    # Don't use setMetadata, as that overrides it for all materials with same base file
                    new_hotend_material.getMetaData(
                    )["compatible"] = hotend_compatibility
                    new_hotend_material.getMetaData(
                    )["machine_manufacturer"] = machine_manufacturer

                    cached_hotend_setting_properties = cached_machine_setting_properties.copy(
                    )
                    cached_hotend_setting_properties.update(
                        hotend_setting_values)

                    new_hotend_material.setCachedValues(
                        cached_hotend_setting_properties)

                    new_hotend_material._dirty = False

                    if is_new_material:
                        containers_to_add.append(new_hotend_material)

        for container_to_add in containers_to_add:
            ContainerRegistry.getInstance().addContainer(container_to_add)
예제 #34
0
    def deserialize(self, serialized):
        # update the serialized data first
        from UM.Settings.Interfaces import ContainerInterface
        serialized = ContainerInterface.deserialize(self, serialized)

        try:
            data = ET.fromstring(serialized)
        except:
            Logger.logException("e", "An exception occured while parsing the material profile")
            return

        # Reset previous metadata
        self.clearData() # Ensure any previous data is gone.
        meta_data = {}
        meta_data["type"] = "material"
        meta_data["base_file"] = self.id
        meta_data["status"] = "unknown"  # TODO: Add material verification

        common_setting_values = {}

        inherits = data.find("./um:inherits", self.__namespaces)
        if inherits is not None:
            inherited = self._resolveInheritance(inherits.text)
            data = self._mergeXML(inherited, data)

        # set setting_version in metadata
        if "version" in data.attrib:
            meta_data["setting_version"] = self.xmlVersionToSettingVersion(data.attrib["version"])
        else:
            meta_data["setting_version"] = self.xmlVersionToSettingVersion("1.2") #1.2 and lower didn't have that version number there yet.

        metadata = data.iterfind("./um:metadata/*", self.__namespaces)
        for entry in metadata:
            tag_name = _tag_without_namespace(entry)

            if tag_name == "name":
                brand = entry.find("./um:brand", self.__namespaces)
                material = entry.find("./um:material", self.__namespaces)
                color = entry.find("./um:color", self.__namespaces)
                label = entry.find("./um:label", self.__namespaces)

                if label is not None:
                    self._name = label.text
                else:
                    self._name = self._profile_name(material.text, color.text)
                meta_data["brand"] = brand.text
                meta_data["material"] = material.text
                meta_data["color_name"] = color.text
                continue

            # setting_version is derived from the "version" tag in the schema earlier, so don't set it here
            if tag_name == "setting_version":
                continue

            meta_data[tag_name] = entry.text

            if tag_name in self.__material_metadata_setting_map:
                common_setting_values[self.__material_metadata_setting_map[tag_name]] = entry.text

        if "description" not in meta_data:
            meta_data["description"] = ""

        if "adhesion_info" not in meta_data:
            meta_data["adhesion_info"] = ""

        property_values = {}
        properties = data.iterfind("./um:properties/*", self.__namespaces)
        for entry in properties:
            tag_name = _tag_without_namespace(entry)
            property_values[tag_name] = entry.text

            if tag_name in self.__material_properties_setting_map:
                common_setting_values[self.__material_properties_setting_map[tag_name]] = entry.text

        meta_data["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm
        meta_data["properties"] = property_values

        self.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0])

        common_compatibility = True
        settings = data.iterfind("./um:settings/um:setting", self.__namespaces)
        for entry in settings:
            key = entry.get("key")
            if key in self.__material_settings_setting_map:
                common_setting_values[self.__material_settings_setting_map[key]] = entry.text
            elif key in self.__unmapped_settings:
                if key == "hardware compatible":
                    common_compatibility = self._parseCompatibleValue(entry.text)
            else:
                Logger.log("d", "Unsupported material setting %s", key)
        self._cached_values = common_setting_values # from InstanceContainer ancestor

        meta_data["compatible"] = common_compatibility
        self.setMetaData(meta_data)
        self._dirty = False

        machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
        for machine in machines:
            machine_compatibility = common_compatibility
            machine_setting_values = {}
            settings = machine.iterfind("./um:setting", self.__namespaces)
            for entry in settings:
                key = entry.get("key")
                if key in self.__material_settings_setting_map:
                    machine_setting_values[self.__material_settings_setting_map[key]] = entry.text
                elif key in self.__unmapped_settings:
                    if key == "hardware compatible":
                        machine_compatibility = self._parseCompatibleValue(entry.text)
                else:
                    Logger.log("d", "Unsupported material setting %s", key)

            cached_machine_setting_properties = common_setting_values.copy()
            cached_machine_setting_properties.update(machine_setting_values)

            identifiers = machine.iterfind("./um:machine_identifier", self.__namespaces)
            for identifier in identifiers:
                machine_id = self.__product_id_map.get(identifier.get("product"), None)
                if machine_id is None:
                    # Lets try again with some naive heuristics.
                    machine_id = identifier.get("product").replace(" ", "").lower()

                definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id)
                if not definitions:
                    Logger.log("w", "No definition found for machine ID %s", machine_id)
                    continue

                definition = definitions[0]

                machine_manufacturer = identifier.get("manufacturer", definition.getMetaDataEntry("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.

                if machine_compatibility:
                    new_material_id = self.id + "_" + machine_id

                    # The child or derived material container may already exist. This can happen when a material in a
                    # project file and the a material in Cura have the same ID.
                    # In the case if a derived material already exists, override that material container because if
                    # the data in the parent material has been changed, the derived ones should be updated too.
                    found_materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
                    is_new_material = False
                    if found_materials:
                        new_material = found_materials[0]
                    else:
                        new_material = XmlMaterialProfile(new_material_id)
                        is_new_material = True

                    # Update the private directly, as we want to prevent the lookup that is done when using setName
                    new_material._name = self.getName()
                    new_material.setMetaData(copy.deepcopy(self.getMetaData()))
                    new_material.setDefinition(definition)
                    # Don't use setMetadata, as that overrides it for all materials with same base file
                    new_material.getMetaData()["compatible"] = machine_compatibility
                    new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer

                    new_material.setCachedValues(cached_machine_setting_properties)

                    new_material._dirty = False

                    if is_new_material:
                        ContainerRegistry.getInstance().addContainer(new_material)

                hotends = machine.iterfind("./um:hotend", self.__namespaces)
                for hotend in hotends:
                    hotend_id = hotend.get("id")
                    if hotend_id is None:
                        continue

                    variant_containers = ContainerRegistry.getInstance().findInstanceContainers(id = hotend_id)
                    if not variant_containers:
                        # It is not really properly defined what "ID" is so also search for variants by name.
                        variant_containers = ContainerRegistry.getInstance().findInstanceContainers(definition = definition.id, name = hotend_id)

                    if not variant_containers:
                        #Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id)
                        continue

                    hotend_compatibility = machine_compatibility
                    hotend_setting_values = {}
                    settings = hotend.iterfind("./um:setting", self.__namespaces)
                    for entry in settings:
                        key = entry.get("key")
                        if key in self.__material_settings_setting_map:
                            hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text
                        elif key in self.__unmapped_settings:
                            if key == "hardware compatible":
                                hotend_compatibility = self._parseCompatibleValue(entry.text)
                        else:
                            Logger.log("d", "Unsupported material setting %s", key)

                    new_hotend_id = self.id + "_" + machine_id + "_" + hotend_id.replace(" ", "_")

                    # Same as machine compatibility, keep the derived material containers consistent with the parent
                    # material
                    found_materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_hotend_id)
                    is_new_material = False
                    if found_materials:
                        new_hotend_material = found_materials[0]
                    else:
                        new_hotend_material = XmlMaterialProfile(new_hotend_id)
                        is_new_material = True

                    # Update the private directly, as we want to prevent the lookup that is done when using setName
                    new_hotend_material._name = self.getName()
                    new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
                    new_hotend_material.setDefinition(definition)
                    new_hotend_material.addMetaDataEntry("variant", variant_containers[0].id)
                    # Don't use setMetadata, as that overrides it for all materials with same base file
                    new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
                    new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer

                    cached_hotend_setting_properties = cached_machine_setting_properties.copy()
                    cached_hotend_setting_properties.update(hotend_setting_values)

                    new_hotend_material.setCachedValues(cached_hotend_setting_properties)

                    new_hotend_material._dirty = False

                    if is_new_material:
                        ContainerRegistry.getInstance().addContainer(new_hotend_material)
예제 #35
0
    def __call__(
        self,
        value_provider: ContainerInterface,
        context: Optional[PropertyEvaluationContext] = None
    ) -> Optional[ValidatorState]:
        if not value_provider:
            return None

        state = ValidatorState.Unknown
        try:
            allow_empty = value_provider.getProperty(
                self._key, "allow_empty", context=context)  # For string only
            is_uuid = value_provider.getProperty(
                self._key, "is_uuid", context=context)  # For string only
            regex_blacklist_pattern = value_provider.getProperty(
                self._key, "regex_blacklist_pattern",
                context=context)  # For string only
            minimum = value_provider.getProperty(self._key,
                                                 "minimum_value",
                                                 context=context)
            maximum = value_provider.getProperty(self._key,
                                                 "maximum_value",
                                                 context=context)
            minimum_warning = value_provider.getProperty(
                self._key, "minimum_value_warning", context=context)
            maximum_warning = value_provider.getProperty(
                self._key, "maximum_value_warning", context=context)

            # For boolean
            boolean_warning_value = value_provider.getProperty(self._key,
                                                               "warning_value",
                                                               context=context)
            boolean_error_value = value_provider.getProperty(self._key,
                                                             "error_value",
                                                             context=context)

            if minimum is not None and maximum is not None and minimum > maximum:
                raise ValueError(
                    "Cannot validate a state of setting {0} with minimum > maximum"
                    .format(self._key))

            if context is not None:
                value_provider = context.rootStack()

            value = value_provider.getProperty(self._key,
                                               "value",
                                               context=context)
            if value is None or value != value:
                raise ValueError(
                    "Cannot validate None, NaN or similar values in setting {0}, actual value: {1}"
                    .format(self._key, value))

            setting_type = value_provider.getProperty(self._key,
                                                      "type",
                                                      context=context)

            if setting_type == "str":
                # "allow_empty is not None" is not necessary here because of "allow_empty is False", but it states
                # explicitly that we should not do this check when "allow_empty is None".
                if allow_empty is not None and allow_empty is False and str(
                        value) == "":
                    state = ValidatorState.Invalid
                    return state

                if is_uuid is not None and is_uuid is True:
                    # Try to parse the UUID string with uuid.UUID(). It will raise a ValueError if it's not valid.
                    try:
                        uuid.UUID(str(value))
                        state = ValidatorState.Valid
                    except ValueError:
                        state = ValidatorState.Invalid
                    return state

                # If "regex_blacklist_pattern" is set, it will be used to validate the value string. If the value string
                # matches the blacklist pattern, the value will be invalid.
                if regex_blacklist_pattern is not None and len(
                        regex_blacklist_pattern) > 0:
                    regex = re.compile(regex_blacklist_pattern)
                    is_valid = regex.fullmatch(str(value).lower()) is None
                    state = ValidatorState.Valid if is_valid else ValidatorState.Invalid
                    return state

                state = ValidatorState.Valid
                return state

            elif setting_type == "bool":
                state = ValidatorState.Valid
                if boolean_warning_value is not None and value == boolean_warning_value:
                    state = ValidatorState.MaximumWarning
                elif boolean_error_value is not None and value == boolean_error_value:
                    state = ValidatorState.MaximumError

            elif minimum is not None and value < minimum:
                state = ValidatorState.MinimumError
            elif maximum is not None and value > maximum:
                state = ValidatorState.MaximumError
            elif minimum_warning is not None and value < minimum_warning:
                state = ValidatorState.MinimumWarning
            elif maximum_warning is not None and value > maximum_warning:
                state = ValidatorState.MaximumWarning
            else:
                state = ValidatorState.Valid
        except:
            Logger.logException(
                "w", "Could not validate setting %s, an exception was raised",
                self._key)
            state = ValidatorState.Exception

        return state
예제 #36
0
    def deserialize(self, serialized, file_name = None):
        containers_to_add = []
        # update the serialized data first
        from UM.Settings.Interfaces import ContainerInterface
        serialized = ContainerInterface.deserialize(self, serialized, file_name)

        try:
            data = ET.fromstring(serialized)
        except:
            Logger.logException("e", "An exception occurred while parsing the material profile")
            return

        # Reset previous metadata
        old_id = self.getId()
        self.clearData() # Ensure any previous data is gone.
        meta_data = {}
        meta_data["type"] = "material"
        meta_data["base_file"] = self.getId()
        meta_data["status"] = "unknown"  # TODO: Add material verification
        meta_data["id"] = old_id
        meta_data["container_type"] = XmlMaterialProfile

        common_setting_values = {}

        inherits = data.find("./um:inherits", self.__namespaces)
        if inherits is not None:
            inherited = self._resolveInheritance(inherits.text)
            data = self._mergeXML(inherited, data)

        # set setting_version in metadata
        if "version" in data.attrib:
            meta_data["setting_version"] = self.xmlVersionToSettingVersion(data.attrib["version"])
        else:
            meta_data["setting_version"] = self.xmlVersionToSettingVersion("1.2") #1.2 and lower didn't have that version number there yet.

        meta_data["name"] = "Unknown Material" #In case the name tag is missing.
        for entry in data.iterfind("./um:metadata/*", self.__namespaces):
            tag_name = _tag_without_namespace(entry)

            if tag_name == "name":
                brand = entry.find("./um:brand", self.__namespaces)
                material = entry.find("./um:material", self.__namespaces)
                color = entry.find("./um:color", self.__namespaces)
                label = entry.find("./um:label", self.__namespaces)

                if label is not None:
                    meta_data["name"] = label.text
                else:
                    meta_data["name"] = self._profile_name(material.text, color.text)
                meta_data["brand"] = brand.text
                meta_data["material"] = material.text
                meta_data["color_name"] = color.text
                continue

            # setting_version is derived from the "version" tag in the schema earlier, so don't set it here
            if tag_name == "setting_version":
                continue

            meta_data[tag_name] = entry.text

            if tag_name in self.__material_metadata_setting_map:
                common_setting_values[self.__material_metadata_setting_map[tag_name]] = entry.text

        if "description" not in meta_data:
            meta_data["description"] = ""

        if "adhesion_info" not in meta_data:
            meta_data["adhesion_info"] = ""

        validation_message = XmlMaterialValidator.validateMaterialMetaData(meta_data)
        if validation_message is not None:
            raise Exception("Not valid material profile: %s" % (validation_message))

        property_values = {}
        properties = data.iterfind("./um:properties/*", self.__namespaces)
        for entry in properties:
            tag_name = _tag_without_namespace(entry)
            property_values[tag_name] = entry.text

            if tag_name in self.__material_properties_setting_map:
                common_setting_values[self.__material_properties_setting_map[tag_name]] = entry.text

        meta_data["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm
        meta_data["properties"] = property_values
        meta_data["definition"] = "fdmprinter"

        common_compatibility = True
        settings = data.iterfind("./um:settings/um:setting", self.__namespaces)
        for entry in settings:
            key = entry.get("key")
            if key in self.__material_settings_setting_map:
                common_setting_values[self.__material_settings_setting_map[key]] = entry.text
            elif key in self.__unmapped_settings:
                if key == "hardware compatible":
                    common_compatibility = self._parseCompatibleValue(entry.text)
        self._cached_values = common_setting_values # from InstanceContainer ancestor

        meta_data["compatible"] = common_compatibility
        self.setMetaData(meta_data)
        self._dirty = False

        # Map machine human-readable names to IDs
        product_id_map = self.getProductIdMap()

        machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
        for machine in machines:
            machine_compatibility = common_compatibility
            machine_setting_values = {}
            settings = machine.iterfind("./um:setting", self.__namespaces)
            for entry in settings:
                key = entry.get("key")
                if key in self.__material_settings_setting_map:
                    machine_setting_values[self.__material_settings_setting_map[key]] = entry.text
                elif key in self.__unmapped_settings:
                    if key == "hardware compatible":
                        machine_compatibility = self._parseCompatibleValue(entry.text)
                else:
                    Logger.log("d", "Unsupported material setting %s", key)

            cached_machine_setting_properties = common_setting_values.copy()
            cached_machine_setting_properties.update(machine_setting_values)

            identifiers = machine.iterfind("./um:machine_identifier", self.__namespaces)
            for identifier in identifiers:
                machine_id_list = product_id_map.get(identifier.get("product"), [])
                if not machine_id_list:
                    machine_id_list = self.getPossibleDefinitionIDsFromName(identifier.get("product"))

                for machine_id in machine_id_list:
                    definitions = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
                    if not definitions:
                        Logger.log("w", "No definition found for machine ID %s", machine_id)
                        continue

                    definition = definitions[0]

                    machine_manufacturer = identifier.get("manufacturer", definition.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.

                    if machine_compatibility:
                        new_material_id = self.getId() + "_" + machine_id

                        # The child or derived material container may already exist. This can happen when a material in a
                        # project file and the a material in Cura have the same ID.
                        # In the case if a derived material already exists, override that material container because if
                        # the data in the parent material has been changed, the derived ones should be updated too.
                        if ContainerRegistry.getInstance().isLoaded(new_material_id):
                            new_material = ContainerRegistry.getInstance().findContainers(id = new_material_id)[0]
                            is_new_material = False
                        else:
                            new_material = XmlMaterialProfile(new_material_id)
                            is_new_material = True

                        new_material.setMetaData(copy.deepcopy(self.getMetaData()))
                        new_material.getMetaData()["id"] = new_material_id
                        new_material.getMetaData()["name"] = self.getName()
                        new_material.setDefinition(machine_id)
                        # Don't use setMetadata, as that overrides it for all materials with same base file
                        new_material.getMetaData()["compatible"] = machine_compatibility
                        new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
                        new_material.getMetaData()["definition"] = machine_id

                        new_material.setCachedValues(cached_machine_setting_properties)

                        new_material._dirty = False

                        if is_new_material:
                            containers_to_add.append(new_material)

                    # Find the buildplates compatibility
                    buildplates = machine.iterfind("./um:buildplate", self.__namespaces)
                    buildplate_map = {}
                    buildplate_map["buildplate_compatible"] = {}
                    buildplate_map["buildplate_recommended"] = {}
                    for buildplate in buildplates:
                        buildplate_id = buildplate.get("id")
                        if buildplate_id is None:
                            continue

                        variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(
                            id = buildplate_id)
                        if not variant_containers:
                            # It is not really properly defined what "ID" is so also search for variants by name.
                            variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(
                                definition = machine_id, name = buildplate_id)

                        if not variant_containers:
                            continue

                        buildplate_compatibility = machine_compatibility
                        buildplate_recommended = machine_compatibility
                        settings = buildplate.iterfind("./um:setting", self.__namespaces)
                        for entry in settings:
                            key = entry.get("key")
                            if key in self.__unmapped_settings:
                                if key == "hardware compatible":
                                    buildplate_compatibility = self._parseCompatibleValue(entry.text)
                                elif key == "hardware recommended":
                                    buildplate_recommended = self._parseCompatibleValue(entry.text)
                            else:
                                Logger.log("d", "Unsupported material setting %s", key)

                        buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility
                        buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended

                    hotends = machine.iterfind("./um:hotend", self.__namespaces)
                    for hotend in hotends:
                        hotend_id = hotend.get("id")
                        if hotend_id is None:
                            continue

                        variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
                        if not variant_containers:
                            # It is not really properly defined what "ID" is so also search for variants by name.
                            variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)

                        if not variant_containers:
                            continue

                        hotend_compatibility = machine_compatibility
                        hotend_setting_values = {}
                        settings = hotend.iterfind("./um:setting", self.__namespaces)
                        for entry in settings:
                            key = entry.get("key")
                            if key in self.__material_settings_setting_map:
                                hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text
                            elif key in self.__unmapped_settings:
                                if key == "hardware compatible":
                                    hotend_compatibility = self._parseCompatibleValue(entry.text)
                            else:
                                Logger.log("d", "Unsupported material setting %s", key)

                        new_hotend_id = self.getId() + "_" + machine_id + "_" + hotend_id.replace(" ", "_")

                        # Same as machine compatibility, keep the derived material containers consistent with the parent material
                        if ContainerRegistry.getInstance().isLoaded(new_hotend_id):
                            new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0]
                            is_new_material = False
                        else:
                            new_hotend_material = XmlMaterialProfile(new_hotend_id)
                            is_new_material = True

                        new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
                        new_hotend_material.getMetaData()["id"] = new_hotend_id
                        new_hotend_material.getMetaData()["name"] = self.getName()
                        new_hotend_material.getMetaData()["variant"] = variant_containers[0]["id"]
                        new_hotend_material.setDefinition(machine_id)
                        # Don't use setMetadata, as that overrides it for all materials with same base file
                        new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
                        new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
                        new_hotend_material.getMetaData()["definition"] = machine_id
                        if buildplate_map["buildplate_compatible"]:
                            new_hotend_material.getMetaData()["buildplate_compatible"] = buildplate_map["buildplate_compatible"]
                            new_hotend_material.getMetaData()["buildplate_recommended"] = buildplate_map["buildplate_recommended"]

                        cached_hotend_setting_properties = cached_machine_setting_properties.copy()
                        cached_hotend_setting_properties.update(hotend_setting_values)

                        new_hotend_material.setCachedValues(cached_hotend_setting_properties)

                        new_hotend_material._dirty = False

                        if is_new_material:
                            containers_to_add.append(new_hotend_material)

                    # there is only one ID for a machine. Once we have reached here, it means we have already found
                    # a workable ID for that machine, so there is no need to continue
                    break

        for container_to_add in containers_to_add:
            ContainerRegistry.getInstance().addContainer(container_to_add)