def test_roundtrip_instance(tmpdir, process_count, loaded_container_registry): instance_container = InstanceContainer("test_container") instance_container.setName("Test Instance Container") instance_container.setDefinition("inherits") instance_container.setMetaDataEntry("test", "test") instance_container.setProperty("test_setting_1", "value", 20) temp_file = tmpdir.join("instance_container_test") mp_run(process_count, write_data, temp_file, instance_container) assert len(list(tmpdir.listdir())) == 1 results = mp_run(process_count, read_data, temp_file) for result in results: deserialized_container = InstanceContainer("test_container") deserialized_container.setDefinition("inherits") with unittest.mock.patch( "UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", unittest.mock.MagicMock( return_value=loaded_container_registry)): deserialized_container.deserialize(result) assert deserialized_container.getName() == instance_container.getName() assert deserialized_container.getMetaData( ) == instance_container.getMetaData() assert deserialized_container.getProperty( "test_setting_1", "value") == instance_container.getProperty("test_setting_1", "value")
def convertUserContainerToQuality(self): if not self._global_container_stack: return new_quality_container = InstanceContainer("") name = self._createUniqueName("quality", "", self.activeQualityName, catalog.i18nc("@label", "Custom profile")) user_settings = self._global_container_stack.getTop() ## Copy all values new_quality_container.deserialize(user_settings.serialize()) ## If the currently active machine does not have quality profiles of its own, # make the new quality profile available for all machines that don't have # unique quality profiles (including the current machine) if not self.filterQualityByMachine: new_quality_container.setDefinition(UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0]) ## Change type / id / name new_quality_container.setMetaDataEntry("type", "quality") new_quality_container.setMetaDataEntry("read_only", False) new_quality_container.setName(name) new_quality_container._id = name UM.Settings.ContainerRegistry.getInstance().addContainer(new_quality_container) self.clearUserSettings() # As all users settings are now transfered to the new quality profile, remove them. self.setActiveQuality(name) return name
def test_roundtrip_instance(tmpdir, process_count, loaded_container_registry): definition = loaded_container_registry.findDefinitionContainers(id = "inherits")[0] instance_container = InstanceContainer("test_container") instance_container.setName("Test Instance Container") instance_container.setDefinition(definition) instance_container.addMetaDataEntry("test", "test") instance_container.setProperty("test_setting_1", "value", 20) temp_file = tmpdir.join("instance_container_test") mp_run(process_count, write_data, temp_file, instance_container) assert len(list(tmpdir.listdir())) == 1 results = mp_run(process_count, read_data, temp_file) for result in results: deserialized_container = InstanceContainer("test_container") deserialized_container.setDefinition(definition) deserialized_container.deserialize(result) assert deserialized_container.getName() == instance_container.getName() assert deserialized_container.getMetaData() == instance_container.getMetaData() assert deserialized_container.getProperty("test_setting_1", "value") == instance_container.getProperty("test_setting_1", "value")
def test_roundtrip_instance(tmpdir, process_count, loaded_container_registry): definition = loaded_container_registry.findDefinitionContainers( id="inherits")[0] instance_container = InstanceContainer("test_container") instance_container.setName("Test Instance Container") instance_container.setDefinition(definition) instance_container.addMetaDataEntry("test", "test") instance_container.setProperty("test_setting_1", "value", 20) temp_file = tmpdir.join("instance_container_test") mp_run(process_count, write_data, temp_file, instance_container) assert len(list(tmpdir.listdir())) == 1 results = mp_run(process_count, read_data, temp_file) for result in results: deserialized_container = InstanceContainer("test_container") deserialized_container.setDefinition(definition) deserialized_container.deserialize(result) assert deserialized_container.getName() == instance_container.getName() assert deserialized_container.getMetaData( ) == instance_container.getMetaData() assert deserialized_container.getProperty( "test_setting_1", "value") == instance_container.getProperty("test_setting_1", "value")
def _loadProfile(self, serialized: str, profile_id: str) -> Optional[InstanceContainer]: """Load a profile from a serialized string. :param serialized: The profile data to read. :param profile_id: The name of the profile. :return: The profile that was stored in the string. """ # Create an empty profile. profile = InstanceContainer(profile_id) profile.setMetaDataEntry("type", "quality_changes") try: profile.deserialize(serialized, file_name=profile_id) except ContainerFormatError as e: Logger.log("e", "Error in the format of a container: %s", str(e)) return None except Exception as e: Logger.log("e", "Error while trying to parse profile: %s", str(e)) return None global_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_stack is None: return None active_quality_definition = ContainerTree.getInstance().machines[ global_stack.definition.getId()].quality_definition if profile.getMetaDataEntry("definition") != active_quality_definition: profile.setMetaDataEntry("definition", active_quality_definition) return profile
def _findQualityChangesContainerInCuraFolder(self, name): quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer) instance_container = None for item in os.listdir(quality_changes_dir): file_path = os.path.join(quality_changes_dir, item) if not os.path.isfile(file_path): continue parser = configparser.ConfigParser() try: parser.read([file_path]) except: # skip, it is not a valid stack file continue if not parser.has_option("general", "name"): continue if parser["general"]["name"] == name: # load the container container_id = os.path.basename(file_path).replace(".inst.cfg", "") instance_container = InstanceContainer(container_id) with open(file_path, "r") as f: serialized = f.read() instance_container.deserialize(serialized, file_path) self.addContainer(instance_container) break return instance_container
def renameQualityContainer(self, container_id, new_name): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality") if containers: new_name = self._createUniqueName("quality", containers[0].getName(), new_name, catalog.i18nc("@label", "Custom profile")) if containers[0].getName() == new_name: # Nothing to do. return # As we also want the id of the container to be changed (so that profile name is the name of the file # on disk. We need to create a new instance and remove it (so the old file of the container is removed) # If we don't do that, we might get duplicates & other weird issues. new_container = InstanceContainer("") new_container.deserialize(containers[0].serialize()) # Actually set the name new_container.setName(new_name) new_container._id = new_name # Todo: Fix proper id change function for this. # Add the "new" container. UM.Settings.ContainerRegistry.getInstance().addContainer(new_container) # Ensure that the renamed profile is saved -before- we remove the old profile. Application.getInstance().saveSettings() # Actually set & remove new / old quality. self.setActiveQuality(new_name) self.removeQualityContainer(containers[0].getId())
def convertUserContainerToQuality(self): if not self._global_container_stack: return new_quality_container = InstanceContainer("") name = self._createUniqueName( "quality", "", self.activeQualityName, catalog.i18nc("@label", "Custom profile")) user_settings = self._global_container_stack.getTop() ## Copy all values new_quality_container.deserialize(user_settings.serialize()) ## If the currently active machine does not have quality profiles of its own, # make the new quality profile available for all machines that don't have # unique quality profiles (including the current machine) if not self.filterQualityByMachine: new_quality_container.setDefinition( UM.Settings.ContainerRegistry.getInstance( ).findDefinitionContainers(id="fdmprinter")[0]) ## Change type / id / name new_quality_container.setMetaDataEntry("type", "quality") new_quality_container.setMetaDataEntry("read_only", False) new_quality_container.setName(name) new_quality_container._id = name UM.Settings.ContainerRegistry.getInstance().addContainer( new_quality_container) self.clearUserSettings( ) # As all users settings are now transfered to the new quality profile, remove them. self.setActiveQuality(name) return name
def readQualityProfileFromString(profile_string): # Create an empty profile - the id and name will be changed by the ContainerRegistry profile = InstanceContainer("") try: profile.deserialize(profile_string) except Exception as e: # Not a valid g-code file. Logger.log("e", "Unable to serialise the profile: %s", str(e)) return None return profile
def _loadProfile(self, serialized, profile_id): # Create an empty profile. profile = InstanceContainer(profile_id) profile.addMetaDataEntry("type", "quality_changes") try: profile.deserialize(serialized) except Exception as e: # Parsing error. This is not a (valid) Cura profile then. Logger.log("e", "Error while trying to parse profile: %s", str(e)) return None return profile
def _loadProfile(self, serialized, profile_id): # Create an empty profile. profile = InstanceContainer(profile_id) profile.setMetaDataEntry("type", "quality_changes") try: profile.deserialize(serialized) except ContainerFormatError as e: Logger.log("e", "Error in the format of a container: %s", str(e)) return None except Exception as e: Logger.log("e", "Error while trying to parse profile: %s", str(e)) return None return profile
def read(self, file_name): if file_name.split(".")[-1] != "gcode": return None prefix = ";SETTING_" + str(GCodeProfileReader.version) + " " prefix_length = len(prefix) # Loading all settings from the file. # They are all at the end, but Python has no reverse seek any more since Python3. # TODO: Consider moving settings to the start? serialized = "" # Will be filled with the serialized profile. try: with open(file_name) as f: for line in f: if line.startswith(prefix): # Remove the prefix and the newline from the line and add it to the rest. serialized += line[prefix_length:-1] except IOError as e: Logger.log("e", "Unable to open file %s for reading: %s", file_name, str(e)) return None # Un-escape the serialized profile. pattern = re.compile("|".join( GCodeProfileReader.escape_characters.keys())) # Perform the replacement with a regular expression. serialized = pattern.sub( lambda m: GCodeProfileReader.escape_characters[re.escape(m.group( 0))], serialized) Logger.log( "i", "Serialized the following from %s: %s" % (file_name, repr(serialized))) # Create an empty profile - the id will be changed later profile = InstanceContainer("") profile.addMetaDataEntry("type", "quality") try: profile.deserialize(serialized) except Exception as e: # Not a valid g-code file. Logger.log("e", "Unable to serialise the profile: %s", str(e)) return None #Creating a unique name using the filename of the GCode new_name = catalog.i18nc("@label", "Custom profile (%s)") % ( os.path.splitext(os.path.basename(file_name))[0]) profile.setName(new_name) profile._id = new_name return profile
def read(self, file_name): # Create an empty profile. profile = InstanceContainer(os.path.basename(os.path.splitext(file_name)[0])) profile.addMetaDataEntry("type", "quality") try: with open(file_name) as f: # Open file for reading. serialized = f.read() except IOError as e: Logger.log("e", "Unable to open file %s for reading: %s", file_name, str(e)) return None try: profile.deserialize(serialized) except Exception as e: # Parsing error. This is not a (valid) Cura profile then. Logger.log("e", "Error while trying to parse profile: %s", str(e)) return None return profile
def read(self, file_name): archive = zipfile.ZipFile(file_name, "r") results = [] for profile_id in archive.namelist(): # Create an empty profile. profile = InstanceContainer(profile_id) profile.addMetaDataEntry("type", "quality_changes") serialized = "" with archive.open(profile_id) as f: serialized = f.read() try: profile.deserialize(serialized.decode("utf-8") ) except Exception as e: # Parsing error. This is not a (valid) Cura profile then. Logger.log("e", "Error while trying to parse profile: %s", str(e)) continue results.append(profile) return results
def _findQualityChangesContainerInCuraFolder( self, name: str) -> Optional[InstanceContainer]: quality_changes_dir = Resources.getPath( cura.CuraApplication.CuraApplication.ResourceTypes. QualityChangesInstanceContainer) instance_container = None for item in os.listdir(quality_changes_dir): file_path = os.path.join(quality_changes_dir, item) if not os.path.isfile(file_path): continue parser = configparser.ConfigParser(interpolation=None) try: parser.read([file_path]) except Exception: # Skip, it is not a valid stack file continue if not parser.has_option("general", "name"): continue if parser["general"]["name"] == name: # Load the container container_id = os.path.basename(file_path).replace( ".inst.cfg", "") if self.findInstanceContainers(id=container_id): # This container is already in the registry, skip it continue instance_container = InstanceContainer(container_id) with open(file_path, "r", encoding="utf-8") as f: serialized = f.read() try: instance_container.deserialize(serialized, file_path) except ContainerFormatError: Logger.logException( "e", "Unable to deserialize InstanceContainer %s", file_path) continue self.addContainer(instance_container) break return instance_container
def readQualityProfileFromString(profile_string) -> Optional[InstanceContainer]: """Read in a profile from a serialized string. :param profile_string: The profile data in serialized form. :return: The resulting Profile object or None if it could not be read. """ # Create an empty profile - the id and name will be changed by the ContainerRegistry profile = InstanceContainer("") try: profile.deserialize(profile_string) except ContainerFormatError as e: Logger.log("e", "Corrupt profile in this g-code file: %s", str(e)) return None except Exception as e: # Not a valid g-code file. Logger.log("e", "Unable to serialise the profile: %s", str(e)) return None return profile
def read(self, file_name): if file_name.split(".")[-1] != "gcode": return None prefix = ";SETTING_" + str(GCodeProfileReader.version) + " " prefix_length = len(prefix) # Loading all settings from the file. # They are all at the end, but Python has no reverse seek any more since Python3. # TODO: Consider moving settings to the start? serialized = "" # Will be filled with the serialized profile. try: with open(file_name) as f: for line in f: if line.startswith(prefix): # Remove the prefix and the newline from the line and add it to the rest. serialized += line[prefix_length : -1] except IOError as e: Logger.log("e", "Unable to open file %s for reading: %s", file_name, str(e)) return None # Un-escape the serialized profile. pattern = re.compile("|".join(GCodeProfileReader.escape_characters.keys())) # Perform the replacement with a regular expression. serialized = pattern.sub(lambda m: GCodeProfileReader.escape_characters[re.escape(m.group(0))], serialized) Logger.log("i", "Serialized the following from %s: %s" %(file_name, repr(serialized))) # Create an empty profile - the id will be changed later profile = InstanceContainer("") profile.addMetaDataEntry("type", "quality") try: profile.deserialize(serialized) except Exception as e: # Not a valid g-code file. Logger.log("e", "Unable to serialise the profile: %s", str(e)) return None #Creating a unique name using the filename of the GCode new_name = catalog.i18nc("@label", "Custom profile (%s)") %(os.path.splitext(os.path.basename(file_name))[0]) profile.setName(new_name) profile._id = new_name return profile
def duplicateContainer(self, container_id): if not self._active_container_stack: return "" containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id) if containers: new_name = self._createUniqueName("quality", "", containers[0].getName(), catalog.i18nc("@label", "Custom profile")) new_container = InstanceContainer("") ## Copy all values new_container.deserialize(containers[0].serialize()) new_container.setReadOnly(False) new_container.setName(new_name) new_container._id = new_name UM.Settings.ContainerRegistry.getInstance().addContainer(new_container) return new_name return ""
def _loadProfile(self, serialized: str, profile_id: str) -> Optional[InstanceContainer]: # Create an empty profile. profile = InstanceContainer(profile_id) profile.setMetaDataEntry("type", "quality_changes") try: profile.deserialize(serialized, file_name = profile_id) except ContainerFormatError as e: Logger.log("e", "Error in the format of a container: %s", str(e)) return None except Exception as e: Logger.log("e", "Error while trying to parse profile: %s", str(e)) return None global_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_stack is None: return None active_quality_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition) if profile.getMetaDataEntry("definition") != active_quality_definition: profile.setMetaDataEntry("definition", active_quality_definition) return profile
def _findQualityChangesContainerInCuraFolder(self, name): quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityChangesInstanceContainer) instance_container = None for item in os.listdir(quality_changes_dir): file_path = os.path.join(quality_changes_dir, item) if not os.path.isfile(file_path): continue parser = configparser.ConfigParser(interpolation=None) try: parser.read([file_path]) except: # skip, it is not a valid stack file continue if not parser.has_option("general", "name"): continue if parser["general"]["name"] == name: # load the container container_id = os.path.basename(file_path).replace(".inst.cfg", "") if self.findInstanceContainers(id = container_id): # this container is already in the registry, skip it continue instance_container = InstanceContainer(container_id) with open(file_path, "r", encoding = "utf-8") as f: serialized = f.read() try: instance_container.deserialize(serialized, file_path) except ContainerFormatError: Logger.logException("e", "Unable to deserialize InstanceContainer %s", file_path) continue self.addContainer(instance_container) break return instance_container
def read(self, file_name): # Load all the nodes / meshdata of the workspace nodes = self._3mf_mesh_reader.read(file_name) if nodes is None: nodes = [] archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its # parsing code. temp_preferences = Preferences() temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks. # Copy a number of settings from the temp preferences to the global global_preferences = Preferences.getInstance() global_preferences.setValue("general/visible_settings", temp_preferences.getValue("general/visible_settings")) global_preferences.setValue("cura/categories_expanded", temp_preferences.getValue("cura/categories_expanded")) Application.getInstance().expandedCategoriesChanged.emit() # Notify the GUI of the change self._id_mapping = {} # We don't add containers right away, but wait right until right before the stack serialization. # We do this so that if something goes wrong, it's easier to clean up. containers_to_add = [] # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few # TODO: cases that the container loaded is the same (most notable in materials & definitions). # TODO: It might be possible that we need to add smarter checking in the future. 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.findDefinitionContainers(id=container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8")) self._container_registry.addContainer(definition_container) Logger.log("d", "Workspace loading is checking materials...") material_containers = [] # 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers(id=container_id) if not materials: material_container = xml_material_profile(container_id) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) containers_to_add.append(material_container) else: if not materials[0].isReadOnly(): # Only create new materials if they are not read only. if self._resolve_strategies["material"] == "override": materials[0].deserialize(archive.open(material_container_file).read().decode("utf-8")) elif self._resolve_strategies["material"] == "new": # Note that we *must* deserialize it with a new ID, as multiple containers will be # auto created & added. material_container = xml_material_profile(self.getNewId(container_id)) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) containers_to_add.append(material_container) material_containers.append(material_container) Logger.log("d", "Workspace loading is checking instance containers...") # Get quality_changes and user profiles saved in the workspace instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] user_instance_containers = [] quality_changes_instance_containers = [] for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) container_type = instance_container.getMetaDataEntry("type") if container_type == "user": # Check if quality changes already exists. user_containers = self._container_registry.findInstanceContainers(id=container_id) if not user_containers: containers_to_add.append(instance_container) else: if self._resolve_strategies["machine"] == "override": user_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8")) elif self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. extruder_id = instance_container.getMetaDataEntry("extruder", None) if extruder_id: new_id = self.getNewId(extruder_id) + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry("extruder", self.getNewId(extruder_id)) containers_to_add.append(instance_container) machine_id = instance_container.getMetaDataEntry("machine", None) if machine_id: new_id = self.getNewId(machine_id) + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry("machine", self.getNewId(machine_id)) containers_to_add.append(instance_container) user_instance_containers.append(instance_container) elif container_type == "quality_changes": # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers(id = container_id) if not quality_changes: containers_to_add.append(instance_container) else: if self._resolve_strategies["quality_changes"] == "override": quality_changes[0].deserialize(archive.open(instance_container_file).read().decode("utf-8")) elif self._resolve_strategies["quality_changes"] is None: # The ID already exists, but nothing in the values changed, so do nothing. pass quality_changes_instance_containers.append(instance_container) else: continue # Add all the containers right before we try to add / serialize the stack for container in containers_to_add: self._container_registry.addContainer(container) # Get the stack(s) saved in the workspace. Logger.log("d", "Workspace loading is checking stacks containers...") container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)] global_stack = None extruder_stacks = [] container_stacks_added = [] try: for container_stack_file in container_stack_files: container_id = self._stripFileToId(container_stack_file) # Check if a stack by this ID already exists; container_stacks = self._container_registry.findContainerStacks(id=container_id) if container_stacks: stack = container_stacks[0] if self._resolve_strategies["machine"] == "override": container_stacks[0].deserialize(archive.open(container_stack_file).read().decode("utf-8")) elif self._resolve_strategies["machine"] == "new": new_id = self.getNewId(container_id) stack = ContainerStack(new_id) stack.deserialize(archive.open(container_stack_file).read().decode("utf-8")) # Ensure a unique ID and name stack._id = new_id # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the # bound machine also needs to change. if stack.getMetaDataEntry("machine", None): stack.setMetaDataEntry("machine", self.getNewId(stack.getMetaDataEntry("machine"))) if stack.getMetaDataEntry("type") != "extruder_train": # Only machines need a new name, stacks may be non-unique stack.setName(self._container_registry.uniqueName(stack.getName())) container_stacks_added.append(stack) self._container_registry.addContainer(stack) else: Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) else: stack = ContainerStack(container_id) # Deserialize stack by converting read data from bytes to string stack.deserialize(archive.open(container_stack_file).read().decode("utf-8")) container_stacks_added.append(stack) self._container_registry.addContainer(stack) if stack.getMetaDataEntry("type") == "extruder_train": extruder_stacks.append(stack) else: global_stack = stack except: Logger.log("W", "We failed to serialize the stack. Trying to clean up.") # Something went really wrong. Try to remove any data that we added. for container in containers_to_add: self._container_registry.getInstance().removeContainer(container.getId()) for container in container_stacks_added: self._container_registry.getInstance().removeContainer(container.getId()) return None if self._resolve_strategies["machine"] == "new": # A new machine was made, but it was serialized with the wrong user container. Fix that now. for container in user_instance_containers: extruder_id = container.getMetaDataEntry("extruder", None) if extruder_id: for extruder in extruder_stacks: if extruder.getId() == extruder_id: extruder.replaceContainer(0, container) continue machine_id = container.getMetaDataEntry("machine", None) if machine_id: if global_stack.getId() == machine_id: global_stack.replaceContainer(0, container) continue if self._resolve_strategies["quality_changes"] == "new": # Quality changes needs to get a new ID, added to registry and to the right stacks for container in quality_changes_instance_containers: old_id = container.getId() container.setName(self._container_registry.uniqueName(container.getName())) # We're not really supposed to change the ID in normal cases, but this is an exception. container._id = self.getNewId(container.getId()) # The container was not added yet, as it didn't have an unique ID. It does now, so add it. self._container_registry.addContainer(container) # Replace the quality changes container old_container = global_stack.findContainer({"type": "quality_changes"}) if old_container.getId() == old_id: quality_changes_index = global_stack.getContainerIndex(old_container) global_stack.replaceContainer(quality_changes_index, container) continue for stack in extruder_stacks: old_container = stack.findContainer({"type": "quality_changes"}) if old_container.getId() == old_id: quality_changes_index = stack.getContainerIndex(old_container) stack.replaceContainer(quality_changes_index, container) if self._resolve_strategies["material"] == "new": for material in material_containers: old_material = global_stack.findContainer({"type": "material"}) if old_material.getId() in self._id_mapping: material_index = global_stack.getContainerIndex(old_material) global_stack.replaceContainer(material_index, material) continue for stack in extruder_stacks: old_material = stack.findContainer({"type": "material"}) if old_material.getId() in self._id_mapping: material_index = stack.getContainerIndex(old_material) stack.replaceContainer(material_index, material) continue for stack in extruder_stacks: ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId()) else: # Machine has no extruders, but it needs to be registered with the extruder manager. ExtruderManager.getInstance().registerExtruder(None, global_stack.getId()) Logger.log("d", "Workspace loading is notifying rest of the code of changes...") # Notify everything/one that is to notify about changes. for container in global_stack.getContainers(): global_stack.containersChanged.emit(container) for stack in extruder_stacks: stack.setNextStack(global_stack) for container in stack.getContainers(): stack.containersChanged.emit(container) # Actually change the active machine. Application.getInstance().setGlobalContainerStack(global_stack) return nodes
def preRead(self, file_name, show_dialog=True, *args, **kwargs): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler( ).getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead( file_name) == WorkspaceReader.PreReadResult.accepted: pass else: Logger.log( "w", "Could not find reader that was able to read the scene data for 3MF workspace" ) return WorkspaceReader.PreReadResult.failed machine_name = "" machine_type = "" variant_type_name = i18n_catalog.i18nc("@label", "Nozzle") num_extruders = 0 # Check if there are any conflicts, so we can ask the user. archive = zipfile.ZipFile(file_name, "r") cura_file_names = [ name for name in archive.namelist() if name.startswith("Cura/") ] container_stack_files = [ name for name in cura_file_names if name.endswith(self._container_stack_suffix) ] self._resolve_strategies = { "machine": None, "quality_changes": None, "material": None } machine_conflict = False quality_changes_conflict = False for container_stack_file in container_stack_files: container_id = self._stripFileToId(container_stack_file) serialized = archive.open(container_stack_file).read().decode( "utf-8") if machine_name == "": machine_name = self._getMachineNameFromSerializedStack( serialized) stacks = self._container_registry.findContainerStacks( id=container_id) if stacks: # Check if there are any changes at all in any of the container stacks. id_list = self._getContainerIdListFromSerialized(serialized) for index, container_id in enumerate(id_list): if stacks[0].getContainer(index).getId() != container_id: machine_conflict = True Job.yieldThread() 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.findDefinitionContainers( id=container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize( archive.open(definition_container_file).read().decode( "utf-8")) else: definition_container = definitions[0] if definition_container.getMetaDataEntry("type") != "extruder": machine_type = definition_container.getName() variant_type_name = definition_container.getMetaDataEntry( "variants_name", variant_type_name) else: num_extruders += 1 Job.yieldThread() if num_extruders == 0: num_extruders = 1 # No extruder stacks found, which means there is one extruder extruders = num_extruders * [""] material_labels = [] material_conflict = False xml_material_profile = self._getXmlProfileClass() if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer( xml_material_profile).preferredSuffix 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers( id=container_id) material_labels.append( self._getMaterialLabelFromSerialized( archive.open(material_container_file).read().decode( "utf-8"))) if materials and not materials[0].isReadOnly( ): # Only non readonly materials can be in conflict material_conflict = True Job.yieldThread() # Check if any quality_changes instance container is in conflict. instance_container_files = [ name for name in cura_file_names if name.endswith(self._instance_container_suffix) ] quality_name = "" quality_type = "" num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes num_user_settings = 0 for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize( archive.open(instance_container_file).read().decode("utf-8")) container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": quality_name = instance_container.getName() num_settings_overriden_by_quality_changes += len( instance_container._instances) # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers( id=container_id) if quality_changes: # Check if there really is a conflict by comparing the values if quality_changes[0] != instance_container: quality_changes_conflict = True elif container_type == "quality": # If the quality name is not set (either by quality or changes, set it now) # Quality changes should always override this (as they are "on top") if quality_name == "": quality_name = instance_container.getName() quality_type = instance_container.getName() elif container_type == "user": num_user_settings += len(instance_container._instances) Job.yieldThread() num_visible_settings = 0 try: temp_preferences = Preferences() temp_preferences.readFromFile( io.TextIOWrapper(archive.open("Cura/preferences.cfg")) ) # We need to wrap it, else the archive parser breaks. visible_settings_string = temp_preferences.getValue( "general/visible_settings") if visible_settings_string is not None: num_visible_settings = len(visible_settings_string.split(";")) active_mode = temp_preferences.getValue("cura/active_mode") if not active_mode: active_mode = Preferences.getInstance().getValue( "cura/active_mode") except KeyError: # If there is no preferences file, it's not a workspace, so notify user of failure. Logger.log("w", "File %s is not a valid workspace.", file_name) return WorkspaceReader.PreReadResult.failed # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog. if not show_dialog: return WorkspaceReader.PreReadResult.accepted # Show the dialog, informing the user what is about to happen. self._dialog.setMachineConflict(machine_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict) self._dialog.setMaterialConflict(material_conflict) self._dialog.setNumVisibleSettings(num_visible_settings) self._dialog.setQualityName(quality_name) self._dialog.setQualityType(quality_type) self._dialog.setNumSettingsOverridenByQualityChanges( num_settings_overriden_by_quality_changes) self._dialog.setNumUserSettings(num_user_settings) self._dialog.setActiveMode(active_mode) self._dialog.setMachineName(machine_name) self._dialog.setMaterialLabels(material_labels) self._dialog.setMachineType(machine_type) self._dialog.setExtruders(extruders) self._dialog.setVariantType(variant_type_name) self._dialog.setHasObjectsOnPlate( Application.getInstance().platformActivity) self._dialog.show() # Block until the dialog is closed. self._dialog.waitForClose() if self._dialog.getResult() == {}: return WorkspaceReader.PreReadResult.cancelled self._resolve_strategies = self._dialog.getResult() return WorkspaceReader.PreReadResult.accepted
def read(self, file_name): archive = zipfile.ZipFile(file_name, "r") cura_file_names = [ name for name in archive.namelist() if name.startswith("Cura/") ] # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its # parsing code. temp_preferences = Preferences() temp_preferences.readFromFile( io.TextIOWrapper(archive.open("Cura/preferences.cfg")) ) # We need to wrap it, else the archive parser breaks. # Copy a number of settings from the temp preferences to the global global_preferences = Preferences.getInstance() visible_settings = temp_preferences.getValue( "general/visible_settings") if visible_settings is None: Logger.log( "w", "Workspace did not contain visible settings. Leaving visibility unchanged" ) else: global_preferences.setValue("general/visible_settings", visible_settings) categories_expanded = temp_preferences.getValue( "cura/categories_expanded") if categories_expanded is None: Logger.log( "w", "Workspace did not contain expanded categories. Leaving them unchanged" ) else: global_preferences.setValue("cura/categories_expanded", categories_expanded) Application.getInstance().expandedCategoriesChanged.emit( ) # Notify the GUI of the change self._id_mapping = {} # We don't add containers right away, but wait right until right before the stack serialization. # We do this so that if something goes wrong, it's easier to clean up. containers_to_add = [] # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few # TODO: cases that the container loaded is the same (most notable in materials & definitions). # TODO: It might be possible that we need to add smarter checking in the future. 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.findDefinitionContainers( id=container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize( archive.open(definition_container_file).read().decode( "utf-8")) self._container_registry.addContainer(definition_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking materials...") material_containers = [] # 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers( id=container_id) if not materials: material_container = xml_material_profile(container_id) material_container.deserialize( archive.open(material_container_file).read().decode( "utf-8")) containers_to_add.append(material_container) else: if not materials[0].isReadOnly( ): # Only create new materials if they are not read only. if self._resolve_strategies["material"] == "override": materials[0].deserialize( archive.open(material_container_file).read(). decode("utf-8")) elif self._resolve_strategies["material"] == "new": # Note that we *must* deserialize it with a new ID, as multiple containers will be # auto created & added. material_container = xml_material_profile( self.getNewId(container_id)) material_container.deserialize( archive.open(material_container_file).read(). decode("utf-8")) containers_to_add.append(material_container) material_containers.append(material_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking instance containers...") # Get quality_changes and user profiles saved in the workspace instance_container_files = [ name for name in cura_file_names if name.endswith(self._instance_container_suffix) ] user_instance_containers = [] quality_changes_instance_containers = [] for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize( archive.open(instance_container_file).read().decode("utf-8")) container_type = instance_container.getMetaDataEntry("type") Job.yieldThread() if container_type == "user": # Check if quality changes already exists. user_containers = self._container_registry.findInstanceContainers( id=container_id) if not user_containers: containers_to_add.append(instance_container) else: if self._resolve_strategies[ "machine"] == "override" or self._resolve_strategies[ "machine"] is None: user_containers[0].deserialize( archive.open(instance_container_file).read(). decode("utf-8")) elif self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. extruder_id = instance_container.getMetaDataEntry( "extruder", None) if extruder_id: new_id = self.getNewId( extruder_id) + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry( "extruder", self.getNewId(extruder_id)) containers_to_add.append(instance_container) machine_id = instance_container.getMetaDataEntry( "machine", None) if machine_id: new_id = self.getNewId( machine_id) + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry( "machine", self.getNewId(machine_id)) containers_to_add.append(instance_container) user_instance_containers.append(instance_container) elif container_type == "quality_changes": # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers( id=container_id) if not quality_changes: containers_to_add.append(instance_container) else: if self._resolve_strategies[ "quality_changes"] == "override": quality_changes[0].deserialize( archive.open(instance_container_file).read(). decode("utf-8")) elif self._resolve_strategies["quality_changes"] is None: # The ID already exists, but nothing in the values changed, so do nothing. pass quality_changes_instance_containers.append(instance_container) else: continue # Add all the containers right before we try to add / serialize the stack for container in containers_to_add: self._container_registry.addContainer(container) container.setDirty(True) # Get the stack(s) saved in the workspace. Logger.log("d", "Workspace loading is checking stacks containers...") container_stack_files = [ name for name in cura_file_names if name.endswith(self._container_stack_suffix) ] global_stack = None extruder_stacks = [] container_stacks_added = [] try: for container_stack_file in container_stack_files: container_id = self._stripFileToId(container_stack_file) # Check if a stack by this ID already exists; container_stacks = self._container_registry.findContainerStacks( id=container_id) if container_stacks: stack = container_stacks[0] if self._resolve_strategies["machine"] == "override": # TODO: HACK # There is a machine, check if it has authenticationd data. If so, keep that data. network_authentication_id = container_stacks[ 0].getMetaDataEntry("network_authentication_id") network_authentication_key = container_stacks[ 0].getMetaDataEntry("network_authentication_key") container_stacks[0].deserialize( archive.open(container_stack_file).read().decode( "utf-8")) if network_authentication_id: container_stacks[0].addMetaDataEntry( "network_authentication_id", network_authentication_id) if network_authentication_key: container_stacks[0].addMetaDataEntry( "network_authentication_key", network_authentication_key) elif self._resolve_strategies["machine"] == "new": new_id = self.getNewId(container_id) stack = ContainerStack(new_id) stack.deserialize( archive.open(container_stack_file).read().decode( "utf-8")) # Ensure a unique ID and name stack._id = new_id # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the # bound machine also needs to change. if stack.getMetaDataEntry("machine", None): stack.setMetaDataEntry( "machine", self.getNewId( stack.getMetaDataEntry("machine"))) if stack.getMetaDataEntry("type") != "extruder_train": # Only machines need a new name, stacks may be non-unique stack.setName( self._container_registry.uniqueName( stack.getName())) container_stacks_added.append(stack) self._container_registry.addContainer(stack) else: Logger.log( "w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) else: stack = ContainerStack(container_id) # Deserialize stack by converting read data from bytes to string stack.deserialize( archive.open(container_stack_file).read().decode( "utf-8")) container_stacks_added.append(stack) self._container_registry.addContainer(stack) if stack.getMetaDataEntry("type") == "extruder_train": extruder_stacks.append(stack) else: global_stack = stack Job.yieldThread() except: Logger.logException( "w", "We failed to serialize the stack. Trying to clean up.") # Something went really wrong. Try to remove any data that we added. for container in containers_to_add: self._container_registry.getInstance().removeContainer( container.getId()) for container in container_stacks_added: self._container_registry.getInstance().removeContainer( container.getId()) return None if self._resolve_strategies["machine"] == "new": # A new machine was made, but it was serialized with the wrong user container. Fix that now. for container in user_instance_containers: extruder_id = container.getMetaDataEntry("extruder", None) if extruder_id: for extruder in extruder_stacks: if extruder.getId() == extruder_id: extruder.replaceContainer(0, container) continue machine_id = container.getMetaDataEntry("machine", None) if machine_id: if global_stack.getId() == machine_id: global_stack.replaceContainer(0, container) continue if self._resolve_strategies["quality_changes"] == "new": # Quality changes needs to get a new ID, added to registry and to the right stacks for container in quality_changes_instance_containers: old_id = container.getId() container.setName( self._container_registry.uniqueName(container.getName())) # We're not really supposed to change the ID in normal cases, but this is an exception. container._id = self.getNewId(container.getId()) # The container was not added yet, as it didn't have an unique ID. It does now, so add it. self._container_registry.addContainer(container) # Replace the quality changes container old_container = global_stack.findContainer( {"type": "quality_changes"}) if old_container.getId() == old_id: quality_changes_index = global_stack.getContainerIndex( old_container) global_stack.replaceContainer(quality_changes_index, container) continue for stack in extruder_stacks: old_container = stack.findContainer( {"type": "quality_changes"}) if old_container.getId() == old_id: quality_changes_index = stack.getContainerIndex( old_container) stack.replaceContainer(quality_changes_index, container) if self._resolve_strategies["material"] == "new": for material in material_containers: old_material = global_stack.findContainer({"type": "material"}) if old_material.getId() in self._id_mapping: material_index = global_stack.getContainerIndex( old_material) global_stack.replaceContainer(material_index, material) continue for stack in extruder_stacks: old_material = stack.findContainer({"type": "material"}) if old_material.getId() in self._id_mapping: material_index = stack.getContainerIndex(old_material) stack.replaceContainer(material_index, material) continue for stack in extruder_stacks: ExtruderManager.getInstance().registerExtruder( stack, global_stack.getId()) else: # Machine has no extruders, but it needs to be registered with the extruder manager. ExtruderManager.getInstance().registerExtruder( None, global_stack.getId()) Logger.log( "d", "Workspace loading is notifying rest of the code of changes...") # Notify everything/one that is to notify about changes. global_stack.containersChanged.emit(global_stack.getTop()) for stack in extruder_stacks: stack.setNextStack(global_stack) stack.containersChanged.emit(stack.getTop()) # Actually change the active machine. Application.getInstance().setGlobalContainerStack(global_stack) # Load all the nodes / meshdata of the workspace nodes = self._3mf_mesh_reader.read(file_name) if nodes is None: nodes = [] return nodes
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 preRead(self, file_name, show_dialog=True, *args, **kwargs): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted: pass else: Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace") return WorkspaceReader.PreReadResult.failed machine_name = "" machine_type = "" variant_type_name = i18n_catalog.i18nc("@label", "Nozzle") # Check if there are any conflicts, so we can ask the user. archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] # A few lists of containers in this project files. # When loading the global stack file, it may be associated with those containers, which may or may not be # in Cura already, so we need to provide them as alternative search lists. definition_container_list = [] instance_container_list = [] material_container_list = [] resolve_strategy_keys = ["machine", "material", "quality_changes"] self._resolve_strategies = {k: None for k in resolve_strategy_keys} containers_found_dict = {k: False for k in resolve_strategy_keys} # # Read definition containers # machine_definition_container_count = 0 extruder_definition_container_count = 0 definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] for each_definition_container_file in definition_container_files: container_id = self._stripFileToId(each_definition_container_file) definitions = self._container_registry.findDefinitionContainers(id=container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8")) else: definition_container = definitions[0] definition_container_list.append(definition_container) definition_container_type = definition_container.getMetaDataEntry("type") if definition_container_type == "machine": machine_type = definition_container.getName() variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name) machine_definition_container_count += 1 elif definition_container_type == "extruder": extruder_definition_container_count += 1 else: Logger.log("w", "Unknown definition container type %s for %s", definition_container_type, each_definition_container_file) Job.yieldThread() # sanity check if machine_definition_container_count != 1: msg = "Expecting one machine definition container but got %s" % machine_definition_container_count Logger.log("e", msg) raise RuntimeError(msg) material_labels = [] material_conflict = False xml_material_profile = self._getXmlProfileClass() if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers(id=container_id) material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8"))) if materials: containers_found_dict["material"] = True if not materials[0].isReadOnly(): # Only non readonly materials can be in conflict material_conflict = True Job.yieldThread() # Check if any quality_changes instance container is in conflict. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] quality_name = "" quality_type = "" num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes num_user_settings = 0 quality_changes_conflict = False definition_changes_conflict = False for each_instance_container_file in instance_container_files: container_id = self._stripFileToId(each_instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8")) instance_container_list.append(instance_container) container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": quality_name = instance_container.getName() num_settings_overriden_by_quality_changes += len(instance_container._instances) # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers(id = container_id) if quality_changes: containers_found_dict["quality_changes"] = True # Check if there really is a conflict by comparing the values if quality_changes[0] != instance_container: quality_changes_conflict = True elif container_type == "definition_changes": definition_name = instance_container.getName() num_settings_overriden_by_definition_changes += len(instance_container._instances) definition_changes = self._container_registry.findDefinitionContainers(id = container_id) if definition_changes: if definition_changes[0] != instance_container: definition_changes_conflict = True elif container_type == "user": num_user_settings += len(instance_container._instances) elif container_type in self._ignored_instance_container_types: # Ignore certain instance container types Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type) continue Job.yieldThread() # Load ContainerStack files and ExtruderStack files global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles( file_name, cura_file_names) machine_conflict = False # Because there can be cases as follows: # - the global stack exists but some/all of the extruder stacks DON'T exist # - the global stack DOESN'T exist but some/all of the extruder stacks exist # To simplify this, only check if the global stack exists or not container_id = self._stripFileToId(global_stack_file) serialized = archive.open(global_stack_file).read().decode("utf-8") machine_name = self._getMachineNameFromSerializedStack(serialized) stacks = self._container_registry.findContainerStacks(id = container_id) if stacks: global_stack = stacks[0] containers_found_dict["machine"] = True # Check if there are any changes at all in any of the container stacks. id_list = self._getContainerIdListFromSerialized(serialized) for index, container_id in enumerate(id_list): # take into account the old empty container IDs container_id = self._old_empty_profile_id_dict.get(container_id, container_id) if global_stack.getContainer(index).getId() != container_id: machine_conflict = True break Job.yieldThread() # if the global stack is found, we check if there are conflicts in the extruder stacks if containers_found_dict["machine"] and not machine_conflict: for extruder_stack_file in extruder_stack_files: container_id = self._stripFileToId(extruder_stack_file) serialized = archive.open(extruder_stack_file).read().decode("utf-8") parser = configparser.ConfigParser() parser.read_string(serialized) # The check should be done for the extruder stack that's associated with the existing global stack, # and those extruder stacks may have different IDs. # So we check according to the positions position = str(parser["metadata"]["position"]) if position not in global_stack.extruders: # The extruder position defined in the project doesn't exist in this global stack. # We can say that it is a machine conflict, but it is very hard to override the machine in this # case because we need to override the existing extruders and add the non-existing extruders. # # HACK: # To make this simple, we simply say that there is no machine conflict and create a new machine # by default. machine_conflict = False break existing_extruder_stack = global_stack.extruders[position] # check if there are any changes at all in any of the container stacks. id_list = self._getContainerIdListFromSerialized(serialized) for index, container_id in enumerate(id_list): # take into account the old empty container IDs container_id = self._old_empty_profile_id_dict.get(container_id, container_id) if existing_extruder_stack.getContainer(index).getId() != container_id: machine_conflict = True break num_visible_settings = 0 try: temp_preferences = Preferences() temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks. visible_settings_string = temp_preferences.getValue("general/visible_settings") if visible_settings_string is not None: num_visible_settings = len(visible_settings_string.split(";")) active_mode = temp_preferences.getValue("cura/active_mode") if not active_mode: active_mode = Preferences.getInstance().getValue("cura/active_mode") except KeyError: # If there is no preferences file, it's not a workspace, so notify user of failure. Logger.log("w", "File %s is not a valid workspace.", file_name) return WorkspaceReader.PreReadResult.failed # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog. if not show_dialog: return WorkspaceReader.PreReadResult.accepted # prepare data for the dialog num_extruders = extruder_definition_container_count if num_extruders == 0: num_extruders = 1 # No extruder stacks found, which means there is one extruder extruders = num_extruders * [""] # Show the dialog, informing the user what is about to happen. self._dialog.setMachineConflict(machine_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict) self._dialog.setDefinitionChangesConflict(definition_changes_conflict) self._dialog.setMaterialConflict(material_conflict) self._dialog.setNumVisibleSettings(num_visible_settings) self._dialog.setQualityName(quality_name) self._dialog.setQualityType(quality_type) self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes) self._dialog.setNumUserSettings(num_user_settings) self._dialog.setActiveMode(active_mode) self._dialog.setMachineName(machine_name) self._dialog.setMaterialLabels(material_labels) self._dialog.setMachineType(machine_type) self._dialog.setExtruders(extruders) self._dialog.setVariantType(variant_type_name) self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity) self._dialog.show() # Block until the dialog is closed. self._dialog.waitForClose() if self._dialog.getResult() == {}: return WorkspaceReader.PreReadResult.cancelled self._resolve_strategies = self._dialog.getResult() # # There can be 3 resolve strategies coming from the dialog: # - new: create a new container # - override: override the existing container # - None: There is no conflict, which means containers with the same IDs may or may not be there already. # If there is an existing container, there is no conflict between the them, and default to "override" # If there is no existing container, default to "new" # # Default values for key, strategy in self._resolve_strategies.items(): if key not in containers_found_dict or strategy is not None: continue self._resolve_strategies[key] = "override" if containers_found_dict[key] else "new" return WorkspaceReader.PreReadResult.accepted
def read(self, file_name): archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its # parsing code. temp_preferences = Preferences() temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks. # Copy a number of settings from the temp preferences to the global global_preferences = Preferences.getInstance() visible_settings = temp_preferences.getValue("general/visible_settings") if visible_settings is None: Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged") else: global_preferences.setValue("general/visible_settings", visible_settings) categories_expanded = temp_preferences.getValue("cura/categories_expanded") if categories_expanded is None: Logger.log("w", "Workspace did not contain expanded categories. Leaving them unchanged") else: global_preferences.setValue("cura/categories_expanded", categories_expanded) Application.getInstance().expandedCategoriesChanged.emit() # Notify the GUI of the change self._id_mapping = {} # We don't add containers right away, but wait right until right before the stack serialization. # We do this so that if something goes wrong, it's easier to clean up. containers_to_add = [] global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names) global_stack = None extruder_stacks = [] extruder_stacks_added = [] container_stacks_added = [] containers_added = [] global_stack_id_original = self._stripFileToId(global_stack_file) global_stack_id_new = global_stack_id_original global_stack_need_rename = False extruder_stack_id_map = {} # new and old ExtruderStack IDs map if self._resolve_strategies["machine"] == "new": # We need a new id if the id already exists if self._container_registry.findContainerStacks(id = global_stack_id_original): global_stack_id_new = self.getNewId(global_stack_id_original) global_stack_need_rename = True for each_extruder_stack_file in extruder_stack_files: old_container_id = self._stripFileToId(each_extruder_stack_file) new_container_id = old_container_id if self._container_registry.findContainerStacks(id = old_container_id): # get a new name for this extruder new_container_id = self.getNewId(old_container_id) extruder_stack_id_map[old_container_id] = new_container_id # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few # TODO: cases that the container loaded is the same (most notable in materials & definitions). # TODO: It might be possible that we need to add smarter checking in the future. 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.findDefinitionContainers(id = container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8")) self._container_registry.addContainer(definition_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking materials...") material_containers = [] # 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers(id = container_id) if not materials: material_container = xml_material_profile(container_id) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) containers_to_add.append(material_container) else: material_container = materials[0] if not material_container.isReadOnly(): # Only create new materials if they are not read only. if self._resolve_strategies["material"] == "override": material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) elif self._resolve_strategies["material"] == "new": # Note that we *must* deserialize it with a new ID, as multiple containers will be # auto created & added. material_container = xml_material_profile(self.getNewId(container_id)) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) containers_to_add.append(material_container) material_containers.append(material_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking instance containers...") # Get quality_changes and user profiles saved in the workspace instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] user_instance_containers = [] quality_and_definition_changes_instance_containers = [] for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) serialized = archive.open(instance_container_file).read().decode("utf-8") # HACK! we ignore "quality" and "variant" instance containers! parser = configparser.ConfigParser() parser.read_string(serialized) if not parser.has_option("metadata", "type"): Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file) continue if parser.get("metadata", "type") in self._ignored_instance_container_types: continue instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize(serialized) container_type = instance_container.getMetaDataEntry("type") Job.yieldThread() # # IMPORTANT: # If an instance container (or maybe other type of container) exists, and user chooses "Create New", # we need to rename this container and all references to it, and changing those references are VERY # HARD. # if container_type in self._ignored_instance_container_types: # Ignore certain instance container types Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type) continue elif container_type == "user": # Check if quality changes already exists. user_containers = self._container_registry.findInstanceContainers(id = container_id) if not user_containers: containers_to_add.append(instance_container) else: if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None: instance_container = user_containers[0] instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) instance_container.setDirty(True) elif self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. old_extruder_id = instance_container.getMetaDataEntry("extruder", None) if old_extruder_id: new_extruder_id = extruder_stack_id_map[old_extruder_id] new_id = new_extruder_id + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry("extruder", new_extruder_id) containers_to_add.append(instance_container) machine_id = instance_container.getMetaDataEntry("machine", None) if machine_id: new_machine_id = self.getNewId(machine_id) new_id = new_machine_id + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry("machine", new_machine_id) containers_to_add.append(instance_container) user_instance_containers.append(instance_container) elif container_type in ("quality_changes", "definition_changes"): # Check if quality changes already exists. changes_containers = self._container_registry.findInstanceContainers(id = container_id) if not changes_containers: # no existing containers with the same ID, so we can safely add the new one containers_to_add.append(instance_container) else: # we have found existing container with the same ID, so we need to resolve according to the # selected strategy. if self._resolve_strategies[container_type] == "override": instance_container = changes_containers[0] instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) instance_container.setDirty(True) elif self._resolve_strategies[container_type] == "new": # TODO: how should we handle the case "new" for quality_changes and definition_changes? instance_container.setName(self._container_registry.uniqueName(instance_container.getName())) new_changes_container_id = self.getNewId(instance_container.getId()) instance_container._id = new_changes_container_id # TODO: we don't know the following is correct or not, need to verify # AND REFACTOR!!! if self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. old_extruder_id = instance_container.getMetaDataEntry("extruder", None) if old_extruder_id: new_extruder_id = extruder_stack_id_map[old_extruder_id] instance_container.setMetaDataEntry("extruder", new_extruder_id) machine_id = instance_container.getMetaDataEntry("machine", None) if machine_id: new_machine_id = self.getNewId(machine_id) instance_container.setMetaDataEntry("machine", new_machine_id) containers_to_add.append(instance_container) elif self._resolve_strategies[container_type] is None: # The ID already exists, but nothing in the values changed, so do nothing. pass quality_and_definition_changes_instance_containers.append(instance_container) else: existing_container = self._container_registry.findInstanceContainers(id = container_id) if not existing_container: containers_to_add.append(instance_container) if global_stack_need_rename: if instance_container.getMetaDataEntry("machine"): instance_container.setMetaDataEntry("machine", global_stack_id_new) # Add all the containers right before we try to add / serialize the stack for container in containers_to_add: self._container_registry.addContainer(container) container.setDirty(True) containers_added.append(container) # Get the stack(s) saved in the workspace. Logger.log("d", "Workspace loading is checking stacks containers...") # -- # load global stack file try: if self._resolve_strategies["machine"] == "override": container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original) stack = container_stacks[0] # HACK # There is a machine, check if it has authentication data. If so, keep that data. network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id") network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key") container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8")) if network_authentication_id: container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id) if network_authentication_key: container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key) elif self._resolve_strategies["machine"] == "new": # create a new global stack stack = GlobalStack(global_stack_id_new) # Deserialize stack by converting read data from bytes to string stack.deserialize(archive.open(global_stack_file).read().decode("utf-8")) # Ensure a unique ID and name stack._id = global_stack_id_new # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the # bound machine also needs to change. if stack.getMetaDataEntry("machine", None): stack.setMetaDataEntry("machine", global_stack_id_new) # Only machines need a new name, stacks may be non-unique stack.setName(global_stack_id_new) container_stacks_added.append(stack) self._container_registry.addContainer(stack) containers_added.append(stack) else: Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) global_stack = stack Job.yieldThread() except: Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") # Something went really wrong. Try to remove any data that we added. for container in containers_added: self._container_registry.removeContainer(container.getId()) return # -- # load extruder stack files try: for extruder_stack_file in extruder_stack_files: container_id = self._stripFileToId(extruder_stack_file) extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8") if self._resolve_strategies["machine"] == "override": # deserialize new extruder stack over the current ones stack = self._overrideExtruderStack(global_stack, extruder_file_content) elif self._resolve_strategies["machine"] == "new": new_id = extruder_stack_id_map[container_id] stack = ExtruderStack(new_id) # HACK: the global stack can have a new name, so we need to make sure that this extruder stack # references to the new name instead of the old one. Normally, this can be done after # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize() # also does addExtruder() to its machine stack, so we have to make sure that it's pointing # to the right machine BEFORE deserialization. extruder_config = configparser.ConfigParser() extruder_config.read_string(extruder_file_content) extruder_config.set("metadata", "machine", global_stack_id_new) tmp_string_io = io.StringIO() extruder_config.write(tmp_string_io) extruder_file_content = tmp_string_io.getvalue() stack.deserialize(extruder_file_content) # Ensure a unique ID and name stack._id = new_id self._container_registry.addContainer(stack) extruder_stacks_added.append(stack) containers_added.append(stack) else: Logger.log("w", "Unknown resolve strategy: %s", self._resolve_strategies["machine"]) extruder_stacks.append(stack) except: Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") # Something went really wrong. Try to remove any data that we added. for container in containers_added: self._container_registry.removeContainer(container.getId()) return # # Replacing the old containers if resolve is "new". # When resolve is "new", some containers will get renamed, so all the other containers that reference to those # MUST get updated too. # if self._resolve_strategies["machine"] == "new": # A new machine was made, but it was serialized with the wrong user container. Fix that now. for container in user_instance_containers: # replacing the container ID for user instance containers for the extruders extruder_id = container.getMetaDataEntry("extruder", None) if extruder_id: for extruder in extruder_stacks: if extruder.getId() == extruder_id: extruder.userChanges = container continue # replacing the container ID for user instance containers for the machine machine_id = container.getMetaDataEntry("machine", None) if machine_id: if global_stack.getId() == machine_id: global_stack.userChanges = container continue for changes_container_type in ("quality_changes", "definition_changes"): if self._resolve_strategies[changes_container_type] == "new": # Quality changes needs to get a new ID, added to registry and to the right stacks for each_changes_container in quality_and_definition_changes_instance_containers: # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the # instance container loading part. new_id = each_changes_container.getId() # Find the old (current) changes container in the global stack if changes_container_type == "quality_changes": old_container = global_stack.qualityChanges elif changes_container_type == "definition_changes": old_container = global_stack.definitionChanges # sanity checks # NOTE: The following cases SHOULD NOT happen!!!! if not old_container: Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!", changes_container_type, global_stack.getId()) # Replace the quality/definition changes container if it's in the GlobalStack # NOTE: we can get an empty container here, but the IDs will not match, # so this comparison is fine. if self._id_mapping.get(old_container.getId()) == new_id: if changes_container_type == "quality_changes": global_stack.qualityChanges = each_changes_container elif changes_container_type == "definition_changes": global_stack.definitionChanges = each_changes_container continue # Replace the quality/definition changes container if it's in one of the ExtruderStacks for each_extruder_stack in extruder_stacks: changes_container = None if changes_container_type == "quality_changes": changes_container = each_extruder_stack.qualityChanges elif changes_container_type == "definition_changes": changes_container = each_extruder_stack.definitionChanges # sanity checks # NOTE: The following cases SHOULD NOT happen!!!! if not changes_container: Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!", changes_container_type, each_extruder_stack.getId()) # NOTE: we can get an empty container here, but the IDs will not match, # so this comparison is fine. if self._id_mapping.get(changes_container.getId()) == new_id: if changes_container_type == "quality_changes": each_extruder_stack.qualityChanges = each_changes_container elif changes_container_type == "definition_changes": each_extruder_stack.definitionChanges = each_changes_container if self._resolve_strategies["material"] == "new": for each_material in material_containers: old_material = global_stack.material # check if the old material container has been renamed to this material container ID # if the container hasn't been renamed, we do nothing. new_id = self._id_mapping.get(old_material.getId()) if new_id is None or new_id != each_material.getId(): continue if old_material.getId() in self._id_mapping: global_stack.material = each_material for each_extruder_stack in extruder_stacks: old_material = each_extruder_stack.material # check if the old material container has been renamed to this material container ID # if the container hasn't been renamed, we do nothing. new_id = self._id_mapping.get(old_material.getId()) if new_id is None or new_id != each_material.getId(): continue if old_material.getId() in self._id_mapping: each_extruder_stack.material = each_material if extruder_stacks: for stack in extruder_stacks: ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId()) else: # Machine has no extruders, but it needs to be registered with the extruder manager. ExtruderManager.getInstance().registerExtruder(None, global_stack.getId()) Logger.log("d", "Workspace loading is notifying rest of the code of changes...") if self._resolve_strategies["machine"] == "new": for stack in extruder_stacks: stack.setNextStack(global_stack) stack.containersChanged.emit(stack.getTop()) # Actually change the active machine. Application.getInstance().setGlobalContainerStack(global_stack) # Notify everything/one that is to notify about changes. global_stack.containersChanged.emit(global_stack.getTop()) # Load all the nodes / meshdata of the workspace nodes = self._3mf_mesh_reader.read(file_name) if nodes is None: nodes = [] return nodes
def preRead(self, file_name): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted: pass else: Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace") return WorkspaceReader.PreReadResult.failed # Check if there are any conflicts, so we can ask the user. archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)] self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None} machine_conflict = False quality_changes_conflict = False for container_stack_file in container_stack_files: container_id = self._stripFileToId(container_stack_file) stacks = self._container_registry.findContainerStacks(id=container_id) if stacks: # Check if there are any changes at all in any of the container stacks. id_list = self._getContainerIdListFromSerialized(archive.open(container_stack_file).read().decode("utf-8")) for index, container_id in enumerate(id_list): if stacks[0].getContainer(index).getId() != container_id: machine_conflict = True break material_conflict = False xml_material_profile = self._getXmlProfileClass() if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers(id=container_id) if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict material_conflict = True # Check if any quality_changes instance container is in conflict. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers(id = container_id) if quality_changes: # Check if there really is a conflict by comparing the values if quality_changes[0] != instance_container: quality_changes_conflict = True break try: archive.open("Cura/preferences.cfg") except KeyError: # If there is no preferences file, it's not a workspace, so notify user of failure. Logger.log("w", "File %s is not a valid workspace.", file_name) return WorkspaceReader.PreReadResult.failed if machine_conflict or quality_changes_conflict or material_conflict: # There is a conflict; User should choose to either update the existing data, add everything as new data or abort self._dialog.setMachineConflict(machine_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict) self._dialog.setMaterialConflict(material_conflict) self._dialog.show() # Block until the dialog is closed. self._dialog.waitForClose() if self._dialog.getResult() == {}: return WorkspaceReader.PreReadResult.cancelled self._resolve_strategies = self._dialog.getResult() return WorkspaceReader.PreReadResult.accepted
def preRead(self, file_name): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted: pass else: Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace") return WorkspaceReader.PreReadResult.failed machine_name = "" # Check if there are any conflicts, so we can ask the user. archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)] self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None} machine_conflict = False quality_changes_conflict = False for container_stack_file in container_stack_files: container_id = self._stripFileToId(container_stack_file) serialized = archive.open(container_stack_file).read().decode("utf-8") if machine_name == "": machine_name = self._getMachineNameFromSerializedStack(serialized) stacks = self._container_registry.findContainerStacks(id=container_id) if stacks: # Check if there are any changes at all in any of the container stacks. id_list = self._getContainerIdListFromSerialized(serialized) for index, container_id in enumerate(id_list): if stacks[0].getContainer(index).getId() != container_id: machine_conflict = True Job.yieldThread() material_labels = [] material_conflict = False xml_material_profile = self._getXmlProfileClass() if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers(id=container_id) material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8"))) if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict material_conflict = True Job.yieldThread() # Check if any quality_changes instance container is in conflict. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] quality_name = "" quality_type = "" num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": quality_name = instance_container.getName() num_settings_overriden_by_quality_changes += len(instance_container._instances) # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers(id = container_id) if quality_changes: # Check if there really is a conflict by comparing the values if quality_changes[0] != instance_container: quality_changes_conflict = True elif container_type == "quality": # If the quality name is not set (either by quality or changes, set it now) # Quality changes should always override this (as they are "on top") if quality_name == "": quality_name = instance_container.getName() quality_type = instance_container.getName() Job.yieldThread() num_visible_settings = 0 try: temp_preferences = Preferences() temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks. visible_settings_string = temp_preferences.getValue("general/visible_settings") if visible_settings_string is not None: num_visible_settings = len(visible_settings_string.split(";")) active_mode = temp_preferences.getValue("cura/active_mode") if not active_mode: active_mode = Preferences.getInstance().getValue("cura/active_mode") except KeyError: # If there is no preferences file, it's not a workspace, so notify user of failure. Logger.log("w", "File %s is not a valid workspace.", file_name) return WorkspaceReader.PreReadResult.failed # Show the dialog, informing the user what is about to happen. self._dialog.setMachineConflict(machine_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict) self._dialog.setMaterialConflict(material_conflict) self._dialog.setNumVisibleSettings(num_visible_settings) self._dialog.setQualityName(quality_name) self._dialog.setQualityType(quality_type) self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes) self._dialog.setActiveMode(active_mode) self._dialog.setMachineName(machine_name) self._dialog.setMaterialLabels(material_labels) self._dialog.setHasObjectsOnPlate(Application.getInstance().getPlatformActivity) self._dialog.show() # Block until the dialog is closed. self._dialog.waitForClose() if self._dialog.getResult() == {}: return WorkspaceReader.PreReadResult.cancelled self._resolve_strategies = self._dialog.getResult() return WorkspaceReader.PreReadResult.accepted
def read(self, file_name): archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its # parsing code. temp_preferences = Preferences() temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks. # Copy a number of settings from the temp preferences to the global global_preferences = Preferences.getInstance() visible_settings = temp_preferences.getValue("general/visible_settings") if visible_settings is None: Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged") else: global_preferences.setValue("general/visible_settings", visible_settings) categories_expanded = temp_preferences.getValue("cura/categories_expanded") if categories_expanded is None: Logger.log("w", "Workspace did not contain expanded categories. Leaving them unchanged") else: global_preferences.setValue("cura/categories_expanded", categories_expanded) Application.getInstance().expandedCategoriesChanged.emit() # Notify the GUI of the change self._id_mapping = {} # We don't add containers right away, but wait right until right before the stack serialization. # We do this so that if something goes wrong, it's easier to clean up. containers_to_add = [] global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names) global_stack = None extruder_stacks = [] extruder_stacks_added = [] container_stacks_added = [] containers_added = [] global_stack_id_original = self._stripFileToId(global_stack_file) global_stack_id_new = global_stack_id_original global_stack_need_rename = False extruder_stack_id_map = {} # new and old ExtruderStack IDs map if self._resolve_strategies["machine"] == "new": # We need a new id if the id already exists if self._container_registry.findContainerStacks(id = global_stack_id_original): global_stack_id_new = self.getNewId(global_stack_id_original) global_stack_need_rename = True for each_extruder_stack_file in extruder_stack_files: old_container_id = self._stripFileToId(each_extruder_stack_file) new_container_id = old_container_id if self._container_registry.findContainerStacks(id = old_container_id): # get a new name for this extruder new_container_id = self.getNewId(old_container_id) extruder_stack_id_map[old_container_id] = new_container_id # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few # TODO: cases that the container loaded is the same (most notable in materials & definitions). # TODO: It might be possible that we need to add smarter checking in the future. 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.findDefinitionContainers(id = container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8")) self._container_registry.addContainer(definition_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking materials...") material_containers = [] # 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers(id = container_id) if not materials: material_container = xml_material_profile(container_id) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) containers_to_add.append(material_container) else: material_container = materials[0] if not material_container.isReadOnly(): # Only create new materials if they are not read only. if self._resolve_strategies["material"] == "override": material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) elif self._resolve_strategies["material"] == "new": # Note that we *must* deserialize it with a new ID, as multiple containers will be # auto created & added. material_container = xml_material_profile(self.getNewId(container_id)) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) containers_to_add.append(material_container) material_containers.append(material_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking instance containers...") # Get quality_changes and user profiles saved in the workspace instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] user_instance_containers = [] quality_and_definition_changes_instance_containers = [] for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) serialized = archive.open(instance_container_file).read().decode("utf-8") # HACK! we ignore "quality" and "variant" instance containers! parser = configparser.ConfigParser() parser.read_string(serialized) if not parser.has_option("metadata", "type"): Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file) continue if parser.get("metadata", "type") in self._ignored_instance_container_types: continue instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize(serialized) container_type = instance_container.getMetaDataEntry("type") Job.yieldThread() # # IMPORTANT: # If an instance container (or maybe other type of container) exists, and user chooses "Create New", # we need to rename this container and all references to it, and changing those references are VERY # HARD. # if container_type in self._ignored_instance_container_types: # Ignore certain instance container types Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type) continue elif container_type == "user": # Check if quality changes already exists. user_containers = self._container_registry.findInstanceContainers(id = container_id) if not user_containers: containers_to_add.append(instance_container) else: if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None: instance_container = user_containers[0] instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) instance_container.setDirty(True) elif self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. old_extruder_id = instance_container.getMetaDataEntry("extruder", None) if old_extruder_id: new_extruder_id = extruder_stack_id_map[old_extruder_id] new_id = new_extruder_id + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry("extruder", new_extruder_id) containers_to_add.append(instance_container) machine_id = instance_container.getMetaDataEntry("machine", None) if machine_id: new_machine_id = self.getNewId(machine_id) new_id = new_machine_id + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry("machine", new_machine_id) containers_to_add.append(instance_container) user_instance_containers.append(instance_container) elif container_type in ("quality_changes", "definition_changes"): # Check if quality changes already exists. changes_containers = self._container_registry.findInstanceContainers(id = container_id) if not changes_containers: # no existing containers with the same ID, so we can safely add the new one containers_to_add.append(instance_container) else: # we have found existing container with the same ID, so we need to resolve according to the # selected strategy. if self._resolve_strategies[container_type] == "override": instance_container = changes_containers[0] instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) instance_container.setDirty(True) elif self._resolve_strategies[container_type] == "new": # TODO: how should we handle the case "new" for quality_changes and definition_changes? instance_container.setName(self._container_registry.uniqueName(instance_container.getName())) new_changes_container_id = self.getNewId(instance_container.getId()) instance_container._id = new_changes_container_id # TODO: we don't know the following is correct or not, need to verify # AND REFACTOR!!! if self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. old_extruder_id = instance_container.getMetaDataEntry("extruder", None) if old_extruder_id: new_extruder_id = extruder_stack_id_map[old_extruder_id] instance_container.setMetaDataEntry("extruder", new_extruder_id) machine_id = instance_container.getMetaDataEntry("machine", None) if machine_id: new_machine_id = self.getNewId(machine_id) instance_container.setMetaDataEntry("machine", new_machine_id) containers_to_add.append(instance_container) elif self._resolve_strategies[container_type] is None: # The ID already exists, but nothing in the values changed, so do nothing. pass quality_and_definition_changes_instance_containers.append(instance_container) else: existing_container = self._container_registry.findInstanceContainers(id = container_id) if not existing_container: containers_to_add.append(instance_container) if global_stack_need_rename: if instance_container.getMetaDataEntry("machine"): instance_container.setMetaDataEntry("machine", global_stack_id_new) # Add all the containers right before we try to add / serialize the stack for container in containers_to_add: self._container_registry.addContainer(container) container.setDirty(True) containers_added.append(container) # Get the stack(s) saved in the workspace. Logger.log("d", "Workspace loading is checking stacks containers...") # -- # load global stack file try: # Check if a stack by this ID already exists; container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original) if container_stacks: stack = container_stacks[0] if self._resolve_strategies["machine"] == "override": # TODO: HACK # There is a machine, check if it has authentication data. If so, keep that data. network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id") network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key") container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8")) if network_authentication_id: container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id) if network_authentication_key: container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key) elif self._resolve_strategies["machine"] == "new": stack = GlobalStack(global_stack_id_new) stack.deserialize(archive.open(global_stack_file).read().decode("utf-8")) # Ensure a unique ID and name stack._id = global_stack_id_new # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the # bound machine also needs to change. if stack.getMetaDataEntry("machine", None): stack.setMetaDataEntry("machine", global_stack_id_new) # Only machines need a new name, stacks may be non-unique stack.setName(self._container_registry.uniqueName(stack.getName())) container_stacks_added.append(stack) self._container_registry.addContainer(stack) else: Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) else: # no existing container stack, so we create a new one stack = GlobalStack(global_stack_id_new) # Deserialize stack by converting read data from bytes to string stack.deserialize(archive.open(global_stack_file).read().decode("utf-8")) container_stacks_added.append(stack) self._container_registry.addContainer(stack) containers_added.append(stack) global_stack = stack Job.yieldThread() except: Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") # Something went really wrong. Try to remove any data that we added. for container in containers_added: self._container_registry.removeContainer(container.getId()) return # -- # load extruder stack files try: for index, extruder_stack_file in enumerate(extruder_stack_files): container_id = self._stripFileToId(extruder_stack_file) extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8") container_stacks = self._container_registry.findContainerStacks(id = container_id) if container_stacks: # this container stack already exists, try to resolve stack = container_stacks[0] if self._resolve_strategies["machine"] == "override": # NOTE: This is the same code as those in the lower part # deserialize new extruder stack over the current ones stack = self._overrideExtruderStack(global_stack, extruder_file_content) elif self._resolve_strategies["machine"] == "new": # create a new extruder stack from this one new_id = extruder_stack_id_map[container_id] stack = ExtruderStack(new_id) # HACK: the global stack can have a new name, so we need to make sure that this extruder stack # references to the new name instead of the old one. Normally, this can be done after # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize() # also does addExtruder() to its machine stack, so we have to make sure that it's pointing # to the right machine BEFORE deserialization. extruder_config = configparser.ConfigParser() extruder_config.read_string(extruder_file_content) extruder_config.set("metadata", "machine", global_stack_id_new) tmp_string_io = io.StringIO() extruder_config.write(tmp_string_io) extruder_file_content = tmp_string_io.getvalue() stack.deserialize(extruder_file_content) # Ensure a unique ID and name stack._id = new_id self._container_registry.addContainer(stack) extruder_stacks_added.append(stack) containers_added.append(stack) else: # No extruder stack with the same ID can be found if self._resolve_strategies["machine"] == "override": # deserialize new extruder stack over the current ones stack = self._overrideExtruderStack(global_stack, extruder_file_content) elif self._resolve_strategies["machine"] == "new": # container not found, create a new one stack = ExtruderStack(container_id) # HACK: the global stack can have a new name, so we need to make sure that this extruder stack # references to the new name instead of the old one. Normally, this can be done after # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize() # also does addExtruder() to its machine stack, so we have to make sure that it's pointing # to the right machine BEFORE deserialization. extruder_config = configparser.ConfigParser() extruder_config.read_string(extruder_file_content) extruder_config.set("metadata", "machine", global_stack_id_new) tmp_string_io = io.StringIO() extruder_config.write(tmp_string_io) extruder_file_content = tmp_string_io.getvalue() stack.deserialize(extruder_file_content) self._container_registry.addContainer(stack) extruder_stacks_added.append(stack) containers_added.append(stack) else: Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"])) extruder_stacks.append(stack) except: Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") # Something went really wrong. Try to remove any data that we added. for container in containers_added: self._container_registry.removeContainer(container.getId()) return # # Replacing the old containers if resolve is "new". # When resolve is "new", some containers will get renamed, so all the other containers that reference to those # MUST get updated too. # if self._resolve_strategies["machine"] == "new": # A new machine was made, but it was serialized with the wrong user container. Fix that now. for container in user_instance_containers: # replacing the container ID for user instance containers for the extruders extruder_id = container.getMetaDataEntry("extruder", None) if extruder_id: for extruder in extruder_stacks: if extruder.getId() == extruder_id: extruder.userChanges = container continue # replacing the container ID for user instance containers for the machine machine_id = container.getMetaDataEntry("machine", None) if machine_id: if global_stack.getId() == machine_id: global_stack.userChanges = container continue for changes_container_type in ("quality_changes", "definition_changes"): if self._resolve_strategies[changes_container_type] == "new": # Quality changes needs to get a new ID, added to registry and to the right stacks for each_changes_container in quality_and_definition_changes_instance_containers: # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the # instance container loading part. new_id = each_changes_container.getId() # Find the old (current) changes container in the global stack if changes_container_type == "quality_changes": old_container = global_stack.qualityChanges elif changes_container_type == "definition_changes": old_container = global_stack.definitionChanges # sanity checks # NOTE: The following cases SHOULD NOT happen!!!! if not old_container: Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!", changes_container_type, global_stack.getId()) # Replace the quality/definition changes container if it's in the GlobalStack # NOTE: we can get an empty container here, but the IDs will not match, # so this comparison is fine. if self._id_mapping.get(old_container.getId()) == new_id: if changes_container_type == "quality_changes": global_stack.qualityChanges = each_changes_container elif changes_container_type == "definition_changes": global_stack.definitionChanges = each_changes_container continue # Replace the quality/definition changes container if it's in one of the ExtruderStacks for each_extruder_stack in extruder_stacks: changes_container = None if changes_container_type == "quality_changes": changes_container = each_extruder_stack.qualityChanges elif changes_container_type == "definition_changes": changes_container = each_extruder_stack.definitionChanges # sanity checks # NOTE: The following cases SHOULD NOT happen!!!! if not changes_container: Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!", changes_container_type, each_extruder_stack.getId()) # NOTE: we can get an empty container here, but the IDs will not match, # so this comparison is fine. if self._id_mapping.get(changes_container.getId()) == new_id: if changes_container_type == "quality_changes": each_extruder_stack.qualityChanges = each_changes_container elif changes_container_type == "definition_changes": each_extruder_stack.definitionChanges = each_changes_container if self._resolve_strategies["material"] == "new": for each_material in material_containers: old_material = global_stack.material # check if the old material container has been renamed to this material container ID # if the container hasn't been renamed, we do nothing. new_id = self._id_mapping.get(old_material.getId()) if new_id is None or new_id != each_material.getId(): continue if old_material.getId() in self._id_mapping: global_stack.material = each_material for each_extruder_stack in extruder_stacks: old_material = each_extruder_stack.material # check if the old material container has been renamed to this material container ID # if the container hasn't been renamed, we do nothing. new_id = self._id_mapping.get(old_material.getId()) if new_id is None or new_id != each_material.getId(): continue if old_material.getId() in self._id_mapping: each_extruder_stack.material = each_material if extruder_stacks: for stack in extruder_stacks: ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId()) else: # Machine has no extruders, but it needs to be registered with the extruder manager. ExtruderManager.getInstance().registerExtruder(None, global_stack.getId()) Logger.log("d", "Workspace loading is notifying rest of the code of changes...") if self._resolve_strategies["machine"] == "new": for stack in extruder_stacks: stack.setNextStack(global_stack) stack.containersChanged.emit(stack.getTop()) # Actually change the active machine. Application.getInstance().setGlobalContainerStack(global_stack) # Notify everything/one that is to notify about changes. global_stack.containersChanged.emit(global_stack.getTop()) # Load all the nodes / meshdata of the workspace nodes = self._3mf_mesh_reader.read(file_name) if nodes is None: nodes = [] return nodes
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", -1, "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 profile.setDefinition(current_printer_definition.getId()) 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) #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) #Only the extruder stack has an extruder metadata entry. profile.addMetaDataEntry( "extruder", ExtruderManager.getInstance().getActiveExtruderStack().definition. getId()) #Split all settings into per-extruder and global settings. for setting_key in profile.getAllKeys(): settable_per_extruder = global_container_stack.getProperty( setting_key, "settable_per_extruder") if settable_per_extruder: global_profile.removeInstance(setting_key) else: profile.removeInstance(setting_key) return [global_profile, profile]
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 preRead(self, file_name): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler( ).getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead( file_name) == WorkspaceReader.PreReadResult.accepted: pass else: Logger.log( "w", "Could not find reader that was able to read the scene data for 3MF workspace" ) return WorkspaceReader.PreReadResult.failed # Check if there are any conflicts, so we can ask the user. archive = zipfile.ZipFile(file_name, "r") cura_file_names = [ name for name in archive.namelist() if name.startswith("Cura/") ] container_stack_files = [ name for name in cura_file_names if name.endswith(self._container_stack_suffix) ] self._resolve_strategies = { "machine": None, "quality_changes": None, "material": None } machine_conflict = False quality_changes_conflict = False for container_stack_file in container_stack_files: container_id = self._stripFileToId(container_stack_file) stacks = self._container_registry.findContainerStacks( id=container_id) if stacks: # Check if there are any changes at all in any of the container stacks. id_list = self._getContainerIdListFromSerialized( archive.open(container_stack_file).read().decode("utf-8")) for index, container_id in enumerate(id_list): if stacks[0].getContainer(index).getId() != container_id: machine_conflict = True break Job.yieldThread() material_conflict = False xml_material_profile = self._getXmlProfileClass() if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer( xml_material_profile).preferredSuffix 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers( id=container_id) if materials and not materials[0].isReadOnly( ): # Only non readonly materials can be in conflict material_conflict = True Job.yieldThread() # Check if any quality_changes instance container is in conflict. instance_container_files = [ name for name in cura_file_names if name.endswith(self._instance_container_suffix) ] for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize( archive.open(instance_container_file).read().decode("utf-8")) container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers( id=container_id) if quality_changes: # Check if there really is a conflict by comparing the values if quality_changes[0] != instance_container: quality_changes_conflict = True break Job.yieldThread() try: archive.open("Cura/preferences.cfg") except KeyError: # If there is no preferences file, it's not a workspace, so notify user of failure. Logger.log("w", "File %s is not a valid workspace.", file_name) return WorkspaceReader.PreReadResult.failed if machine_conflict or quality_changes_conflict or material_conflict: # There is a conflict; User should choose to either update the existing data, add everything as new data or abort self._dialog.setMachineConflict(machine_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict) self._dialog.setMaterialConflict(material_conflict) self._dialog.show() # Block until the dialog is closed. self._dialog.waitForClose() if self._dialog.getResult() == {}: return WorkspaceReader.PreReadResult.cancelled self._resolve_strategies = self._dialog.getResult() return WorkspaceReader.PreReadResult.accepted
def preRead(self, file_name, show_dialog=True, *args, **kwargs): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted: pass else: Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace") return WorkspaceReader.PreReadResult.failed machine_name = "" machine_type = "" variant_type_name = i18n_catalog.i18nc("@label", "Nozzle") # Check if there are any conflicts, so we can ask the user. archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] # A few lists of containers in this project files. # When loading the global stack file, it may be associated with those containers, which may or may not be # in Cura already, so we need to provide them as alternative search lists. definition_container_list = [] instance_container_list = [] material_container_list = [] # # Read definition containers # machine_definition_container_count = 0 extruder_definition_container_count = 0 definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] for each_definition_container_file in definition_container_files: container_id = self._stripFileToId(each_definition_container_file) definitions = self._container_registry.findDefinitionContainers(id=container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8")) else: definition_container = definitions[0] definition_container_list.append(definition_container) definition_container_type = definition_container.getMetaDataEntry("type") if definition_container_type == "machine": machine_type = definition_container.getName() variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name) machine_definition_container_count += 1 elif definition_container_type == "extruder": extruder_definition_container_count += 1 else: Logger.log("w", "Unknown definition container type %s for %s", definition_container_type, each_definition_container_file) Job.yieldThread() # sanity check if machine_definition_container_count != 1: msg = "Expecting one machine definition container but got %s" % machine_definition_container_count Logger.log("e", msg) raise RuntimeError(msg) material_labels = [] material_conflict = False xml_material_profile = self._getXmlProfileClass() if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix 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: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers(id=container_id) material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8"))) if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict material_conflict = True Job.yieldThread() # Check if any quality_changes instance container is in conflict. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] quality_name = "" quality_type = "" num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes num_user_settings = 0 quality_changes_conflict = False definition_changes_conflict = False for each_instance_container_file in instance_container_files: container_id = self._stripFileToId(each_instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8")) instance_container_list.append(instance_container) container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": quality_name = instance_container.getName() num_settings_overriden_by_quality_changes += len(instance_container._instances) # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers(id = container_id) if quality_changes: # Check if there really is a conflict by comparing the values if quality_changes[0] != instance_container: quality_changes_conflict = True elif container_type == "definition_changes": definition_name = instance_container.getName() num_settings_overriden_by_definition_changes += len(instance_container._instances) definition_changes = self._container_registry.findDefinitionContainers(id = container_id) if definition_changes: if definition_changes[0] != instance_container: definition_changes_conflict = True elif container_type == "user": num_user_settings += len(instance_container._instances) elif container_type in self._ignored_instance_container_types: # Ignore certain instance container types Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type) continue Job.yieldThread() # Load ContainerStack files and ExtruderStack files global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles( file_name, cura_file_names) self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None} machine_conflict = False for container_stack_file in [global_stack_file] + extruder_stack_files: container_id = self._stripFileToId(container_stack_file) serialized = archive.open(container_stack_file).read().decode("utf-8") if machine_name == "": machine_name = self._getMachineNameFromSerializedStack(serialized) stacks = self._container_registry.findContainerStacks(id = container_id) if stacks: # Check if there are any changes at all in any of the container stacks. id_list = self._getContainerIdListFromSerialized(serialized) for index, container_id in enumerate(id_list): if stacks[0].getContainer(index).getId() != container_id: machine_conflict = True Job.yieldThread() num_visible_settings = 0 try: temp_preferences = Preferences() temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks. visible_settings_string = temp_preferences.getValue("general/visible_settings") if visible_settings_string is not None: num_visible_settings = len(visible_settings_string.split(";")) active_mode = temp_preferences.getValue("cura/active_mode") if not active_mode: active_mode = Preferences.getInstance().getValue("cura/active_mode") except KeyError: # If there is no preferences file, it's not a workspace, so notify user of failure. Logger.log("w", "File %s is not a valid workspace.", file_name) return WorkspaceReader.PreReadResult.failed # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog. if not show_dialog: return WorkspaceReader.PreReadResult.accepted # prepare data for the dialog num_extruders = extruder_definition_container_count if num_extruders == 0: num_extruders = 1 # No extruder stacks found, which means there is one extruder extruders = num_extruders * [""] # Show the dialog, informing the user what is about to happen. self._dialog.setMachineConflict(machine_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict) self._dialog.setDefinitionChangesConflict(definition_changes_conflict) self._dialog.setMaterialConflict(material_conflict) self._dialog.setNumVisibleSettings(num_visible_settings) self._dialog.setQualityName(quality_name) self._dialog.setQualityType(quality_type) self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes) self._dialog.setNumUserSettings(num_user_settings) self._dialog.setActiveMode(active_mode) self._dialog.setMachineName(machine_name) self._dialog.setMaterialLabels(material_labels) self._dialog.setMachineType(machine_type) self._dialog.setExtruders(extruders) self._dialog.setVariantType(variant_type_name) self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity) self._dialog.show() # Block until the dialog is closed. self._dialog.waitForClose() if self._dialog.getResult() == {}: return WorkspaceReader.PreReadResult.cancelled self._resolve_strategies = self._dialog.getResult() # # There can be 3 resolve strategies coming from the dialog: # - new: create a new container # - override: override the existing container # - None: There is no conflict, which means containers with the same IDs may or may not be there already. # If they are there, there is no conflict between the them. # In this case, you can either create a new one, or safely override the existing one. # # Default values for k, v in self._resolve_strategies.items(): if v is None: self._resolve_strategies[k] = "new" return WorkspaceReader.PreReadResult.accepted