mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
Now loading user settings
CURA-11561
This commit is contained in:
parent
733ef4d3d8
commit
ab0a52063d
4 changed files with 178 additions and 90 deletions
|
@ -5,6 +5,7 @@ from configparser import ConfigParser
|
|||
import zipfile
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
from typing import cast, Dict, List, Optional, Tuple, Any, Set
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
@ -141,10 +142,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._old_new_materials: Dict[str, str] = {}
|
||||
self._machine_info = None
|
||||
|
||||
self._load_profile = False
|
||||
|
||||
def _clearState(self):
|
||||
self._id_mapping = {}
|
||||
self._old_new_materials = {}
|
||||
self._machine_info = None
|
||||
self._load_profile = False
|
||||
|
||||
def getNewId(self, old_id: str):
|
||||
"""Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
|
||||
|
@ -228,7 +232,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._resolve_strategies = {k: None for k in resolve_strategy_keys}
|
||||
containers_found_dict = {k: False for k in resolve_strategy_keys}
|
||||
|
||||
# Check whether the file is a PCB
|
||||
# Check whether the file is a PCB, which changes some import options
|
||||
is_pcb = file_name.endswith('.pcb')
|
||||
|
||||
#
|
||||
|
@ -621,6 +625,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
|
||||
self._dialog.setMissingPackagesMetadata(missing_package_metadata)
|
||||
self._dialog.setHasVisibleSelectSameProfileChanged(is_pcb)
|
||||
self._dialog.setAllowCreatemachine(not is_pcb)
|
||||
self._dialog.show()
|
||||
|
||||
# Choosing the initially selected printer in MachineSelector
|
||||
|
@ -652,6 +657,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._dialog.setIsNetworkedMachine(is_networked_machine)
|
||||
self._dialog.setIsAbstractMachine(is_abstract_machine)
|
||||
self._dialog.setMachineName(machine_name)
|
||||
self._dialog.updateCompatibleMachine()
|
||||
self._dialog.setSelectSameProfileChecked(self._dialog.isCompatibleMachine)
|
||||
|
||||
# Block until the dialog is closed.
|
||||
self._dialog.waitForClose()
|
||||
|
@ -659,6 +666,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
if self._dialog.getResult() == {}:
|
||||
return WorkspaceReader.PreReadResult.cancelled
|
||||
|
||||
self._load_profile = not is_pcb or self._dialog.selectSameProfileChecked
|
||||
|
||||
self._resolve_strategies = self._dialog.getResult()
|
||||
#
|
||||
# There can be 3 resolve strategies coming from the dialog:
|
||||
|
@ -694,16 +703,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
except EnvironmentError as e:
|
||||
message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tags <filename> or <message>!",
|
||||
"Project file <filename>{0}</filename> is suddenly inaccessible: <message>{1}</message>.", file_name, str(e)),
|
||||
title = i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
|
||||
message_type = Message.MessageType.ERROR)
|
||||
title = i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
|
||||
message_type = Message.MessageType.ERROR)
|
||||
message.show()
|
||||
self.setWorkspaceName("")
|
||||
return [], {}
|
||||
except zipfile.BadZipFile as e:
|
||||
message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tags <filename> or <message>!",
|
||||
"Project file <filename>{0}</filename> is corrupt: <message>{1}</message>.", file_name, str(e)),
|
||||
title = i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
|
||||
message_type = Message.MessageType.ERROR)
|
||||
title = i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
|
||||
message_type = Message.MessageType.ERROR)
|
||||
message.show()
|
||||
self.setWorkspaceName("")
|
||||
return [], {}
|
||||
|
@ -765,9 +774,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# Find the machine which will be overridden
|
||||
global_stacks = self._container_registry.findContainerStacks(id = self._dialog.getMachineToOverride(), type = "machine")
|
||||
if not global_stacks:
|
||||
message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tag <filename>!",
|
||||
message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tag <filename>!",
|
||||
"Project file <filename>{0}</filename> is made using profiles that are unknown to this version of UltiMaker Cura.", file_name),
|
||||
message_type = Message.MessageType.ERROR)
|
||||
message_type = Message.MessageType.ERROR)
|
||||
message.show()
|
||||
self.setWorkspaceName("")
|
||||
return [], {}
|
||||
|
@ -781,84 +790,107 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
for stack in extruder_stacks:
|
||||
stack.setNextStack(global_stack, connect_signals = False)
|
||||
|
||||
Logger.log("d", "Workspace loading is checking definitions...")
|
||||
# Get all the definition files & check if they exist. If not, add them.
|
||||
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
||||
for definition_container_file in definition_container_files:
|
||||
container_id = self._stripFileToId(definition_container_file)
|
||||
user_settings = {}
|
||||
|
||||
definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
|
||||
if not definitions:
|
||||
definition_container = DefinitionContainer(container_id)
|
||||
try:
|
||||
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
|
||||
file_name = definition_container_file)
|
||||
except ContainerFormatError:
|
||||
# We cannot just skip the definition file because everything else later will just break if the
|
||||
# machine definition cannot be found.
|
||||
Logger.logException("e", "Failed to deserialize definition file %s in project file %s",
|
||||
definition_container_file, file_name)
|
||||
definition_container = self._container_registry.findDefinitionContainers(id = "fdmprinter")[0] #Fall back to defaults.
|
||||
self._container_registry.addContainer(definition_container)
|
||||
Job.yieldThread()
|
||||
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
|
||||
if self._load_profile:
|
||||
Logger.log("d", "Workspace loading is checking definitions...")
|
||||
# Get all the definition files & check if they exist. If not, add them.
|
||||
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
||||
for definition_container_file in definition_container_files:
|
||||
container_id = self._stripFileToId(definition_container_file)
|
||||
|
||||
Logger.log("d", "Workspace loading is checking materials...")
|
||||
# Get all the material files and check if they exist. If not, add them.
|
||||
xml_material_profile = self._getXmlProfileClass()
|
||||
if self._material_container_suffix is None:
|
||||
self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
|
||||
if xml_material_profile:
|
||||
material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
|
||||
for material_container_file in material_container_files:
|
||||
to_deserialize_material = False
|
||||
container_id = self._stripFileToId(material_container_file)
|
||||
need_new_name = False
|
||||
materials = self._container_registry.findInstanceContainers(id = container_id)
|
||||
|
||||
if not materials:
|
||||
# No material found, deserialize this material later and add it
|
||||
to_deserialize_material = True
|
||||
else:
|
||||
material_container = materials[0]
|
||||
old_material_root_id = material_container.getMetaDataEntry("base_file")
|
||||
if old_material_root_id is not None and not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only.
|
||||
to_deserialize_material = True
|
||||
|
||||
if self._resolve_strategies["material"] == "override":
|
||||
# Remove the old materials and then deserialize the one from the project
|
||||
root_material_id = material_container.getMetaDataEntry("base_file")
|
||||
application.getContainerRegistry().removeContainer(root_material_id)
|
||||
elif self._resolve_strategies["material"] == "new":
|
||||
# Note that we *must* deserialize it with a new ID, as multiple containers will be
|
||||
# auto created & added.
|
||||
container_id = self.getNewId(container_id)
|
||||
self._old_new_materials[old_material_root_id] = container_id
|
||||
need_new_name = True
|
||||
|
||||
if to_deserialize_material:
|
||||
material_container = xml_material_profile(container_id)
|
||||
definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
|
||||
if not definitions:
|
||||
definition_container = DefinitionContainer(container_id)
|
||||
try:
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
|
||||
file_name = container_id + "." + self._material_container_suffix)
|
||||
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
|
||||
file_name = definition_container_file)
|
||||
except ContainerFormatError:
|
||||
Logger.logException("e", "Failed to deserialize material file %s in project file %s",
|
||||
material_container_file, file_name)
|
||||
continue
|
||||
if need_new_name:
|
||||
new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName())
|
||||
material_container.setName(new_name)
|
||||
material_container.setDirty(True)
|
||||
self._container_registry.addContainer(material_container)
|
||||
# We cannot just skip the definition file because everything else later will just break if the
|
||||
# machine definition cannot be found.
|
||||
Logger.logException("e", "Failed to deserialize definition file %s in project file %s",
|
||||
definition_container_file, file_name)
|
||||
definition_container = self._container_registry.findDefinitionContainers(id = "fdmprinter")[0] #Fall back to defaults.
|
||||
self._container_registry.addContainer(definition_container)
|
||||
Job.yieldThread()
|
||||
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
|
||||
|
||||
if global_stack:
|
||||
# Handle quality changes if any
|
||||
self._processQualityChanges(global_stack)
|
||||
Logger.log("d", "Workspace loading is checking materials...")
|
||||
# Get all the material files and check if they exist. If not, add them.
|
||||
xml_material_profile = self._getXmlProfileClass()
|
||||
if self._material_container_suffix is None:
|
||||
self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
|
||||
if xml_material_profile:
|
||||
material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
|
||||
for material_container_file in material_container_files:
|
||||
to_deserialize_material = False
|
||||
container_id = self._stripFileToId(material_container_file)
|
||||
need_new_name = False
|
||||
materials = self._container_registry.findInstanceContainers(id = container_id)
|
||||
|
||||
# Prepare the machine
|
||||
self._applyChangesToMachine(global_stack, extruder_stack_dict)
|
||||
if not materials:
|
||||
# No material found, deserialize this material later and add it
|
||||
to_deserialize_material = True
|
||||
else:
|
||||
material_container = materials[0]
|
||||
old_material_root_id = material_container.getMetaDataEntry("base_file")
|
||||
if old_material_root_id is not None and not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only.
|
||||
to_deserialize_material = True
|
||||
|
||||
if self._resolve_strategies["material"] == "override":
|
||||
# Remove the old materials and then deserialize the one from the project
|
||||
root_material_id = material_container.getMetaDataEntry("base_file")
|
||||
application.getContainerRegistry().removeContainer(root_material_id)
|
||||
elif self._resolve_strategies["material"] == "new":
|
||||
# Note that we *must* deserialize it with a new ID, as multiple containers will be
|
||||
# auto created & added.
|
||||
container_id = self.getNewId(container_id)
|
||||
self._old_new_materials[old_material_root_id] = container_id
|
||||
need_new_name = True
|
||||
|
||||
if to_deserialize_material:
|
||||
material_container = xml_material_profile(container_id)
|
||||
try:
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
|
||||
file_name = container_id + "." + self._material_container_suffix)
|
||||
except ContainerFormatError:
|
||||
Logger.logException("e", "Failed to deserialize material file %s in project file %s",
|
||||
material_container_file, file_name)
|
||||
continue
|
||||
if need_new_name:
|
||||
new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName())
|
||||
material_container.setName(new_name)
|
||||
material_container.setDirty(True)
|
||||
self._container_registry.addContainer(material_container)
|
||||
Job.yieldThread()
|
||||
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
|
||||
else:
|
||||
Logger.log("d", "Workspace loading user settings...")
|
||||
try:
|
||||
user_settings = json.loads(archive.open("Cura/user-settings.json").read().decode("utf-8"))
|
||||
except KeyError as e:
|
||||
# If there is no user settings file, it's not a PCB, so notify user of failure.
|
||||
Logger.log("w", "File %s is not a valid PCB.", file_name)
|
||||
message = Message(
|
||||
i18n_catalog.i18nc("@info:error Don't translate the XML tags <filename> or <message>!",
|
||||
"Project file <filename>{0}</filename> is corrupt: <message>{1}</message>.",
|
||||
file_name, str(e)),
|
||||
title=i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
|
||||
message_type=Message.MessageType.ERROR)
|
||||
message.show()
|
||||
self.setWorkspaceName("")
|
||||
return [], {}
|
||||
|
||||
|
||||
if global_stack:
|
||||
if self._load_profile:
|
||||
# Handle quality changes if any
|
||||
self._processQualityChanges(global_stack)
|
||||
|
||||
# Prepare the machine
|
||||
self._applyChangesToMachine(global_stack, extruder_stack_dict)
|
||||
else:
|
||||
self._applyUserSettings(global_stack, extruder_stack_dict, user_settings)
|
||||
|
||||
Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
|
||||
# Actually change the active machine.
|
||||
|
@ -1181,21 +1213,47 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
material_node = machine_node.variants[extruder_stack.variant.getName()].materials[root_material_id]
|
||||
extruder_stack.material = material_node.container
|
||||
|
||||
def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
|
||||
# Clear all first
|
||||
def _clearMachineSettings(self, global_stack, extruder_stack_dict):
|
||||
self._clearStack(global_stack)
|
||||
for extruder_stack in extruder_stack_dict.values():
|
||||
self._clearStack(extruder_stack)
|
||||
|
||||
self._quality_changes_to_apply = None
|
||||
self._quality_type_to_apply = None
|
||||
self._intent_category_to_apply = None
|
||||
self._user_settings_to_apply = None
|
||||
|
||||
def _applyUserSettings(self, global_stack, extruder_stack_dict, user_settings):
|
||||
# Clear all first
|
||||
self._clearMachineSettings(global_stack, extruder_stack_dict)
|
||||
|
||||
for stack_name, settings in user_settings.items():
|
||||
if stack_name == 'global':
|
||||
ThreeMFWorkspaceReader._applyUserSettingsOnStack(global_stack, settings)
|
||||
else:
|
||||
extruder_match = re.fullmatch('extruder_([0-9]+)', stack_name)
|
||||
if extruder_match is not None:
|
||||
extruder_nr = extruder_match.group(1)
|
||||
if extruder_nr in extruder_stack_dict:
|
||||
ThreeMFWorkspaceReader._applyUserSettingsOnStack(extruder_stack_dict[extruder_nr], settings)
|
||||
|
||||
@staticmethod
|
||||
def _applyUserSettingsOnStack(stack, user_settings):
|
||||
user_settings_container = stack.userChanges
|
||||
|
||||
for setting_to_import, setting_value in user_settings.items():
|
||||
user_settings_container.setProperty(setting_to_import, 'value', setting_value)
|
||||
|
||||
def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
|
||||
# Clear all first
|
||||
self._clearMachineSettings(global_stack, extruder_stack_dict)
|
||||
|
||||
self._applyDefinitionChanges(global_stack, extruder_stack_dict)
|
||||
self._applyUserChanges(global_stack, extruder_stack_dict)
|
||||
self._applyVariants(global_stack, extruder_stack_dict)
|
||||
self._applyMaterials(global_stack, extruder_stack_dict)
|
||||
|
||||
# prepare the quality to select
|
||||
self._quality_changes_to_apply = None
|
||||
self._quality_type_to_apply = None
|
||||
self._intent_category_to_apply = None
|
||||
if self._machine_info.quality_changes_info is not None:
|
||||
self._quality_changes_to_apply = self._machine_info.quality_changes_info.name
|
||||
else:
|
||||
|
|
|
@ -73,6 +73,8 @@ class WorkspaceDialog(QObject):
|
|||
self._is_networked_machine = False
|
||||
self._is_compatible_machine = False
|
||||
self._has_visible_select_same_profile = False
|
||||
self._select_same_profile_checked = True
|
||||
self._allow_create_machine = True
|
||||
|
||||
machineConflictChanged = pyqtSignal()
|
||||
qualityChangesConflictChanged = pyqtSignal()
|
||||
|
@ -98,6 +100,7 @@ class WorkspaceDialog(QObject):
|
|||
missingPackagesChanged = pyqtSignal()
|
||||
isCompatibleMachineChanged = pyqtSignal()
|
||||
hasVisibleSelectSameProfileChanged = pyqtSignal()
|
||||
selectSameProfileCheckedChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify = isPrinterGroupChanged)
|
||||
def isPrinterGroup(self) -> bool:
|
||||
|
@ -295,17 +298,19 @@ class WorkspaceDialog(QObject):
|
|||
|
||||
@pyqtSlot(str)
|
||||
def setMachineToOverride(self, machine_name: str) -> None:
|
||||
self._override_machine = machine_name
|
||||
self.updateCompatibleMachine()
|
||||
|
||||
def updateCompatibleMachine(self):
|
||||
registry = ContainerRegistry.getInstance()
|
||||
containers_expected = registry.findDefinitionContainers(name = self._machine_type)
|
||||
containers_selected = registry.findContainerStacks(id = machine_name)
|
||||
containers_expected = registry.findDefinitionContainers(name=self._machine_type)
|
||||
containers_selected = registry.findContainerStacks(id=self._override_machine)
|
||||
if len(containers_expected) == 1 and len(containers_selected) == 1:
|
||||
new_compatible_machine = (containers_expected[0] == containers_selected[0].definition)
|
||||
if new_compatible_machine != self._is_compatible_machine:
|
||||
self._is_compatible_machine = new_compatible_machine
|
||||
self.isCompatibleMachineChanged.emit()
|
||||
|
||||
self._override_machine = machine_name
|
||||
|
||||
@pyqtProperty(bool, notify = isCompatibleMachineChanged)
|
||||
def isCompatibleMachine(self) -> bool:
|
||||
return self._is_compatible_machine
|
||||
|
@ -319,6 +324,22 @@ class WorkspaceDialog(QObject):
|
|||
def hasVisibleSelectSameProfile(self):
|
||||
return self._has_visible_select_same_profile
|
||||
|
||||
def setSelectSameProfileChecked(self, select_same_profile_checked):
|
||||
if select_same_profile_checked != self._select_same_profile_checked:
|
||||
self._select_same_profile_checked = select_same_profile_checked
|
||||
self.selectSameProfileCheckedChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify = selectSameProfileCheckedChanged, fset = setSelectSameProfileChecked)
|
||||
def selectSameProfileChecked(self):
|
||||
return self._select_same_profile_checked
|
||||
|
||||
def setAllowCreatemachine(self, allow_create_machine):
|
||||
self._allow_create_machine = allow_create_machine
|
||||
|
||||
@pyqtProperty(bool, constant = True)
|
||||
def allowCreateMachine(self):
|
||||
return self._allow_create_machine
|
||||
|
||||
@pyqtSlot()
|
||||
def closeBackend(self) -> None:
|
||||
"""Close the backend: otherwise one could end up with "Slicing..."""
|
||||
|
|
|
@ -120,13 +120,17 @@ UM.Dialog
|
|||
|
||||
minDropDownWidth: machineSelector.width
|
||||
|
||||
buttons: [
|
||||
Component
|
||||
{
|
||||
id: componentNewPrinter
|
||||
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
id: createNewPrinter
|
||||
text: catalog.i18nc("@button", "Create new")
|
||||
fixedWidthMode: true
|
||||
width: parent.width - leftPadding * 1.5
|
||||
visible: manager.allowCreateMachine
|
||||
onClicked:
|
||||
{
|
||||
toggleContent()
|
||||
|
@ -136,7 +140,9 @@ UM.Dialog
|
|||
manager.setIsNetworkedMachine(false)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
buttons: manager.allowCreateMachine ? [componentNewPrinter.createObject()] : []
|
||||
|
||||
onSelectPrinter: function(machine)
|
||||
{
|
||||
|
@ -191,9 +197,12 @@ UM.Dialog
|
|||
{
|
||||
text: catalog.i18nc("@action:checkbox", "Select the same profile")
|
||||
enabled: manager.isCompatibleMachine
|
||||
onEnabledChanged: checked = enabled
|
||||
onEnabledChanged: manager.selectSameProfileChecked = enabled
|
||||
tooltip: enabled ? "" : catalog.i18nc("@tooltip", "You can use the same profile only if you have the same printer as the project was published with")
|
||||
visible: manager.hasVisibleSelectSameProfile
|
||||
|
||||
checked: manager.selectSameProfileChecked
|
||||
onCheckedChanged: manager.selectSameProfileChecked = checked
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ class SettingsExportModel(QObject):
|
|||
|
||||
@staticmethod
|
||||
def _exportSettings(settings_stack):
|
||||
user_settings_container = settings_stack.getTop()
|
||||
user_settings_container = settings_stack.userChanges
|
||||
user_keys = user_settings_container.getAllKeys()
|
||||
|
||||
settings_export = []
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue