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"])
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)
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)
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)
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)
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")
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)
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
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
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)
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
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
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)
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])
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])
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))
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)
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)
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
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]
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]
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)
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)
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))
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
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)
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
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]
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
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)
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)
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)
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)
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
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)