|
@@ -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)
|
|
|
-
|
|
|
- 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.
|
|
|
-
|
|
|
- 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)
|
|
|
+ user_settings = {}
|
|
|
|
|
|
- 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._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)
|
|
|
|
|
|
- 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.
|
|
|
|
|
|
+ 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)
|
|
|
+ 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:
|
|
|
- # Handle quality changes if any
|
|
|
- self._processQualityChanges(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)
|
|
|
+ # 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:
|