def getDefaultVariantNode(self, machine_definition: "DefinitionContainer", variant_type: VariantType) -> Optional["ContainerNode"]: machine_definition_id = machine_definition.getId() preferred_variant_name = None if variant_type == VariantType.BUILD_PLATE: if parseBool(machine_definition.getMetaDataEntry("has_variant_buildplates", False)): preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_buildplate_name") else: if parseBool(machine_definition.getMetaDataEntry("has_variants", False)): preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_name") node = None if preferred_variant_name: node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type) return node
def _update(self): Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) global_stack = self._machine_manager.activeMachine if global_stack is None: self.setItems([]) return has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", False)) if not has_variants: self.setItems([]) return variant_node_dict = self._variant_manager.getVariantNodes(global_stack, VariantType.NOZZLE) if not variant_node_dict: self.setItems([]) return item_list = [] for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0].upper()): item = {"id": hotend_name, "hotend_name": hotend_name, "container_node": container_node } item_list.append(item) self.setItems(item_list)
def getDefaultMaterial(self, global_stack: "GlobalStack", position: str, nozzle_name: Optional[str], extruder_definition: Optional["DefinitionContainer"] = None) -> Optional["MaterialNode"]: node = None buildplate_name = global_stack.getBuildplateName() machine_definition = global_stack.definition # The extruder-compatible material diameter in the extruder definition may not be the correct value because # the user can change it in the definition_changes container. if extruder_definition is None: extruder_stack_or_definition = global_stack.extruders[position] is_extruder_stack = True else: extruder_stack_or_definition = extruder_definition is_extruder_stack = False if extruder_stack_or_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)): if is_extruder_stack: material_diameter = extruder_stack_or_definition.getCompatibleMaterialDiameter() else: material_diameter = extruder_stack_or_definition.getProperty("material_diameter", "value") if isinstance(material_diameter, SettingFunction): material_diameter = material_diameter(global_stack) approximate_material_diameter = str(round(material_diameter)) root_material_id = machine_definition.getMetaDataEntry("preferred_material") root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter) node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name, material_diameter, root_material_id) return node
def getDefaultVariantNode(self, machine_definition: "DefinitionContainer", variant_type: "VariantType", global_stack: Optional["GlobalStack"] = None) -> Optional["ContainerNode"]: machine_definition_id = machine_definition.getId() container_for_metadata_fetching = global_stack if global_stack is not None else machine_definition preferred_variant_name = None if variant_type == VariantType.BUILD_PLATE: if parseBool(container_for_metadata_fetching.getMetaDataEntry("has_variant_buildplates", False)): preferred_variant_name = container_for_metadata_fetching.getMetaDataEntry("preferred_variant_buildplate_name") else: if parseBool(container_for_metadata_fetching.getMetaDataEntry("has_variants", False)): preferred_variant_name = container_for_metadata_fetching.getMetaDataEntry("preferred_variant_name") node = None if preferred_variant_name: node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type) return node
def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainerInterface", default_definition_id: str = "fdmprinter") -> str: machine_definition_id = default_definition_id if parseBool(machine_definition.getMetaDataEntry("has_machine_quality", False)): # Only use the machine's own quality definition ID if this machine has machine quality. machine_definition_id = machine_definition.getMetaDataEntry("quality_definition") if machine_definition_id is None: machine_definition_id = machine_definition.getId() return machine_definition_id
def _getMaterialContainerIdForActiveMachine(self, base_file): global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return base_file has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False)) has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False)) has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False)) if has_machine_materials or has_variant_materials: if has_variants: materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId) else: materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId()) if materials: return materials[0].getId() Logger.log("w", "Unable to find a suitable container based on %s for the current machine .", base_file) return "" # do not activate a new material if a container can not be found return base_file
def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: Optional[str]) -> Optional["MaterialNode"]: node = None machine_definition = global_stack.definition if parseBool(global_stack.getMetaDataEntry("has_materials", False)): material_diameter = machine_definition.getProperty("material_diameter", "value") if isinstance(material_diameter, SettingFunction): material_diameter = material_diameter(global_stack) approximate_material_diameter = str(round(material_diameter)) root_material_id = machine_definition.getMetaDataEntry("preferred_material") root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter) node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name, material_diameter, root_material_id) return node
def reCheckConnections(self) -> None: global_container_stack = Application.getInstance().getGlobalContainerStack() if not global_container_stack: return for key in self._instances: if key == global_container_stack.getMetaDataEntry("octoprint_id"): api_key = global_container_stack.getMetaDataEntry("octoprint_api_key", "") self._instances[key].setApiKey(self._deobfuscateString(api_key)) self._instances[key].setShowCamera(parseBool(global_container_stack.getMetaDataEntry("octoprint_show_camera", "false"))) self._instances[key].connectionStateChanged.connect(self._onInstanceConnectionStateChanged) self._instances[key].connect() else: if self._instances[key].isConnected(): self._instances[key].close()
def discoveredPrinters(self) -> List["DiscoveredPrinter"]: item_list = list( x for x in self._discovered_printer_by_ip_dict.values() if not parseBool(x.device.getProperty("temporary"))) # Split the printers into 2 lists and sort them ascending based on names. available_list = [] not_available_list = [] for item in item_list: if item.isUnknownMachineType or getattr(item.device, "clusterSize", 1) < 1: not_available_list.append(item) else: available_list.append(item) available_list.sort(key = lambda x: x.device.name) not_available_list.sort(key = lambda x: x.device.name) return available_list + not_available_list
def _update(self): Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) global_stack = self._machine_manager._global_container_stack if not global_stack: self.setItems([]) return has_variants = parseBool(global_stack.getMetaDataEntry("has_variant_buildplates", False)) if not has_variants: self.setItems([]) return variant_dict = self._variant_manager.getVariantNodes(global_stack, variant_type = VariantType.BUILD_PLATE) item_list = [] for name, variant_node in variant_dict.items(): item = {"name": name, "container_node": variant_node} item_list.append(item) self.setItems(item_list)
def getMaterialNodeByType(self, global_stack: "GlobalStack", extruder_variant_name: str, material_guid: str) -> Optional["MaterialNode"]: node = None machine_definition = global_stack.definition if parseBool(machine_definition.getMetaDataEntry("has_materials", False)): material_diameter = machine_definition.getProperty("material_diameter", "value") if isinstance(material_diameter, SettingFunction): material_diameter = material_diameter(global_stack) # Look at the guid to material dictionary root_material_id = None for material_group in self._guid_material_groups_map[material_guid]: if material_group.is_read_only: root_material_id = material_group.root_material_node.metadata["id"] break if not root_material_id: Logger.log("i", "Cannot find materials with guid [%s] ", material_guid) return None node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name, material_diameter, root_material_id) return node
def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, nozzle_name: str, buildplate_name: Optional[str], material_guid: str) -> Optional["MaterialNode"]: node = None machine_definition = global_stack.definition extruder_definition = global_stack.extruders[position].definition if parseBool(machine_definition.getMetaDataEntry("has_materials", False)): material_diameter = extruder_definition.getProperty("material_diameter", "value") if isinstance(material_diameter, SettingFunction): material_diameter = material_diameter(global_stack) # Look at the guid to material dictionary root_material_id = None for material_group in self._guid_material_groups_map[material_guid]: root_material_id = cast(str, material_group.root_material_node.getMetaDataEntry("id", "")) break if not root_material_id: Logger.log("i", "Cannot find materials with guid [%s] ", material_guid) return None node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name, material_diameter, root_material_id) return node
def _update(self) -> None: items = [] container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine") for container_stack in container_stacks: has_remote_connection = False for connection_type in container_stack.configuredConnectionTypes: has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value] if parseBool(container_stack.getMetaDataEntry("hidden", False)): continue section_name = "Network enabled printers" if has_remote_connection else "Local printers" section_name = self._catalog.i18nc("@info:title", section_name) items.append({"name": container_stack.getMetaDataEntry("group_name", container_stack.getName()), "id": container_stack.getId(), "hasRemoteConnection": has_remote_connection, "metadata": container_stack.getMetaData().copy(), "discoverySource": section_name}) items.sort(key = lambda i: (not i["hasRemoteConnection"], i["name"])) self.setItems(items)
def startPrint(self): global_container_stack = Application.getInstance( ).getGlobalContainerStack() if not global_container_stack: return self._auto_print = parseBool( global_container_stack.getMetaDataEntry("octoprint_auto_print", True)) if self.jobState not in ["ready", ""]: if self.jobState == "offline": self._error_message = Message( i18n_catalog.i18nc( "@info:status", "OctoPrint is offline. Unable to start a new job.")) elif self._auto_print: self._error_message = Message( i18n_catalog.i18nc( "@info:status", "OctoPrint is busy. Unable to start a new job.")) else: # allow queueing the job even if OctoPrint is currently busy if autoprinting is disabled self._error_message = None if self._error_message: self._error_message.show() return self._preheat_timer.stop() if self._auto_print: Application.getInstance().showPrintMonitor.emit(True) try: self._progress_message = Message( i18n_catalog.i18nc("@info:status", "Sending data to OctoPrint"), 0, False, -1) self._progress_message.addAction( "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None, "") self._progress_message.actionTriggered.connect( self._cancelSendGcode) self._progress_message.show() ## Mash the data into single string single_string_file_data = "" last_process_events = time() for line in self._gcode: single_string_file_data += line if time() > last_process_events + 0.05: # Ensure that the GUI keeps updated at least 20 times per second. QCoreApplication.processEvents() last_process_events = time() job_name = Application.getInstance().getPrintInformation( ).jobName.strip() if job_name is "": job_name = "untitled_print" file_name = "%s.gcode" % job_name ## Create multi_part request self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) ## Create parts (to be placed inside multipart) self._post_part = QHttpPart() self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"select\"") self._post_part.setBody(b"true") self._post_multi_part.append(self._post_part) if self._auto_print: self._post_part = QHttpPart() self._post_part.setHeader( QNetworkRequest.ContentDispositionHeader, "form-data; name=\"print\"") self._post_part.setBody(b"true") self._post_multi_part.append(self._post_part) self._post_part = QHttpPart() self._post_part.setHeader( QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name) self._post_part.setBody(single_string_file_data.encode()) self._post_multi_part.append(self._post_part) destination = "local" if self._sd_supported and parseBool( global_container_stack.getMetaDataEntry( "octoprint_store_sd", False)): destination = "sdcard" ## Post request + data post_request = self._createApiRequest("files/" + destination) self._post_reply = self._manager.post(post_request, self._post_multi_part) self._post_reply.uploadProgress.connect(self._onUploadProgress) self._gcode = None except IOError: self._progress_message.hide() self._error_message = Message( i18n_catalog.i18nc("@info:status", "Unable to send data to OctoPrint.")) self._error_message.show() except Exception as e: self._progress_message.hide() Logger.log( "e", "An exception occurred in network connection: %s" % str(e))
def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]: """ Upgrades stacks to have the new version number. :param serialized: The original contents of the stack. :param filename: The original file name of the stack. :return: A list of new file names, and a list of the new contents for those files. """ parser = configparser.ConfigParser(interpolation=None) parser.read_string(serialized) # Update version number. if "metadata" not in parser: parser["metadata"] = {} parser["metadata"]["setting_version"] = "15" # Update Pause at Height script parameters if present. if "post_processing_scripts" in parser["metadata"]: new_scripts_entries = [] for script_str in parser["metadata"][ "post_processing_scripts"].split("\n"): if not script_str: continue script_str = script_str.replace(r"\\\n", "\n").replace( r"\\\\", "\\\\") # Unescape escape sequences. script_parser = configparser.ConfigParser(interpolation=None) script_parser.optionxform = str # type: ignore # Don't transform the setting keys as they are case-sensitive. script_parser.read_string(script_str) # Unify all Pause at Height script_id = script_parser.sections()[0] if script_id in [ "BQ_PauseAtHeight", "PauseAtHeightRepRapFirmwareDuet", "PauseAtHeightforRepetier" ]: script_settings = script_parser.items(script_id) script_settings.append(("pause_method", { "BQ_PauseAtHeight": "bq", "PauseAtHeightforRepetier": "repetier", "PauseAtHeightRepRapFirmwareDuet": "reprap" }[script_id])) # Since we cannot rename a section, we remove the original section and create a new section with the new script id. script_parser.remove_section(script_id) script_id = "PauseAtHeight" script_parser.add_section(script_id) for setting_tuple in script_settings: script_parser.set(script_id, setting_tuple[0], setting_tuple[1]) # Update redo_layers to redo_layer if "PauseAtHeight" in script_parser: if "redo_layers" in script_parser["PauseAtHeight"]: script_parser["PauseAtHeight"]["redo_layer"] = str( int(script_parser["PauseAtHeight"]["redo_layers"]) > 0) del script_parser["PauseAtHeight"][ "redo_layers"] # Has been renamed to without the S. # Migrate DisplayCompleteOnLCD to DisplayProgressOnLCD if script_id == "DisplayRemainingTimeOnLCD": was_enabled = parseBool( script_parser[script_id]["TurnOn"] ) if "TurnOn" in script_parser[script_id] else False script_parser.remove_section(script_id) script_id = "DisplayProgressOnLCD" script_parser.add_section(script_id) if was_enabled: script_parser.set(script_id, "time_remaining", "True") script_io = io.StringIO() script_parser.write(script_io) script_str = script_io.getvalue() script_str = script_str.replace("\\\\", r"\\\\").replace( "\n", r"\\\n" ) # Escape newlines because configparser sees those as section delimiters. new_scripts_entries.append(script_str) parser["metadata"]["post_processing_scripts"] = "\n".join( new_scripts_entries) # check renamed definition if parser.has_option( "containers", "7") and parser["containers"]["7"] in _RENAMED_DEFINITION_DICT: parser["containers"]["7"] = _RENAMED_DEFINITION_DICT[ parser["containers"]["7"]] result = io.StringIO() parser.write(result) return [filename], [result.getvalue()]
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None: global_container_stack = CuraApplication.getInstance( ).getGlobalContainerStack() if not global_container_stack: return # Make sure post-processing plugin are run on the gcode self.writeStarted.emit(self) # Get the g-code through the GCodeWriter plugin # This produces the same output as "Save to File", adding the print settings to the bottom of the file gcode_writer = cast( MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter")) self._gcode_stream = StringIO() if not gcode_writer.write(self._gcode_stream, None): Logger.log("e", "GCodeWrite failed: %s" % gcode_writer.getInformation()) return if self._error_message: self._error_message.hide() self._error_message = None if self._progress_message: self._progress_message.hide() self._progress_message = None self._auto_print = parseBool( global_container_stack.getMetaDataEntry("octoprint_auto_print", True)) self._forced_queue = False if self.activePrinter.state not in ["idle", ""]: Logger.log( "d", "Tried starting a print, but current state is %s" % self.activePrinter.state) if not self._auto_print: # Allow queueing the job even if OctoPrint is currently busy if autoprinting is disabled self._error_message = None elif self.activePrinter.state == "offline": self._error_message = Message( i18n_catalog.i18nc( "@info:status", "The printer is offline. Unable to start a new job.")) else: self._error_message = Message( i18n_catalog.i18nc( "@info:status", "OctoPrint is busy. Unable to start a new job.")) if self._error_message: self._error_message.addAction( "Queue", i18n_catalog.i18nc("@action:button", "Queue job"), "", i18n_catalog.i18nc( "@action:tooltip", "Queue this print job so it can be printed later")) self._error_message.actionTriggered.connect(self._queuePrint) self._error_message.show() return self._startPrint()
def getHasVariants(self) -> bool: return parseBool(self.getMetaDataEntry("has_variants", False))
def _serialiseSettings(self, stack): prefix = ";SETTING_" + str( GCodeWriter.version) + " " # The prefix to put before each line. prefix_length = len(prefix) container_with_profile = stack.qualityChanges if container_with_profile.getId() == "empty_quality_changes": Logger.log( "e", "No valid quality profile found, not writing settings to g-code!" ) return "" flat_global_container = self._createFlattenedContainerInstance( stack.getTop(), container_with_profile) # If the quality changes is not set, we need to set type manually if flat_global_container.getMetaDataEntry("type", None) is None: flat_global_container.addMetaDataEntry("type", "quality_changes") # Ensure that quality_type is set. (Can happen if we have empty quality changes). if flat_global_container.getMetaDataEntry("quality_type", None) is None: flat_global_container.addMetaDataEntry( "quality_type", stack.quality.getMetaDataEntry("quality_type", "normal")) # Change the default defintion default_machine_definition = "fdmprinter" if parseBool(stack.getMetaDataEntry("has_machine_quality", "False")): default_machine_definition = stack.getMetaDataEntry( "quality_definition") if not default_machine_definition: default_machine_definition = stack.definition.getId() flat_global_container.setMetaDataEntry("definition", default_machine_definition) serialized = flat_global_container.serialize() data = {"global_quality": serialized} for extruder in sorted(stack.extruders.values(), key=lambda k: k.getMetaDataEntry("position")): extruder_quality = extruder.qualityChanges if extruder_quality.getId() == "empty_quality_changes": Logger.log( "w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId()) continue flat_extruder_quality = self._createFlattenedContainerInstance( extruder.getTop(), extruder_quality) # If the quality changes is not set, we need to set type manually if flat_extruder_quality.getMetaDataEntry("type", None) is None: flat_extruder_quality.addMetaDataEntry("type", "quality_changes") # Ensure that extruder is set. (Can happen if we have empty quality changes). if flat_extruder_quality.getMetaDataEntry("extruder", None) is None: flat_extruder_quality.addMetaDataEntry( "extruder", extruder.getBottom().getId()) # Ensure that quality_type is set. (Can happen if we have empty quality changes). if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None: flat_extruder_quality.addMetaDataEntry( "quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal")) # Change the default defintion default_extruder_definition = "fdmextruder" if parseBool(stack.getMetaDataEntry("has_machine_quality", "False")): default_extruder_definition = extruder.getMetaDataEntry( "quality_definition") if not default_extruder_definition: default_extruder_definition = extruder.definition.getId() flat_extruder_quality.setMetaDataEntry( "definition", default_extruder_definition) extruder_serialized = flat_extruder_quality.serialize() data.setdefault("extruder_quality", []).append(extruder_serialized) json_string = json.dumps(data) # Escape characters that have a special meaning in g-code comments. pattern = re.compile("|".join(GCodeWriter.escape_characters.keys())) # Perform the replacement with a regular expression. escaped_string = pattern.sub( lambda m: GCodeWriter.escape_characters[re.escape(m.group(0))], json_string) # Introduce line breaks so that each comment is no longer than 80 characters. Prepend each line with the prefix. result = "" # Lines have 80 characters, so the payload of each line is 80 - prefix. for pos in range(0, len(escaped_string), 80 - prefix_length): result += prefix + escaped_string[pos:pos + 80 - prefix_length] + "\n" return result
def hasVariants(self) -> bool: return parseBool(self.getMetaDataEntry("has_variants", False))
def _machineHasOwnQualities(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False)) return False
def read(self, file_name): if file_name.split(".")[-1] != "ini": return None global_container_stack = Application.getInstance().getGlobalContainerStack() if not global_container_stack: return None multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 if multi_extrusion: Logger.log("e", "Unable to import legacy profile %s. Multi extrusion is not supported", file_name) raise Exception("Unable to import legacy profile. Multi extrusion is not supported") Logger.log("i", "Importing legacy profile from file " + file_name + ".") container_registry = ContainerRegistry.getInstance() profile_id = container_registry.uniqueName("Imported Legacy Profile") profile = InstanceContainer(profile_id) # Create an empty profile. parser = configparser.ConfigParser(interpolation = None) try: parser.read([file_name]) # Parse the INI file. except Exception as e: Logger.log("e", "Unable to open legacy profile %s: %s", file_name, str(e)) return None # Legacy Cura saved the profile under the section "profile_N" where N is the ID of a machine, except when you export in which case it saves it in the section "profile". # Since importing multiple machine profiles is out of scope, just import the first section we find. section = "" for found_section in parser.sections(): if found_section.startswith("profile"): section = found_section break if not section: # No section starting with "profile" was found. Probably not a proper INI file. return None try: with open(os.path.join(PluginRegistry.getInstance().getPluginPath("LegacyProfileReader"), "DictionaryOfDoom.json"), "r", encoding = "utf-8") as f: dict_of_doom = json.load(f) # Parse the Dictionary of Doom. except IOError as e: Logger.log("e", "Could not open DictionaryOfDoom.json for reading: %s", str(e)) return None except Exception as e: Logger.log("e", "Could not parse DictionaryOfDoom.json: %s", str(e)) return None defaults = self.prepareDefaults(dict_of_doom) legacy_settings = self.prepareLocals(parser, section, defaults) #Gets the settings from the legacy profile. #Check the target version in the Dictionary of Doom with this application version. if "target_version" not in dict_of_doom: Logger.log("e", "Dictionary of Doom has no target version. Is it the correct JSON file?") return None if InstanceContainer.Version != dict_of_doom["target_version"]: Logger.log("e", "Dictionary of Doom of legacy profile reader (version %s) is not in sync with the current instance container version (version %s)!", dict_of_doom["target_version"], str(InstanceContainer.Version)) return None if "translation" not in dict_of_doom: Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?") return None current_printer_definition = global_container_stack.definition quality_definition = current_printer_definition.getMetaDataEntry("quality_definition") if not quality_definition: quality_definition = current_printer_definition.getId() profile.setDefinition(quality_definition) for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations. old_setting_expression = dict_of_doom["translation"][new_setting] compiled = compile(old_setting_expression, new_setting, "eval") try: new_value = eval(compiled, {"math": math}, legacy_settings) # Pass the legacy settings as local variables to allow access to in the evaluation. value_using_defaults = eval(compiled, {"math": math}, defaults) #Evaluate again using only the default values to try to see if they are default. except Exception: # Probably some setting name that was missing or something else that went wrong in the ini file. Logger.log("w", "Setting " + new_setting + " could not be set because the evaluation failed. Something is probably missing from the imported legacy profile.") continue definitions = current_printer_definition.findDefinitions(key = new_setting) if definitions: if new_value != value_using_defaults and definitions[0].default_value != new_value: # Not equal to the default in the new Cura OR the default in the legacy Cura. profile.setProperty(new_setting, "value", new_value) # Store the setting in the profile! if len(profile.getAllKeys()) == 0: Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.") profile.addMetaDataEntry("type", "profile") # don't know what quality_type it is based on, so use "normal" by default profile.addMetaDataEntry("quality_type", "normal") profile.setName(profile_id) profile.setDirty(True) #Serialise and deserialise in order to perform the version upgrade. parser = configparser.ConfigParser(interpolation=None) data = profile.serialize() parser.read_string(data) parser["general"]["version"] = "1" if parser.has_section("values"): parser["settings"] = parser["values"] del parser["values"] stream = io.StringIO() parser.write(stream) data = stream.getvalue() profile.deserialize(data) # The definition can get reset to fdmprinter during the deserialization's upgrade. Here we set the definition # again. profile.setDefinition(quality_definition) #We need to return one extruder stack and one global stack. global_container_id = container_registry.uniqueName("Global Imported Legacy Profile") global_profile = profile.duplicate(new_id = global_container_id, new_name = profile_id) #Needs to have the same name as the extruder profile. global_profile.setDirty(True) profile_definition = "fdmprinter" from UM.Util import parseBool if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")): profile_definition = global_container_stack.getMetaDataEntry("quality_definition") if not profile_definition: profile_definition = global_container_stack.definition.getId() global_profile.setDefinition(profile_definition) return [global_profile]
def importProfile(self, file_name): Logger.log("d", "Attempting to import profile %s", file_name) if not file_name: return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path") } plugin_registry = PluginRegistry.getInstance() extension = file_name.split(".")[-1] global_container_stack = Application.getInstance( ).getGlobalContainerStack() if not global_container_stack: return machine_extruders = list( ExtruderManager.getInstance().getMachineExtruders( global_container_stack.getId())) machine_extruders.sort(key=lambda k: k.getMetaDataEntry("position")) for plugin_id, meta_data in self._getIOPlugins("profile_reader"): if meta_data["profile_reader"][0]["extension"] != extension: continue profile_reader = plugin_registry.getPluginObject(plugin_id) try: profile_or_list = profile_reader.read( file_name) # Try to open the file with the profile reader. except Exception as e: # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None. Logger.log( "e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e)) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)) } if profile_or_list: # Ensure it is always a list of profiles if not isinstance(profile_or_list, list): profile_or_list = [profile_or_list] # First check if this profile is suitable for this machine global_profile = None if len(profile_or_list) == 1: global_profile = profile_or_list[0] else: for profile in profile_or_list: if not profile.getMetaDataEntry("extruder"): global_profile = profile break if not global_profile: Logger.log( "e", "Incorrect profile [%s]. Could not find global profile", file_name) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name) } profile_definition = global_profile.getMetaDataEntry( "definition") expected_machine_definition = "fdmprinter" if parseBool( global_container_stack.getMetaDataEntry( "has_machine_quality", "False")): expected_machine_definition = global_container_stack.getMetaDataEntry( "quality_definition") if not expected_machine_definition: expected_machine_definition = global_container_stack.definition.getId( ) if expected_machine_definition is not None and profile_definition is not None and profile_definition != expected_machine_definition: Logger.log( "e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> doesn't match with your current machine, could not import it.", file_name) } name_seed = os.path.splitext(os.path.basename(file_name))[0] new_name = self.uniqueName(name_seed) # Ensure it is always a list of profiles if type(profile_or_list) is not list: profile_or_list = [profile_or_list] # Import all profiles for profile_index, profile in enumerate(profile_or_list): if profile_index == 0: # This is assumed to be the global profile profile_id = ( global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") elif profile_index < len(machine_extruders) + 1: if profile.id.startswith("base/"): break # This is assumed to be an extruder profile extruder_id = Application.getInstance( ).getMachineManager().getQualityDefinitionId( machine_extruders[profile_index - 1].getBottom()) if not profile.getMetaDataEntry("extruder"): profile.addMetaDataEntry("extruder", extruder_id) else: profile.setMetaDataEntry("extruder", extruder_id) profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_") else: #More extruders in the imported file than in the machine. continue #Delete the additional profiles. base_profile = None for p in profile_or_list: if p.id == "base/" + profile.id: base_profile = p break result = self._configureProfile(profile, profile_id, new_name, base_profile) if result is not None: return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, result) } return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName()) } # This message is throw when the profile reader doesn't find any profile in the file return { "status": "error", "message": catalog.i18nc("@info:status", "File {0} does not contain any valid profile.", file_name) } # If it hasn't returned by now, none of the plugins loaded the profile successfully. return { "status": "error", "message": catalog.i18nc( "@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name) }
def getHasVariantsBuildPlates(self) -> bool: return parseBool(self.getMetaDataEntry("has_variant_buildplates", False))
def getHasMachineQuality(self) -> bool: return parseBool(self.getMetaDataEntry("has_machine_quality", False))
def getQualityGroups(self, machine: "GlobalStack") -> dict: machine_definition_id = getMachineDefinitionIDForQualitySearch( machine.definition) # This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks has_variant_materials = parseBool( machine.getMetaDataEntry("has_variant_materials", False)) # To find the quality container for the GlobalStack, check in the following fall-back manner: # (1) the machine-specific node # (2) the generic node machine_node = self._machine_variant_material_quality_type_to_quality_dict.get( machine_definition_id) default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get( self._default_machine_definition_id) nodes_to_check = [machine_node, default_machine_node] # Iterate over all quality_types in the machine node quality_group_dict = {} for node in nodes_to_check: if node and node.quality_type_map: # Only include global qualities if has_variant_materials: quality_node = list(node.quality_type_map.values())[0] is_global_quality = parseBool( quality_node.metadata.get("global_quality", False)) if not is_global_quality: continue for quality_type, quality_node in node.quality_type_map.items( ): quality_group = QualityGroup(quality_node.metadata["name"], quality_type) quality_group.node_for_global = quality_node quality_group_dict[quality_type] = quality_group break # Iterate over all extruders to find quality containers for each extruder for position, extruder in machine.extruders.items(): variant_name = None if extruder.variant.getId() != "empty_variant": variant_name = extruder.variant.getName() # This is a list of root material IDs to use for searching for suitable quality profiles. # The root material IDs in this list are in prioritized order. root_material_id_list = [] has_material = False # flag indicating whether this extruder has a material assigned if extruder.material.getId() != "empty_material": has_material = True root_material_id = extruder.material.getMetaDataEntry( "base_file") # Convert possible generic_pla_175 -> generic_pla root_material_id = self._material_manager.getRootMaterialIDWithoutDiameter( root_material_id) root_material_id_list.append(root_material_id) # Also try to get the fallback material material_type = extruder.material.getMetaDataEntry("material") fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType( material_type) if fallback_root_material_id: root_material_id_list.append(fallback_root_material_id) # Here we construct a list of nodes we want to look for qualities with the highest priority first. # The use case is that, when we look for qualities for a machine, we first want to search in the following # order: # 1. machine-variant-and-material-specific qualities if exist # 2. machine-variant-specific qualities if exist # 3. machine-material-specific qualities if exist # 4. machine-specific qualities if exist # 5. generic qualities if exist # Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into # the list with priorities as the order. Later, we just need to loop over each node in this list and fetch # qualities from there. nodes_to_check = [] if variant_name: # In this case, we have both a specific variant and a specific material variant_node = machine_node.getChildNode(variant_name) if variant_node and has_material: for root_material_id in root_material_id_list: material_node = variant_node.getChildNode( root_material_id) if material_node: nodes_to_check.append(material_node) break nodes_to_check.append(variant_node) # In this case, we only have a specific material but NOT a variant if has_material: for root_material_id in root_material_id_list: material_node = machine_node.getChildNode(root_material_id) if material_node: nodes_to_check.append(material_node) break nodes_to_check += [machine_node, default_machine_node] for node in nodes_to_check: if node and node.quality_type_map: if has_variant_materials: # Only include variant qualities; skip non global qualities quality_node = list(node.quality_type_map.values())[0] is_global_quality = parseBool( quality_node.metadata.get("global_quality", False)) if is_global_quality: continue for quality_type, quality_node in node.quality_type_map.items( ): if quality_type not in quality_group_dict: quality_group = QualityGroup( quality_node.metadata["name"], quality_type) quality_group_dict[quality_type] = quality_group quality_group = quality_group_dict[quality_type] quality_group.nodes_for_extruders[ position] = quality_node break # Update availabilities for each quality group self._updateQualityGroupsAvailability(machine, quality_group_dict.values()) return quality_group_dict
def test_negative(value): assert not parseBool(value)
def test_positive(value): assert parseBool(value)
def deserialize(self, serialized): data = ET.fromstring(serialized) self.addMetaDataEntry("type", "material") self.addMetaDataEntry("base_file", self.id) # TODO: Add material verfication self.addMetaDataEntry("status", "unknown") 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.setName(label.text) else: self.setName(self._profile_name(material.text, color.text)) self.addMetaDataEntry("brand", brand.text) self.addMetaDataEntry("material", material.text) self.addMetaDataEntry("color_name", color.text) continue self.addMetaDataEntry(tag_name, entry.text) if not "description" in self.getMetaData(): self.addMetaDataEntry("description", "") if not "adhesion_info" in self.getMetaData(): self.addMetaDataEntry("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 self.addMetaDataEntry("properties", property_values) self.setDefinition(UM.Settings.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: self.setProperty(self.__material_property_setting_map[key], "value", entry.text, self._definition) 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._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) 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: Logger.log("w", "Cannot create material for unknown machine %s", identifier.get("product")) continue definitions = UM.Settings.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 = XmlMaterialProfile(self.id + "_" + machine_id) new_material.setName(self.getName()) new_material.setMetaData(copy.deepcopy(self.getMetaData())) new_material.setDefinition(definition) for key, value in global_setting_values.items(): new_material.setProperty(key, "value", value, definition) for key, value in machine_setting_values.items(): new_material.setProperty(key, "value", value, definition) new_material._dirty = False UM.Settings.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 = UM.Settings.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 = UM.Settings.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) if not hotend_compatibility: continue new_hotend_material = XmlMaterialProfile(self.id + "_" + machine_id + "_" + hotend_id.replace(" ", "_")) new_hotend_material.setName(self.getName()) new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData())) new_hotend_material.setDefinition(definition) new_hotend_material.addMetaDataEntry("variant", variant_containers[0].id) for key, value in global_setting_values.items(): new_hotend_material.setProperty(key, "value", value, definition) for key, value in machine_setting_values.items(): new_hotend_material.setProperty(key, "value", value, definition) for key, value in hotend_setting_values.items(): new_hotend_material.setProperty(key, "value", value, definition) new_hotend_material._dirty = False UM.Settings.ContainerRegistry.getInstance().addContainer(new_hotend_material) if not global_compatibility: # Change the type of this container so it is not shown as an option in menus. # This uses InstanceContainer.setMetaDataEntry because otherwise all containers that # share this basefile are also updated. super().setMetaDataEntry("type", "incompatible_material")
def _startPrint(self): if self._auto_print and not self._forced_queue: Application.getInstance().getController().setActiveStage( "MonitorStage") # cancel any ongoing preheat timer before starting a print try: self._printers[0].stopPreheatTimers() except AttributeError: # stopPreheatTimers was added after Cura 3.3 beta pass self._progress_message = Message( i18n_catalog.i18nc("@info:status", "Sending data to OctoPrint"), 0, False, -1) self._progress_message.addAction( "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None, "") self._progress_message.actionTriggered.connect(self._cancelSendGcode) self._progress_message.show() ## Mash the data into single string single_string_file_data = "" last_process_events = time() for line in self._gcode: single_string_file_data += line if time() > last_process_events + 0.05: # Ensure that the GUI keeps updated at least 20 times per second. QCoreApplication.processEvents() last_process_events = time() job_name = Application.getInstance().getPrintInformation( ).jobName.strip() if job_name is "": job_name = "untitled_print" file_name = "%s.gcode" % job_name ## Create multi_part request self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) ## Create parts (to be placed inside multipart) post_part = QHttpPart() post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"select\"") post_part.setBody(b"true") self._post_multi_part.append(post_part) if self._auto_print and not self._forced_queue: post_part = QHttpPart() post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"print\"") post_part.setBody(b"true") self._post_multi_part.append(post_part) post_part = QHttpPart() post_part.setHeader( QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name) post_part.setBody(single_string_file_data.encode()) self._post_multi_part.append(post_part) destination = "local" if self._sd_supported and parseBool(Application.getInstance( ).getGlobalContainerStack().getMetaDataEntry("octoprint_store_sd", False)): destination = "sdcard" try: ## Post request + data post_request = self._createApiRequest("files/" + destination) self._post_reply = self._manager.post(post_request, self._post_multi_part) self._post_reply.uploadProgress.connect(self._onUploadProgress) except IOError: self._progress_message.hide() self._error_message = Message( i18n_catalog.i18nc("@info:status", "Unable to send data to OctoPrint.")) self._error_message.show() except Exception as e: self._progress_message.hide() Logger.log( "e", "An exception occurred in network connection: %s" % str(e)) self._gcode = None
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]: """ Upgrades instance containers to have the new version number. This changes the maximum deviation setting if that setting was present in the profile. :param serialized: The original contents of the instance container. :param filename: The original file name of the instance container. :return: A list of new file names, and a list of the new contents for those files. """ parser = configparser.ConfigParser(interpolation=None, comment_prefixes=()) parser.read_string(serialized) # Update version number. parser["metadata"]["setting_version"] = "15" if "values" in parser: # Maximum Deviation's effect was corrected. Previously the deviation # ended up being only half of what the user had entered. This was # fixed in Cura 4.7 so there we need to halve the deviation that the # user had entered. # # This got accidentally merged in Cura 4.6.0. In 4.6.2 we removed # that. In 4.7 it's not unmerged, so there we need to revert all # that again. if "meshfix_maximum_deviation" in parser["values"]: maximum_deviation = parser["values"][ "meshfix_maximum_deviation"] if maximum_deviation.startswith("="): maximum_deviation = maximum_deviation[1:] maximum_deviation = "=(" + maximum_deviation + ") / 2" parser["values"][ "meshfix_maximum_deviation"] = maximum_deviation # Ironing inset is now based on the flow-compensated line width to make the setting have a more logical UX. # Adjust so that the actual print result remains the same. if "ironing_inset" in parser["values"]: ironing_inset = parser["values"]["ironing_inset"] if ironing_inset.startswith("="): ironing_inset = ironing_inset[1:] if "ironing_pattern" in parser["values"] and parser["values"][ "ironing_pattern"] == "concentric": correction = " + ironing_line_spacing - skin_line_width * (1.0 + ironing_flow / 100) / 2" else: # If ironing_pattern doesn't exist, it means the default (zigzag) is selected correction = " + skin_line_width * (1.0 - ironing_flow / 100) / 2" ironing_inset = "=(" + ironing_inset + ")" + correction parser["values"]["ironing_inset"] = ironing_inset # Set support_structure if necessary if "support_tree_enable" in parser["values"]: if parseBool(parser["values"]["support_tree_enable"]): parser["values"]["support_structure"] = "tree" parser["values"]["support_enable"] = "True" for removed in set( parser["values"].keys()).intersection(_removed_settings): del parser["values"][removed] # Check renamed definitions if "definition" in parser["general"] and parser["general"][ "definition"] in _RENAMED_DEFINITION_DICT: parser["general"]["definition"] = _RENAMED_DEFINITION_DICT[ parser["general"]["definition"]] result = io.StringIO() parser.write(result) return [filename], [result.getvalue()]
def hasVariants(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: return parseBool(global_container_stack.getMetaDataEntry("has_variants", "false"))
def _startPrint(self) -> None: global_container_stack = CuraApplication.getInstance( ).getGlobalContainerStack() if not global_container_stack: return if self._auto_print and not self._forced_queue: CuraApplication.getInstance().getController().setActiveStage( "MonitorStage") # cancel any ongoing preheat timer before starting a print try: self._printers[0].stopPreheatTimers() except AttributeError: # stopPreheatTimers was added after Cura 3.3 beta pass self._progress_message = Message( i18n_catalog.i18nc("@info:status", "Sending data to OctoPrint"), 0, False, -1) self._progress_message.addAction( "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), "", "") self._progress_message.actionTriggered.connect(self._cancelSendGcode) self._progress_message.show() job_name = CuraApplication.getInstance().getPrintInformation( ).jobName.strip() if job_name is "": job_name = "untitled_print" extension = "gcode" if not self._ufp_supported else "ufp" file_name = "%s.%s" % (job_name, extension) ## Create multi_part request post_parts = [] # type: List[QHttpPart] ## Create parts (to be placed inside multipart) post_part = QHttpPart() post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"select\"") post_part.setBody(b"true") post_parts.append(post_part) if self._auto_print and not self._forced_queue: post_part = QHttpPart() post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"print\"") post_part.setBody(b"true") post_parts.append(post_part) gcode_body = self._gcode_stream.getvalue() try: # encode StringIO result to bytes gcode_body = gcode_body.encode() except AttributeError: # encode BytesIO is already byte-encoded pass post_part = QHttpPart() post_part.setHeader( QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name) post_part.setBody(gcode_body) post_parts.append(post_part) destination = "local" if self._sd_supported and parseBool( global_container_stack.getMetaDataEntry( "octoprint_store_sd", False)): destination = "sdcard" try: ## Post request + data post_request = self._createEmptyRequest("files/" + destination) self._post_reply = self.postFormWithParts( "files/" + destination, post_parts, on_finished=self._onRequestFinished, on_progress=self._onUploadProgress) except IOError: self._progress_message.hide() self._error_message = Message( i18n_catalog.i18nc("@info:status", "Unable to send data to OctoPrint.")) self._error_message.show() except Exception as e: self._progress_message.hide() Logger.log( "e", "An exception occurred in network connection: %s" % str(e)) self._gcode_stream = None # type: Optional[Union[StringIO, BytesIO]]
def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]: machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition) # This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks has_machine_specific_qualities = machine.getHasMachineQuality() # To find the quality container for the GlobalStack, check in the following fall-back manner: # (1) the machine-specific node # (2) the generic node machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id) # Check if this machine has specific quality profiles for its extruders, if so, when looking up extruder # qualities, we should not fall back to use the global qualities. has_extruder_specific_qualities = False if machine_node: if machine_node.children_map: has_extruder_specific_qualities = True default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id) nodes_to_check = [] # type: List[QualityNode] if machine_node is not None: nodes_to_check.append(machine_node) if default_machine_node is not None: nodes_to_check.append(default_machine_node) # Iterate over all quality_types in the machine node quality_group_dict = {} for node in nodes_to_check: if node and node.quality_type_map: quality_node = list(node.quality_type_map.values())[0] is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False)) if not is_global_quality: continue for quality_type, quality_node in node.quality_type_map.items(): quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type) quality_group.setGlobalNode(quality_node) quality_group_dict[quality_type] = quality_group break buildplate_name = machine.getBuildplateName() # Iterate over all extruders to find quality containers for each extruder for position, extruder in machine.extruders.items(): nozzle_name = None if extruder.variant.getId() != "empty_variant": nozzle_name = extruder.variant.getName() # This is a list of root material IDs to use for searching for suitable quality profiles. # The root material IDs in this list are in prioritized order. root_material_id_list = [] has_material = False # flag indicating whether this extruder has a material assigned root_material_id = None if extruder.material.getId() != "empty_material": has_material = True root_material_id = extruder.material.getMetaDataEntry("base_file") # Convert possible generic_pla_175 -> generic_pla root_material_id = self._material_manager.getRootMaterialIDWithoutDiameter(root_material_id) root_material_id_list.append(root_material_id) # Also try to get the fallback materials fallback_ids = self._material_manager.getFallBackMaterialIdsByMaterial(extruder.material) if fallback_ids: root_material_id_list.extend(fallback_ids) # Weed out duplicates while preserving the order. seen = set() # type: Set[str] root_material_id_list = [x for x in root_material_id_list if x not in seen and not seen.add(x)] # type: ignore # Here we construct a list of nodes we want to look for qualities with the highest priority first. # The use case is that, when we look for qualities for a machine, we first want to search in the following # order: # 1. machine-nozzle-buildplate-and-material-specific qualities if exist # 2. machine-nozzle-and-material-specific qualities if exist # 3. machine-nozzle-specific qualities if exist # 4. machine-material-specific qualities if exist # 5. machine-specific global qualities if exist, otherwise generic global qualities # NOTE: We DO NOT fail back to generic global qualities if machine-specific global qualities exist. # This is because when a machine defines its own global qualities such as Normal, Fine, etc., # it is intended to maintain those specific qualities ONLY. If we still fail back to the generic # global qualities, there can be unimplemented quality types e.g. "coarse", and this is not # correct. # Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into # the list with priorities as the order. Later, we just need to loop over each node in this list and fetch # qualities from there. node_info_list_0 = [nozzle_name, buildplate_name, root_material_id] # type: List[Optional[str]] nodes_to_check = [] # This function tries to recursively find the deepest (the most specific) branch and add those nodes to # the search list in the order described above. So, by iterating over that search node list, we first look # in the more specific branches and then the less specific (generic) ones. def addNodesToCheck(node: Optional[QualityNode], nodes_to_check_list: List[QualityNode], node_info_list, node_info_idx: int) -> None: if node is None: return if node_info_idx < len(node_info_list): node_name = node_info_list[node_info_idx] if node_name is not None: current_node = node.getChildNode(node_name) if current_node is not None and has_material: addNodesToCheck(current_node, nodes_to_check_list, node_info_list, node_info_idx + 1) if has_material: for rmid in root_material_id_list: material_node = node.getChildNode(rmid) if material_node: nodes_to_check_list.append(material_node) break nodes_to_check_list.append(node) addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0) # The last fall back will be the global qualities (either from the machine-specific node or the generic # node), but we only use one. For details see the overview comments above. if machine_node is not None and machine_node.quality_type_map: nodes_to_check += [machine_node] elif default_machine_node is not None: nodes_to_check += [default_machine_node] for node_idx, node in enumerate(nodes_to_check): if node and node.quality_type_map: if has_extruder_specific_qualities: # Only include variant qualities; skip non global qualities quality_node = list(node.quality_type_map.values())[0] is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False)) if is_global_quality: continue for quality_type, quality_node in node.quality_type_map.items(): if quality_type not in quality_group_dict: quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type) quality_group_dict[quality_type] = quality_group quality_group = quality_group_dict[quality_type] if position not in quality_group.nodes_for_extruders: quality_group.setExtruderNode(position, quality_node) # If the machine has its own specific qualities, for extruders, it should skip the global qualities # and use the material/variant specific qualities. if has_extruder_specific_qualities: if node_idx == len(nodes_to_check) - 1: break # Update availabilities for each quality group self._updateQualityGroupsAvailability(machine, quality_group_dict.values()) return quality_group_dict
def importProfile(self, file_name: str) -> Dict[str, str]: Logger.log("d", "Attempting to import profile %s", file_name) if not file_name: return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Failed to import profile from <filename>{0}</filename>: {1}", file_name, "Invalid path")} global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() if not global_stack: return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Can't import profile from <filename>{0}</filename> before a printer is added.", file_name)} container_tree = ContainerTree.getInstance() machine_extruders = global_stack.extruderList plugin_registry = PluginRegistry.getInstance() extension = file_name.split(".")[-1] for plugin_id, meta_data in self._getIOPlugins("profile_reader"): if meta_data["profile_reader"][0]["extension"] != extension: continue profile_reader = cast(ProfileReader, plugin_registry.getPluginObject(plugin_id)) try: profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader. except NoProfileException: return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "No custom profile to import in file <filename>{0}</filename>", file_name)} except Exception as e: # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None. Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e)) return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Failed to import profile from <filename>{0}</filename>:", file_name) + "\n<message>" + str(e) + "</message>"} if profile_or_list: # Ensure it is always a list of profiles if not isinstance(profile_or_list, list): profile_or_list = [profile_or_list] # First check if this profile is suitable for this machine global_profile = None extruder_profiles = [] if len(profile_or_list) == 1: global_profile = profile_or_list[0] else: for profile in profile_or_list: if not profile.getMetaDataEntry("position"): global_profile = profile else: extruder_profiles.append(profile) extruder_profiles = sorted(extruder_profiles, key = lambda x: int(x.getMetaDataEntry("position"))) profile_or_list = [global_profile] + extruder_profiles if not global_profile: Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name) return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)} profile_definition = global_profile.getMetaDataEntry("definition") # Make sure we have a profile_definition in the file: if profile_definition is None: break machine_definitions = self.findContainers(id = profile_definition) if not machine_definitions: Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition) return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name) } machine_definition = machine_definitions[0] # Get the expected machine definition. # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode... has_machine_quality = parseBool(machine_definition.getMetaDataEntry("has_machine_quality", "false")) profile_definition = machine_definition.getMetaDataEntry("quality_definition", machine_definition.getId()) if has_machine_quality else "fdmprinter" expected_machine_definition = container_tree.machines[global_stack.definition.getId()].quality_definition # And check if the profile_definition matches either one (showing error if not): if profile_definition != expected_machine_definition: Logger.log("d", "Profile {file_name} is for machine {profile_definition}, but the current active machine is {expected_machine_definition}. Changing profile's definition.".format(file_name = file_name, profile_definition = profile_definition, expected_machine_definition = expected_machine_definition)) global_profile.setMetaDataEntry("definition", expected_machine_definition) for extruder_profile in extruder_profiles: extruder_profile.setMetaDataEntry("definition", expected_machine_definition) quality_name = global_profile.getName() quality_type = global_profile.getMetaDataEntry("quality_type") name_seed = os.path.splitext(os.path.basename(file_name))[0] new_name = self.uniqueName(name_seed) # Ensure it is always a list of profiles if type(profile_or_list) is not list: profile_or_list = [profile_or_list] # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack if len(profile_or_list) == 1: global_profile = profile_or_list[0] extruder_profiles = [] for idx, extruder in enumerate(global_stack.extruderList): profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1)) profile = InstanceContainer(profile_id) profile.setName(quality_name) profile.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion) profile.setMetaDataEntry("type", "quality_changes") profile.setMetaDataEntry("definition", expected_machine_definition) profile.setMetaDataEntry("quality_type", quality_type) profile.setDirty(True) if idx == 0: # Move all per-extruder settings to the first extruder's quality_changes for qc_setting_key in global_profile.getAllKeys(): settable_per_extruder = global_stack.getProperty(qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = global_profile.getProperty(qc_setting_key, "value") setting_definition = global_stack.getSettingDefinition(qc_setting_key) if setting_definition is not None: new_instance = SettingInstance(setting_definition, profile) new_instance.setProperty("value", setting_value) new_instance.resetState() # Ensure that the state is not seen as a user state. profile.addInstance(new_instance) profile.setDirty(True) global_profile.removeInstance(qc_setting_key, postpone_emit = True) extruder_profiles.append(profile) for profile in extruder_profiles: profile_or_list.append(profile) # Import all profiles profile_ids_added = [] # type: List[str] for profile_index, profile in enumerate(profile_or_list): if profile_index == 0: # This is assumed to be the global profile profile_id = (cast(ContainerInterface, global_stack.getBottom()).getId() + "_" + name_seed).lower().replace(" ", "_") elif profile_index < len(machine_extruders) + 1: # This is assumed to be an extruder profile extruder_id = machine_extruders[profile_index - 1].definition.getId() extruder_position = str(profile_index - 1) if not profile.getMetaDataEntry("position"): profile.setMetaDataEntry("position", extruder_position) else: profile.setMetaDataEntry("position", extruder_position) profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_") else: # More extruders in the imported file than in the machine. continue # Delete the additional profiles. result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition) if result is not None: # Remove any profiles that did got added. for profile_id in profile_ids_added: self.removeContainer(profile_id) return {"status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tag <filename>!", "Failed to import profile from <filename>{0}</filename>:", file_name) + " " + result} profile_ids_added.append(profile.getId()) return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())} # This message is throw when the profile reader doesn't find any profile in the file return {"status": "error", "message": catalog.i18nc("@info:status", "File {0} does not contain any valid profile.", file_name)} # If it hasn't returned by now, none of the plugins loaded the profile successfully. return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
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 _onGetRemoteClustersFinished( self, clusters: List[CloudClusterResponse]) -> None: """Callback for when the request for getting the clusters is successful and finished.""" self._um_cloud_printers = { m.getMetaDataEntry(self.META_CLUSTER_ID): m for m in CuraApplication.getInstance().getContainerRegistry(). findContainerStacks(type="machine") if m.getMetaDataEntry(self.META_CLUSTER_ID, None) } new_clusters = [] all_clusters = {c.cluster_id: c for c in clusters } # type: Dict[str, CloudClusterResponse] online_clusters = {c.cluster_id: c for c in clusters if c.is_online } # type: Dict[str, CloudClusterResponse] # Add the new printers in Cura. for device_id, cluster_data in all_clusters.items(): if device_id not in self._remote_clusters: new_clusters.append(cluster_data) if device_id in self._um_cloud_printers: # Existing cloud printers may not have the host_guid meta-data entry. If that's the case, add it. if not self._um_cloud_printers[device_id].getMetaDataEntry( self.META_HOST_GUID, None): self._um_cloud_printers[device_id].setMetaDataEntry( self.META_HOST_GUID, cluster_data.host_guid) # If a printer was previously not linked to the account and is rediscovered, mark the printer as linked # to the current account if not parseBool( self._um_cloud_printers[device_id].getMetaDataEntry( META_UM_LINKED_TO_ACCOUNT, "true")): self._um_cloud_printers[device_id].setMetaDataEntry( META_UM_LINKED_TO_ACCOUNT, True) self._onDevicesDiscovered(new_clusters) # Hide the current removed_printers_message, if there is any if self._removed_printers_message: self._removed_printers_message.actionTriggered.disconnect( self._onRemovedPrintersMessageActionTriggered) self._removed_printers_message.hide() # Remove the CloudOutput device for offline printers offline_device_keys = set(self._remote_clusters.keys()) - set( online_clusters.keys()) for device_id in offline_device_keys: self._onDiscoveredDeviceRemoved(device_id) # Handle devices that were previously added in Cura but do not exist in the account anymore (i.e. they were # removed from the account) removed_device_keys = set(self._um_cloud_printers.keys()) - set( all_clusters.keys()) if removed_device_keys: self._devicesRemovedFromAccount(removed_device_keys) if new_clusters or offline_device_keys or removed_device_keys: self.discoveredDevicesChanged.emit() if offline_device_keys: # If the removed device was active we should connect to the new active device self._connectToActiveMachine() self._syncing = False self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SUCCESS)
def hasMaterials(self) -> bool: return parseBool(self.getMetaDataEntry("has_materials", False))
def _onDevicesDiscovered(self, clusters: List[CloudClusterResponse]) -> None: """**Synchronously** create machines for discovered devices Any new machines are made available to the user. May take a long time to complete. As this code needs access to the Application and blocks the GIL, creating a Job for this would not make sense. Shows a Message informing the user of progress. """ new_devices = [] remote_clusters_added = False host_guid_map = { machine.getMetaDataEntry(self.META_HOST_GUID): device_cluster_id for device_cluster_id, machine in self._um_cloud_printers.items() if machine.getMetaDataEntry(self.META_HOST_GUID) } machine_manager = CuraApplication.getInstance().getMachineManager() for cluster_data in clusters: device = CloudOutputDevice(self._api, cluster_data) # If the machine already existed before, it will be present in the host_guid_map if cluster_data.host_guid in host_guid_map: machine = machine_manager.getMachine( device.printerType, {self.META_HOST_GUID: cluster_data.host_guid}) if machine and machine.getMetaDataEntry( self.META_CLUSTER_ID) != device.key: # If the retrieved device has a different cluster_id than the existing machine, bring the existing # machine up-to-date. self._updateOutdatedMachine(outdated_machine=machine, new_cloud_output_device=device) # Create a machine if we don't already have it. Do not make it the active machine. # We only need to add it if it wasn't already added by "local" network or by cloud. if machine_manager.getMachine(device.printerType, {self.META_CLUSTER_ID: device.key}) is None \ and machine_manager.getMachine(device.printerType, {self.META_NETWORK_KEY: cluster_data.host_name + "*"}) is None: # The host name is part of the network key. new_devices.append(device) elif device.getId() not in self._remote_clusters: self._remote_clusters[device.getId()] = device remote_clusters_added = True # If a printer that was removed from the account is re-added, change its metadata to mark it not removed # from the account elif not parseBool( self._um_cloud_printers[device.key].getMetaDataEntry( META_UM_LINKED_TO_ACCOUNT, "true")): self._um_cloud_printers[device.key].setMetaDataEntry( META_UM_LINKED_TO_ACCOUNT, True) # Inform the Cloud printers model about new devices. new_devices_list_of_dicts = [{ "key": d.getId(), "name": d.name, "machine_type": d.printerTypeName, "firmware_version": d.firmwareVersion } for d in new_devices] discovered_cloud_printers_model = CuraApplication.getInstance( ).getDiscoveredCloudPrintersModel() discovered_cloud_printers_model.addDiscoveredCloudPrinters( new_devices_list_of_dicts) if not new_devices: if remote_clusters_added: self._connectToActiveMachine() return # Sort new_devices on online status first, alphabetical second. # Since the first device might be activated in case there is no active printer yet, # it would be nice to prioritize online devices online_cluster_names = { c.friendly_name.lower() for c in clusters if c.is_online and not c.friendly_name is None } new_devices.sort(key=lambda x: ("a{}" if x.name.lower( ) in online_cluster_names else "b{}").format(x.name.lower())) image_path = os.path.join( CuraApplication.getInstance().getPluginRegistry().getPluginPath( "UM3NetworkPrinting") or "", "resources", "svg", "cloud-flow-completed.svg") message = Message(title=self.I18N_CATALOG.i18ncp( "info:status", "New printer detected from your Ultimaker account", "New printers detected from your Ultimaker account", len(new_devices)), progress=0, lifetime=0, image_source=image_path) message.show() for idx, device in enumerate(new_devices): message_text = self.I18N_CATALOG.i18nc( "info:status", "Adding printer {} ({}) from your account", device.name, device.printerTypeName) message.setText(message_text) if len(new_devices) > 1: message.setProgress((idx / len(new_devices)) * 100) CuraApplication.getInstance().processEvents() self._remote_clusters[device.getId()] = device # If there is no active machine, activate the first available cloud printer activate = not CuraApplication.getInstance().getMachineManager( ).activeMachine self._createMachineFromDiscoveredDevice(device.getId(), activate=activate) message.setProgress(None) max_disp_devices = 3 if len(new_devices) > max_disp_devices: num_hidden = len(new_devices) - max_disp_devices device_name_list = [ "<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in new_devices[0:max_disp_devices] ] device_name_list.append( self.I18N_CATALOG.i18nc("info:hidden list items", "<li>... and {} others</li>", num_hidden)) device_names = "".join(device_name_list) else: device_names = "".join([ "<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in new_devices ]) message_text = self.I18N_CATALOG.i18nc( "info:status", "Cloud printers added from your account:<ul>{}</ul>", device_names) message.setText(message_text)
def hasVariantBuildplates(self) -> bool: return parseBool(self.getMetaDataEntry("has_variant_buildplates", False))
def _devicesRemovedFromAccount(self, removed_device_ids: Set[str]) -> None: """ Removes the CloudOutputDevice from the received device ids and marks the specific printers as "removed from account". In addition, it generates a message to inform the user about the printers that are no longer linked to his/her account. The message is not generated if all the printers have been previously reported as not linked to the account. :param removed_device_ids: Set of device ids, whose CloudOutputDevice needs to be removed :return: None """ if not CuraApplication.getInstance().getCuraAPI().account.isLoggedIn: return # Do not report device ids which have been previously marked as non-linked to the account ignored_device_ids = set() for device_id in removed_device_ids: if not parseBool( self._um_cloud_printers[device_id].getMetaDataEntry( META_UM_LINKED_TO_ACCOUNT, "true")): ignored_device_ids.add(device_id) # Keep the reported_device_ids list in a class variable, so that the message button actions can access it and # take the necessary steps to fulfill their purpose. self.reported_device_ids = removed_device_ids - ignored_device_ids if not self.reported_device_ids: return # Generate message self._removed_printers_message = Message( title=self.I18N_CATALOG.i18ncp( "info:status", "Cloud connection is not available for a printer", "Cloud connection is not available for some printers", len(self.reported_device_ids))) device_names = "\n".join([ "<li>{} ({})</li>".format( self._um_cloud_printers[device].name, self._um_cloud_printers[device].definition.name) for device in self.reported_device_ids ]) message_text = self.I18N_CATALOG.i18ncp( "info:status", "The following cloud printer is not linked to your account:\n", "The following cloud printers are not linked to your account:\n", len(self.reported_device_ids)) message_text += self.I18N_CATALOG.i18nc( "info:status", "<ul>{}</ul>\nTo establish a connection, please visit the " "<a href='https://mycloud.ultimaker.com/'>Ultimaker Digital Factory</a>.", device_names) self._removed_printers_message.setText(message_text) self._removed_printers_message.addAction( "keep_printer_configurations_action", name=self.I18N_CATALOG.i18nc("@action:button", "Keep printer configurations"), icon="", description= "Keep the configuration of the cloud printer(s) synced with Cura which are not linked to your account.", button_align=Message.ActionButtonAlignment.ALIGN_RIGHT) self._removed_printers_message.addAction( "remove_printers_action", name=self.I18N_CATALOG.i18nc("@action:button", "Remove printers"), icon="", description= "Remove the cloud printer(s) which are not linked to your account.", button_style=Message.ActionButtonStyle.SECONDARY, button_align=Message.ActionButtonAlignment.ALIGN_LEFT) self._removed_printers_message.actionTriggered.connect( self._onRemovedPrintersMessageActionTriggered) output_device_manager = CuraApplication.getInstance( ).getOutputDeviceManager() # Remove the output device from the printers for device_id in removed_device_ids: device = self._um_cloud_printers.get( device_id, None) # type: Optional[GlobalStack] if not device: continue if device_id in output_device_manager.getOutputDeviceIds(): output_device_manager.removeOutputDevice(device_id) if device_id in self._remote_clusters: del self._remote_clusters[device_id] # Update the printer's metadata to mark it as not linked to the account device.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, False) self._removed_printers_message.show()
def getHasMaterials(self) -> bool: return parseBool(self.getMetaDataEntry("has_materials", False))
def hasVariants(self): if self._current_global_stack: return parseBool(self._current_global_stack.getMetaDataEntry("has_variants", "false"))
def read(self, file_name): """Reads a legacy Cura profile from a file and returns it. :param file_name: The file to read the legacy Cura profile from. :return: The legacy Cura profile that was in the file, if any. If the file could not be read or didn't contain a valid profile, None is returned. """ if file_name.split(".")[-1] != "ini": return None global_container_stack = Application.getInstance().getGlobalContainerStack() if not global_container_stack: return None multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 if multi_extrusion: Logger.log("e", "Unable to import legacy profile %s. Multi extrusion is not supported", file_name) raise Exception("Unable to import legacy profile. Multi extrusion is not supported") Logger.log("i", "Importing legacy profile from file " + file_name + ".") container_registry = ContainerRegistry.getInstance() profile_id = container_registry.uniqueName("Imported Legacy Profile") input_parser = configparser.ConfigParser(interpolation = None) try: input_parser.read([file_name]) # Parse the INI file. except Exception as e: Logger.log("e", "Unable to open legacy profile %s: %s", file_name, str(e)) return None # Legacy Cura saved the profile under the section "profile_N" where N is the ID of a machine, except when you export in which case it saves it in the section "profile". # Since importing multiple machine profiles is out of scope, just import the first section we find. section = "" for found_section in input_parser.sections(): if found_section.startswith("profile"): section = found_section break if not section: # No section starting with "profile" was found. Probably not a proper INI file. return None try: with open(os.path.join(PluginRegistry.getInstance().getPluginPath("LegacyProfileReader"), "DictionaryOfDoom.json"), "r", encoding = "utf-8") as f: dict_of_doom = json.load(f) # Parse the Dictionary of Doom. except IOError as e: Logger.log("e", "Could not open DictionaryOfDoom.json for reading: %s", str(e)) return None except Exception as e: Logger.log("e", "Could not parse DictionaryOfDoom.json: %s", str(e)) return None defaults = self.prepareDefaults(dict_of_doom) legacy_settings = self.prepareLocals(input_parser, section, defaults) #Gets the settings from the legacy profile. # Serialised format into version 4.5. Do NOT upgrade this, let the version upgrader handle it. output_parser = configparser.ConfigParser(interpolation = None) output_parser.add_section("general") output_parser.add_section("metadata") output_parser.add_section("values") if "translation" not in dict_of_doom: Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?") return None current_printer_definition = global_container_stack.definition quality_definition = current_printer_definition.getMetaDataEntry("quality_definition") if not quality_definition: quality_definition = current_printer_definition.getId() output_parser["general"]["definition"] = quality_definition for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations. old_setting_expression = dict_of_doom["translation"][new_setting] compiled = compile(old_setting_expression, new_setting, "eval") try: new_value = eval(compiled, {"math": math}, legacy_settings) # Pass the legacy settings as local variables to allow access to in the evaluation. value_using_defaults = eval(compiled, {"math": math}, defaults) #Evaluate again using only the default values to try to see if they are default. except Exception: # Probably some setting name that was missing or something else that went wrong in the ini file. Logger.log("w", "Setting " + new_setting + " could not be set because the evaluation failed. Something is probably missing from the imported legacy profile.") continue definitions = current_printer_definition.findDefinitions(key = new_setting) if definitions: if new_value != value_using_defaults and definitions[0].default_value != new_value: # Not equal to the default in the new Cura OR the default in the legacy Cura. output_parser["values"][new_setting] = str(new_value) # Store the setting in the profile! if len(output_parser["values"]) == 0: Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.") output_parser["general"]["version"] = "4" output_parser["general"]["name"] = profile_id output_parser["metadata"]["type"] = "quality_changes" output_parser["metadata"]["quality_type"] = "normal" # Don't know what quality_type it is based on, so use "normal" by default. output_parser["metadata"]["position"] = "0" # We only support single extrusion. output_parser["metadata"]["setting_version"] = "5" # What the dictionary of doom is made for. # Serialise in order to perform the version upgrade. stream = io.StringIO() output_parser.write(stream) data = stream.getvalue() profile = InstanceContainer(profile_id) profile.deserialize(data, file_name) # Also performs the version upgrade. profile.setDirty(True) #We need to return one extruder stack and one global stack. global_container_id = container_registry.uniqueName("Global Imported Legacy Profile") # We duplicate the extruder profile into the global stack. # This may introduce some settings that are global in the extruder stack and some settings that are per-extruder in the global stack. # We don't care about that. The engine will ignore them anyway. global_profile = profile.duplicate(new_id = global_container_id, new_name = profile_id) #Needs to have the same name as the extruder profile. del global_profile.getMetaData()["position"] # Has no position because it's global. global_profile.setDirty(True) profile_definition = "fdmprinter" from UM.Util import parseBool if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")): profile_definition = global_container_stack.getMetaDataEntry("quality_definition") if not profile_definition: profile_definition = global_container_stack.definition.getId() global_profile.setDefinition(profile_definition) return [global_profile]
def isEnabled(self) -> bool: return parseBool(self.getMetaDataEntry("enabled", "True"))
def setGlobalNode(self, node: "ContainerNode") -> None: self.node_for_global = node # Update is_experimental flag is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False)) self.is_experimental |= is_experimental
def 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 not "description" in meta_data: meta_data["description"] = "" if not "adhesion_info" 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["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 hasVariants(self): if self._current_global_stack: return parseBool( self._current_global_stack.getMetaDataEntry( "has_variants", "false"))
def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]: machine_definition_id = getMachineDefinitionIDForQualitySearch( machine.definition) # To find the quality container for the GlobalStack, check in the following fall-back manner: # (1) the machine-specific node # (2) the generic node machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get( machine_definition_id) # Check if this machine has specific quality profiles for its extruders, if so, when looking up extruder # qualities, we should not fall back to use the global qualities. has_extruder_specific_qualities = False if machine_node: if machine_node.children_map: has_extruder_specific_qualities = True default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get( self._default_machine_definition_id) nodes_to_check = [] # type: List[QualityNode] if machine_node is not None: nodes_to_check.append(machine_node) if default_machine_node is not None: nodes_to_check.append(default_machine_node) # Iterate over all quality_types in the machine node quality_group_dict = {} for node in nodes_to_check: if node and node.quality_type_map: quality_node = list(node.quality_type_map.values())[0] is_global_quality = parseBool( quality_node.getMetaDataEntry("global_quality", False)) if not is_global_quality: continue for quality_type, quality_node in node.quality_type_map.items( ): quality_group = QualityGroup( quality_node.getMetaDataEntry("name", ""), quality_type) quality_group.setGlobalNode(quality_node) quality_group_dict[quality_type] = quality_group break buildplate_name = machine.getBuildplateName() # Iterate over all extruders to find quality containers for each extruder for position, extruder in machine.extruders.items(): nozzle_name = None if extruder.variant.getId() != "empty_variant": nozzle_name = extruder.variant.getName() # This is a list of root material IDs to use for searching for suitable quality profiles. # The root material IDs in this list are in prioritized order. root_material_id_list = [] has_material = False # flag indicating whether this extruder has a material assigned root_material_id = None if extruder.material.getId() != "empty_material": has_material = True root_material_id = extruder.material.getMetaDataEntry( "base_file") # Convert possible generic_pla_175 -> generic_pla root_material_id = self._material_manager.getRootMaterialIDWithoutDiameter( root_material_id) root_material_id_list.append(root_material_id) # Also try to get the fallback materials fallback_ids = self._material_manager.getFallBackMaterialIdsByMaterial( extruder.material) if fallback_ids: root_material_id_list.extend(fallback_ids) # Weed out duplicates while preserving the order. seen = set() # type: Set[str] root_material_id_list = [ x for x in root_material_id_list if x not in seen and not seen.add(x) ] # type: ignore # Here we construct a list of nodes we want to look for qualities with the highest priority first. # The use case is that, when we look for qualities for a machine, we first want to search in the following # order: # 1. machine-nozzle-buildplate-and-material-specific qualities if exist # 2. machine-nozzle-and-material-specific qualities if exist # 3. machine-nozzle-specific qualities if exist # 4. machine-material-specific qualities if exist # 5. machine-specific global qualities if exist, otherwise generic global qualities # NOTE: We DO NOT fail back to generic global qualities if machine-specific global qualities exist. # This is because when a machine defines its own global qualities such as Normal, Fine, etc., # it is intended to maintain those specific qualities ONLY. If we still fail back to the generic # global qualities, there can be unimplemented quality types e.g. "coarse", and this is not # correct. # Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into # the list with priorities as the order. Later, we just need to loop over each node in this list and fetch # qualities from there. node_info_list_0 = [ nozzle_name, buildplate_name, root_material_id ] # type: List[Optional[str]] nodes_to_check = [] # This function tries to recursively find the deepest (the most specific) branch and add those nodes to # the search list in the order described above. So, by iterating over that search node list, we first look # in the more specific branches and then the less specific (generic) ones. def addNodesToCheck(node: Optional[QualityNode], nodes_to_check_list: List[QualityNode], node_info_list, node_info_idx: int) -> None: if node is None: return if node_info_idx < len(node_info_list): node_name = node_info_list[node_info_idx] if node_name is not None: current_node = node.getChildNode(node_name) if current_node is not None and has_material: addNodesToCheck(current_node, nodes_to_check_list, node_info_list, node_info_idx + 1) if has_material: for rmid in root_material_id_list: material_node = node.getChildNode(rmid) if material_node: nodes_to_check_list.append(material_node) break nodes_to_check_list.append(node) addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0) # The last fall back will be the global qualities (either from the machine-specific node or the generic # node), but we only use one. For details see the overview comments above. if machine_node is not None and machine_node.quality_type_map: nodes_to_check += [machine_node] elif default_machine_node is not None: nodes_to_check += [default_machine_node] for node_idx, node in enumerate(nodes_to_check): if node and node.quality_type_map: if has_extruder_specific_qualities: # Only include variant qualities; skip non global qualities quality_node = list(node.quality_type_map.values())[0] is_global_quality = parseBool( quality_node.getMetaDataEntry( "global_quality", False)) if is_global_quality: continue for quality_type, quality_node in node.quality_type_map.items( ): if quality_type not in quality_group_dict: quality_group = QualityGroup( quality_node.getMetaDataEntry("name", ""), quality_type) quality_group_dict[quality_type] = quality_group quality_group = quality_group_dict[quality_type] if position not in quality_group.nodes_for_extruders: quality_group.setExtruderNode( position, quality_node) # If the machine has its own specific qualities, for extruders, it should skip the global qualities # and use the material/variant specific qualities. if has_extruder_specific_qualities: if node_idx == len(nodes_to_check) - 1: break # Update availabilities for each quality group self._updateQualityGroupsAvailability(machine, quality_group_dict.values()) return quality_group_dict
def importProfile(self, file_name): Logger.log("d", "Attempting to import profile %s", file_name) if not file_name: return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path") } plugin_registry = PluginRegistry.getInstance() extension = file_name.split(".")[-1] global_container_stack = Application.getInstance( ).getGlobalContainerStack() if not global_container_stack: return machine_extruders = list( ExtruderManager.getInstance().getMachineExtruders( global_container_stack.getId())) machine_extruders.sort(key=lambda k: k.getMetaDataEntry("position")) for plugin_id, meta_data in self._getIOPlugins("profile_reader"): if meta_data["profile_reader"][0]["extension"] != extension: continue profile_reader = plugin_registry.getPluginObject(plugin_id) try: profile_or_list = profile_reader.read( file_name) # Try to open the file with the profile reader. except NoProfileException: return { "status": "ok", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "No custom profile to import in file <filename>{0}</filename>", file_name) } except Exception as e: # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None. Logger.log( "e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e)) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "\n" + str(e)) } if profile_or_list: # Ensure it is always a list of profiles if not isinstance(profile_or_list, list): profile_or_list = [profile_or_list] # First check if this profile is suitable for this machine global_profile = None if len(profile_or_list) == 1: global_profile = profile_or_list[0] else: for profile in profile_or_list: if not profile.getMetaDataEntry("extruder"): global_profile = profile break if not global_profile: Logger.log( "e", "Incorrect profile [%s]. Could not find global profile", file_name) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name) } profile_definition = global_profile.getMetaDataEntry( "definition") expected_machine_definition = "fdmprinter" if parseBool( global_container_stack.getMetaDataEntry( "has_machine_quality", "False")): expected_machine_definition = global_container_stack.getMetaDataEntry( "quality_definition") if not expected_machine_definition: expected_machine_definition = global_container_stack.definition.getId( ) if expected_machine_definition is not None and profile_definition is not None and profile_definition != expected_machine_definition: Logger.log( "e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition) } name_seed = os.path.splitext(os.path.basename(file_name))[0] new_name = self.uniqueName(name_seed) # Ensure it is always a list of profiles if type(profile_or_list) is not list: profile_or_list = [profile_or_list] # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack if len(profile_or_list) == 1: global_profile = profile_or_list[0] extruder_profiles = [] for idx, extruder in enumerate( global_container_stack.extruders.values()): profile_id = ContainerRegistry.getInstance( ).uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1)) profile = InstanceContainer(profile_id) profile.setName(global_profile.getName()) profile.addMetaDataEntry( "setting_version", CuraApplication.SettingVersion) profile.addMetaDataEntry("type", "quality_changes") profile.addMetaDataEntry( "definition", global_profile.getMetaDataEntry("definition")) profile.addMetaDataEntry( "quality_type", global_profile.getMetaDataEntry("quality_type")) profile.addMetaDataEntry("position", "0") profile.setDirty(True) if idx == 0: # move all per-extruder settings to the first extruder's quality_changes for qc_setting_key in global_profile.getAllKeys(): settable_per_extruder = global_container_stack.getProperty( qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = global_profile.getProperty( qc_setting_key, "value") setting_definition = global_container_stack.getSettingDefinition( qc_setting_key) new_instance = SettingInstance( setting_definition, profile) new_instance.setProperty( "value", setting_value) new_instance.resetState( ) # Ensure that the state is not seen as a user state. profile.addInstance(new_instance) profile.setDirty(True) global_profile.removeInstance( qc_setting_key, postpone_emit=True) extruder_profiles.append(profile) for profile in extruder_profiles: profile_or_list.append(profile) # Import all profiles for profile_index, profile in enumerate(profile_or_list): if profile_index == 0: # This is assumed to be the global profile profile_id = ( global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") elif profile_index < len(machine_extruders) + 1: # This is assumed to be an extruder profile extruder_id = machine_extruders[profile_index - 1].definition.getId() extuder_position = str(profile_index - 1) if not profile.getMetaDataEntry("position"): profile.addMetaDataEntry("position", extuder_position) else: profile.setMetaDataEntry("position", extuder_position) profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_") else: #More extruders in the imported file than in the machine. continue #Delete the additional profiles. result = self._configureProfile(profile, profile_id, new_name) if result is not None: return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, result) } return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName()) } # This message is throw when the profile reader doesn't find any profile in the file return { "status": "error", "message": catalog.i18nc("@info:status", "File {0} does not contain any valid profile.", file_name) } # If it hasn't returned by now, none of the plugins loaded the profile successfully. return { "status": "error", "message": catalog.i18nc( "@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name) }
def deserialize(self, serialized): data = ET.fromstring(serialized) # Reset previous metadata self.clearData() # Ensure any previous data is gone. self.addMetaDataEntry("type", "material") self.addMetaDataEntry("base_file", self.id) # TODO: Add material verfication self.addMetaDataEntry("status", "unknown") 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.setName(label.text) else: self.setName(self._profile_name(material.text, color.text)) self.addMetaDataEntry("brand", brand.text) self.addMetaDataEntry("material", material.text) self.addMetaDataEntry("color_name", color.text) continue self.addMetaDataEntry(tag_name, entry.text) if not "description" in self.getMetaData(): self.addMetaDataEntry("description", "") if not "adhesion_info" in self.getMetaData(): self.addMetaDataEntry("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 self.addMetaDataEntry("properties", property_values) self.setDefinition(UM.Settings.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: self.setProperty(self.__material_property_setting_map[key], "value", entry.text) 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.addMetaDataEntry("compatible", global_compatibility) 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) 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 = UM.Settings.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 # It could be that we are overwriting, so check if the ID already exists. materials = UM.Settings.ContainerRegistry.getInstance( ).findInstanceContainers(id=new_material_id) if materials: new_material = materials[0] new_material.clearData() else: new_material = XmlMaterialProfile(new_material_id) new_material.setName(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 for key, value in global_setting_values.items(): new_material.setProperty(key, "value", value) for key, value in machine_setting_values.items(): new_material.setProperty(key, "value", value) new_material._dirty = False if not materials: UM.Settings.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 = UM.Settings.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 = UM.Settings.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) # It could be that we are overwriting, so check if the ID already exists. new_hotend_id = self.id + "_" + machine_id + "_" + hotend_id.replace( " ", "_") materials = UM.Settings.ContainerRegistry.getInstance( ).findInstanceContainers(id=new_hotend_id) if materials: new_hotend_material = materials[0] new_hotend_material.clearData() else: new_hotend_material = XmlMaterialProfile(new_hotend_id) new_hotend_material.setName(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 for key, value in global_setting_values.items(): new_hotend_material.setProperty(key, "value", value) for key, value in machine_setting_values.items(): new_hotend_material.setProperty(key, "value", value) for key, value in hotend_setting_values.items(): new_hotend_material.setProperty(key, "value", value) new_hotend_material._dirty = False if not materials: # It was not added yet, do so now. UM.Settings.ContainerRegistry.getInstance( ).addContainer(new_hotend_material)
def deserialize(self, serialized): data = ET.fromstring(serialized) self.addMetaDataEntry("type", "material") self.addMetaDataEntry("base_file", self.id) # TODO: Add material verfication self.addMetaDataEntry("status", "unknown") 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.setName(label.text) else: self.setName(self._profile_name(material.text, color.text)) self.addMetaDataEntry("brand", brand.text) self.addMetaDataEntry("material", material.text) self.addMetaDataEntry("color_name", color.text) continue self.addMetaDataEntry(tag_name, entry.text) if not "description" in self.getMetaData(): self.addMetaDataEntry("description", "") if not "adhesion_info" in self.getMetaData(): self.addMetaDataEntry("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 self.addMetaDataEntry("properties", property_values) self.setDefinition(UM.Settings.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: self.setProperty(self.__material_property_setting_map[key], "value", entry.text, self._definition) 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.addMetaDataEntry("compatible", global_compatibility) 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) 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 = UM.Settings.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 = XmlMaterialProfile(self.id + "_" + machine_id) new_material.setName(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 for key, value in global_setting_values.items(): new_material.setProperty(key, "value", value, definition) for key, value in machine_setting_values.items(): new_material.setProperty(key, "value", value, definition) new_material._dirty = False UM.Settings.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 = UM.Settings.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 = UM.Settings.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_material = XmlMaterialProfile(self.id + "_" + machine_id + "_" + hotend_id.replace(" ", "_")) new_hotend_material.setName(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 for key, value in global_setting_values.items(): new_hotend_material.setProperty(key, "value", value, definition) for key, value in machine_setting_values.items(): new_hotend_material.setProperty(key, "value", value, definition) for key, value in hotend_setting_values.items(): new_hotend_material.setProperty(key, "value", value, definition) new_hotend_material._dirty = False UM.Settings.ContainerRegistry.getInstance().addContainer(new_hotend_material)
def isEnabled(self): return parseBool(self.getMetaDataEntry("enabled", "True"))
def setExtruderNode(self, position: int, node: "ContainerNode") -> None: self.nodes_for_extruders[position] = node # Update is_experimental flag is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False)) self.is_experimental |= is_experimental
def startPrint(self): global_container_stack = Application.getInstance( ).getGlobalContainerStack() if not global_container_stack: return self._auto_print = parseBool( global_container_stack.getMetaDataEntry("octoprint_auto_print", True)) if self._auto_print: Application.getInstance().showPrintMonitor.emit(True) if self.jobState != "ready" and self.jobState != "": self._error_message = Message( i18n_catalog.i18nc( "@info:status", "OctoPrint is printing. Unable to start a new job.")) self._error_message.show() return try: self._progress_message = Message( i18n_catalog.i18nc("@info:status", "Sending data to OctoPrint"), 0, False, -1) self._progress_message.show() ## Mash the data into single string single_string_file_data = "" last_process_events = time() for line in self._gcode: single_string_file_data += line if time() > last_process_events + 0.05: # Ensure that the GUI keeps updated at least 20 times per second. QCoreApplication.processEvents() last_process_events = time() file_name = "%s.gcode" % Application.getInstance( ).getPrintInformation().jobName ## Create multi_part request self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) ## Create parts (to be placed inside multipart) self._post_part = QHttpPart() self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"select\"") self._post_part.setBody(b"true") self._post_multi_part.append(self._post_part) if self._auto_print: self._post_part = QHttpPart() self._post_part.setHeader( QNetworkRequest.ContentDispositionHeader, "form-data; name=\"print\"") self._post_part.setBody(b"true") self._post_multi_part.append(self._post_part) self._post_part = QHttpPart() self._post_part.setHeader( QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name) self._post_part.setBody(single_string_file_data.encode()) self._post_multi_part.append(self._post_part) destination = "local" if parseBool( global_container_stack.getMetaDataEntry( "octoprint_store_sd", False)): destination = "sdcard" url = QUrl(self._api_url + "files/" + destination) ## Create the QT request self._post_request = QNetworkRequest(url) self._post_request.setRawHeader(self._api_header.encode(), self._api_key.encode()) ## Post request + data self._post_reply = self._manager.post(self._post_request, self._post_multi_part) self._post_reply.uploadProgress.connect(self._onUploadProgress) self._gcode = None except IOError: self._progress_message.hide() self._error_message = Message( i18n_catalog.i18nc("@info:status", "Unable to send data to OctoPrint.")) self._error_message.show() except Exception as e: self._progress_message.hide() Logger.log( "e", "An exception occurred in network connection: %s" % str(e))