mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-05-08 06:29:01 +08:00

* 'master' of github.com:ultimaker/Cura: (38 commits) Fixed profile file case-sensitivity. Fix UMO Checkup button size Remove debug statement and commented-out code CURA-1385 Show "ready" state when a printer is connected but jobstate is not yet set Added deepcopy function Made exception handling of slice info plugin way more robust Restart timer after slicing is performed when not enabled. Update GUID for PLA to match the GUID in the official repository Set default extruder index to -1 (so global is default) Ensure that the display matches with the backend active extruder data Update UM2 Extended build volume height to value published in marketing materials Fixed firmware upgrade for um2+ Capitalise setting label CHeckup action now correctly resets every time you start it Remove unused name/id when importing a profile from a gcode file Just a little typo BQ Hephestos2: Heat up nozzle while leveling Saving g-code no longer crashes Removed update firmware from extensions; This is now handled by machine actions Changing active extruder no longer trigger re-slice ...
210 lines
11 KiB
Python
210 lines
11 KiB
Python
# Copyright (c) 2016 Ultimaker B.V.
|
|
# Cura is released under the terms of the AGPLv3 or higher.
|
|
|
|
import os
|
|
import os.path
|
|
import re
|
|
from PyQt5.QtWidgets import QMessageBox
|
|
|
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
|
from UM.Settings.ContainerStack import ContainerStack
|
|
from UM.Settings.InstanceContainer import InstanceContainer
|
|
from UM.Application import Application
|
|
from UM.Logger import Logger
|
|
from UM.Message import Message
|
|
from UM.Platform import Platform
|
|
from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with.
|
|
from UM.Util import parseBool
|
|
|
|
from UM.i18n import i18nCatalog
|
|
catalog = i18nCatalog("cura")
|
|
|
|
class CuraContainerRegistry(ContainerRegistry):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
## Create a name that is not empty and unique
|
|
# \param container_type \type{string} Type of the container (machine, quality, ...)
|
|
# \param current_name \type{} Current name of the container, which may be an acceptable option
|
|
# \param new_name \type{string} Base name, which may not be unique
|
|
# \param fallback_name \type{string} Name to use when (stripped) new_name is empty
|
|
# \return \type{string} Name that is unique for the specified type and name/id
|
|
def createUniqueName(self, container_type, current_name, new_name, fallback_name):
|
|
new_name = new_name.strip()
|
|
num_check = re.compile("(.*?)\s*#\d+$").match(new_name)
|
|
if num_check:
|
|
new_name = num_check.group(1)
|
|
if new_name == "":
|
|
new_name = fallback_name
|
|
|
|
unique_name = new_name
|
|
i = 1
|
|
# In case we are renaming, the current name of the container is also a valid end-result
|
|
while self._containerExists(container_type, unique_name) and unique_name != current_name:
|
|
i += 1
|
|
unique_name = "%s #%d" % (new_name, i)
|
|
|
|
return unique_name
|
|
|
|
## Check if a container with of a certain type and a certain name or id exists
|
|
# Both the id and the name are checked, because they may not be the same and it is better if they are both unique
|
|
# \param container_type \type{string} Type of the container (machine, quality, ...)
|
|
# \param container_name \type{string} Name to check
|
|
def _containerExists(self, container_type, container_name):
|
|
container_class = ContainerStack if container_type == "machine" else InstanceContainer
|
|
|
|
return self.findContainers(container_class, id = container_name, type = container_type, ignore_case = True) or \
|
|
self.findContainers(container_class, name = container_name, type = container_type)
|
|
|
|
## Exports an profile to a file
|
|
#
|
|
# \param instance_id \type{str} the ID of the profile to export.
|
|
# \param file_name \type{str} the full path and filename to export to.
|
|
# \param file_type \type{str} the file type with the format "<description> (*.<extension>)"
|
|
def exportProfile(self, instance_id, file_name, file_type):
|
|
Logger.log('d', 'exportProfile instance_id: '+str(instance_id))
|
|
|
|
# Parse the fileType to deduce what plugin can save the file format.
|
|
# fileType has the format "<description> (*.<extension>)"
|
|
split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
|
|
if split < 0: # Not found. Invalid format.
|
|
Logger.log("e", "Invalid file format identifier %s", file_type)
|
|
return
|
|
description = file_type[:split]
|
|
extension = file_type[split + 4:-1] # Leave out the " (*." and ")".
|
|
if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any.
|
|
file_name += "." + extension
|
|
|
|
# On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself.
|
|
if not Platform.isWindows():
|
|
if os.path.exists(file_name):
|
|
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
|
catalog.i18nc("@label", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
|
|
if result == QMessageBox.No:
|
|
return
|
|
|
|
containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id)
|
|
if not containers:
|
|
return
|
|
container = containers[0]
|
|
|
|
profile_writer = self._findProfileWriter(extension, description)
|
|
|
|
try:
|
|
success = profile_writer.write(file_name, container)
|
|
except Exception as e:
|
|
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
|
|
m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), lifetime = 0)
|
|
m.show()
|
|
return
|
|
if not success:
|
|
Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name)
|
|
m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name), lifetime = 0)
|
|
m.show()
|
|
return
|
|
m = Message(catalog.i18nc("@info:status", "Exported profile to <filename>{0}</filename>", file_name))
|
|
m.show()
|
|
|
|
## Gets the plugin object matching the criteria
|
|
# \param extension
|
|
# \param description
|
|
# \return The plugin object matching the given extension and description.
|
|
def _findProfileWriter(self, extension, description):
|
|
plugin_registry = PluginRegistry.getInstance()
|
|
for plugin_id, meta_data in self._getIOPlugins("profile_writer"):
|
|
for supported_type in meta_data["profile_writer"]: # All file types this plugin can supposedly write.
|
|
supported_extension = supported_type.get("extension", None)
|
|
if supported_extension == extension: # This plugin supports a file type with the same extension.
|
|
supported_description = supported_type.get("description", None)
|
|
if supported_description == description: # The description is also identical. Assume it's the same file type.
|
|
return plugin_registry.getPluginObject(plugin_id)
|
|
return None
|
|
|
|
## Imports a profile from a file
|
|
#
|
|
# \param file_name \type{str} the full path and filename of the profile to import
|
|
# \return \type{Dict} dict with a 'status' key containing the string 'ok' or 'error', and a 'message' key
|
|
# containing a message for the user
|
|
def importProfile(self, file_name):
|
|
if not file_name:
|
|
return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")}
|
|
|
|
plugin_registry = PluginRegistry.getInstance()
|
|
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
|
|
profile_reader = plugin_registry.getPluginObject(plugin_id)
|
|
try:
|
|
profile = 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", file_name, str(e))
|
|
return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, str(e))}
|
|
if profile: #Success!
|
|
profile.setReadOnly(False)
|
|
|
|
new_name = self.createUniqueName("quality", "", os.path.splitext(os.path.basename(file_name))[0],
|
|
catalog.i18nc("@label", "Custom profile"))
|
|
profile.setName(new_name)
|
|
profile._id = new_name
|
|
|
|
if self._machineHasOwnQualities():
|
|
profile.setDefinition(self._activeDefinition())
|
|
if self._machineHasOwnMaterials():
|
|
profile.addMetaDataEntry("material", self._activeMaterialId())
|
|
else:
|
|
profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
|
|
ContainerRegistry.getInstance().addContainer(profile)
|
|
|
|
return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) }
|
|
|
|
#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.", file_name)}
|
|
|
|
## Gets a list of profile writer plugins
|
|
# \return List of tuples of (plugin_id, meta_data).
|
|
def _getIOPlugins(self, io_type):
|
|
plugin_registry = PluginRegistry.getInstance()
|
|
active_plugin_ids = plugin_registry.getActivePlugins()
|
|
|
|
result = []
|
|
for plugin_id in active_plugin_ids:
|
|
meta_data = plugin_registry.getMetaData(plugin_id)
|
|
if io_type in meta_data:
|
|
result.append( (plugin_id, meta_data) )
|
|
return result
|
|
|
|
## Gets the active definition
|
|
# \return the active definition object or None if there is no definition
|
|
def _activeDefinition(self):
|
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
if global_container_stack:
|
|
definition = global_container_stack.getBottom()
|
|
if definition:
|
|
return definition
|
|
return None
|
|
|
|
## Returns true if the current machine requires its own materials
|
|
# \return True if the current machine requires its own materials
|
|
def _machineHasOwnMaterials(self):
|
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
if global_container_stack:
|
|
return global_container_stack.getMetaDataEntry("has_materials", False)
|
|
return False
|
|
|
|
## Gets the ID of the active material
|
|
# \return the ID of the active material or the empty string
|
|
def _activeMaterialId(self):
|
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
if global_container_stack:
|
|
material = global_container_stack.findContainer({"type": "material"})
|
|
if material:
|
|
return material.getId()
|
|
return ""
|
|
|
|
## Returns true if the current machien requires its own quality profiles
|
|
# \return true if the current machien requires its own quality profiles
|
|
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
|