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, 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 _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]: profile.setDirty(True) # Ensure the profiles are correctly saved new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile")) profile._id = new_id profile.setName(new_name) if "type" in profile.getMetaData(): profile.setMetaDataEntry("type", "quality_changes") else: profile.addMetaDataEntry("type", "quality_changes") quality_type = profile.getMetaDataEntry("quality_type") if not quality_type: return catalog.i18nc("@info:status", "Profile is missing a quality type.") quality_type_criteria = {"quality_type": quality_type} if self._machineHasOwnQualities(): profile.setDefinition(self._activeQualityDefinition().getId()) if self._machineHasOwnMaterials(): active_material_id = self._activeMaterialId() if active_material_id and active_material_id != "empty": # only update if there is an active material profile.addMetaDataEntry("material", active_material_id) quality_type_criteria["material"] = active_material_id quality_type_criteria["definition"] = profile.getDefinition().getId() else: profile.setDefinition(fdmprinter) quality_type_criteria["definition"] = "fdmprinter" machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() del quality_type_criteria["definition"] # materials = None if "material" in quality_type_criteria: # materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"]) del quality_type_criteria["material"] # Do not filter quality containers here with materials because we are trying to import a profile, so it should # NOT be restricted by the active materials on the current machine. materials = None # Check to make sure the imported profile actually makes sense in context of the current configuration. # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as # successfully imported but then fail to show up. from cura.QualityManager import QualityManager qualities = QualityManager.getInstance()._getFilteredContainersForStack(machine_definition, materials, **quality_type_criteria) if not qualities: return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type) ContainerRegistry.getInstance().addContainer(profile) return None
def _createDefinitionChangesContainer(self, container_stack, container_name, container_index = None): definition_changes_container = InstanceContainer(container_name) definition = container_stack.getBottom() definition_changes_container.setDefinition(definition) definition_changes_container.addMetaDataEntry("type", "definition_changes") definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) self._container_registry.addContainer(definition_changes_container) container_stack.definitionChanges = definition_changes_container return definition_changes_container
def _createDefinitionChangesContainer(self, global_container_stack, container_index = None): definition_changes_container = InstanceContainer(global_container_stack.getName() + "_settings") definition = global_container_stack.getBottom() definition_changes_container.setDefinition(definition) definition_changes_container.addMetaDataEntry("type", "definition_changes") self._container_registry.addContainer(definition_changes_container) # Insert definition_changes between the definition and the variant global_container_stack.insertContainer(-1, definition_changes_container) return definition_changes_container
def _loadProfile(self, serialized, profile_id): # Create an empty profile. profile = InstanceContainer(profile_id) profile.addMetaDataEntry("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 createDefinitionChangesContainer(cls, container_stack, container_name): from cura.CuraApplication import CuraApplication unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name) definition_changes_container = InstanceContainer(unique_container_name) definition_changes_container.setDefinition(container_stack.getBottom().getId()) definition_changes_container.addMetaDataEntry("type", "definition_changes") definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) ContainerRegistry.getInstance().addContainer(definition_changes_container) container_stack.definitionChanges = definition_changes_container return definition_changes_container
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 __init__(self): super().__init__() self._stack = PerObjectContainerStack(container_id = "per_object_stack_" + str(id(self))) self._stack.setDirty(False) # This stack does not need to be saved. user_container = InstanceContainer(container_id = self._generateUniqueName()) user_container.addMetaDataEntry("type", "user") self._stack.userChanges = user_container self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() self._is_non_printing_mesh = False self._stack.propertyChanged.connect(self._onSettingChanged) Application.getInstance().getContainerRegistry().addContainer(self._stack) Application.getInstance().globalContainerStackChanged.connect(self._updateNextStack) self.activeExtruderChanged.connect(self._updateNextStack) self._updateNextStack()
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 _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]: profile.setReadOnly(False) profile.setDirty(True) # Ensure the profiles are correctly saved new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile")) profile._id = new_id profile.setName(new_name) if "type" in profile.getMetaData(): profile.setMetaDataEntry("type", "quality_changes") else: profile.addMetaDataEntry("type", "quality_changes") quality_type = profile.getMetaDataEntry("quality_type") if not quality_type: return catalog.i18nc("@info:status", "Profile is missing a quality type.") quality_type_criteria = {"quality_type": quality_type} if self._machineHasOwnQualities(): profile.setDefinition(self._activeQualityDefinition()) if self._machineHasOwnMaterials(): active_material_id = self._activeMaterialId() if active_material_id: # only update if there is an active material profile.addMetaDataEntry("material", active_material_id) quality_type_criteria["material"] = active_material_id quality_type_criteria["definition"] = profile.getDefinition().getId() else: profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0]) quality_type_criteria["definition"] = "fdmprinter" # Check to make sure the imported profile actually makes sense in context of the current configuration. # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as # successfully imported but then fail to show up. qualities = self.findInstanceContainers(**quality_type_criteria) if not qualities: return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type) ContainerRegistry.getInstance().addContainer(profile) return None
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainer, **kwargs) -> GlobalStack: stack = GlobalStack(new_stack_id) stack.setDefinition(definition) user_container = InstanceContainer(new_stack_id + "_user") user_container.addMetaDataEntry("type", "user") user_container.addMetaDataEntry("machine", new_stack_id) from cura.CuraApplication import CuraApplication user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_container.setDefinition(definition) stack.setUserChanges(user_container) # Important! The order here matters, because that allows the stack to # assume the material and variant have already been set. if "definition_changes" in kwargs: stack.setDefinitionChangesById(kwargs["definition_changes"]) if "variant" in kwargs: stack.setVariantById(kwargs["variant"]) if "material" in kwargs: stack.setMaterialById(kwargs["material"]) if "quality" in kwargs: stack.setQualityById(kwargs["quality"]) if "quality_changes" in kwargs: stack.setQualityChangesById(kwargs["quality_changes"]) registry = ContainerRegistry.getInstance() registry.addContainer(stack) registry.addContainer(user_container) return stack
def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str, machine_definition_id: str) -> Optional[str]: profile.setDirty(True) # Ensure the profiles are correctly saved new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile")) profile.setMetaDataEntry("id", new_id) profile.setName(new_name) # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile # It also solves an issue with importing profiles from G-Codes profile.setMetaDataEntry("id", new_id) profile.setMetaDataEntry("definition", machine_definition_id) if "type" in profile.getMetaData(): profile.setMetaDataEntry("type", "quality_changes") else: profile.addMetaDataEntry("type", "quality_changes") quality_type = profile.getMetaDataEntry("quality_type") if not quality_type: return catalog.i18nc("@info:status", "Profile is missing a quality type.") global_stack = Application.getInstance().getGlobalContainerStack() definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition) profile.setDefinition(definition_id) # Check to make sure the imported profile actually makes sense in context of the current configuration. # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as # successfully imported but then fail to show up. quality_manager = CuraApplication.getInstance()._quality_manager quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack) if quality_type not in quality_group_dict: return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type) ContainerRegistry.getInstance().addContainer(profile) return None
def _createQualityChanges(self, quality_container, new_name, machine_definition, extruder_id): base_id = machine_definition.getId() if extruder_id is None else extruder_id # Create a new quality_changes container for the quality. quality_changes = InstanceContainer(self._createUniqueId(base_id, new_name)) quality_changes.setName(new_name) quality_changes.addMetaDataEntry("type", "quality_changes") quality_changes.addMetaDataEntry("quality_type", quality_container.getMetaDataEntry("quality_type")) # If we are creating a container for an extruder, ensure we add that to the container if extruder_id is not None: quality_changes.addMetaDataEntry("extruder", extruder_id) # If the machine specifies qualities should be filtered, ensure we match the current criteria. if not machine_definition.getMetaDataEntry("has_machine_quality"): quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0]) else: quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition)) from cura.CuraApplication import CuraApplication quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) return quality_changes
def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str, is_global_stack: bool) -> "InstanceContainer": from cura.CuraApplication import CuraApplication unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name) container = InstanceContainer(unique_container_name) container.setDefinition(definition_id) container.addMetaDataEntry("type", "user") container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) metadata_key_to_add = "machine" if is_global_stack else "extruder" container.addMetaDataEntry(metadata_key_to_add, stack_id) return container
def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, machine_definition_id: str, **kwargs) -> ExtruderStack: stack = ExtruderStack(new_stack_id) stack.setName(definition.getName()) stack.setDefinition(definition) stack.addMetaDataEntry("position", definition.getMetaDataEntry("position")) if "next_stack" in kwargs: # Add stacks before containers are added, since they may trigger a setting update. stack.setNextStack(kwargs["next_stack"]) user_container = InstanceContainer(new_stack_id + "_user") user_container.addMetaDataEntry("type", "user") user_container.addMetaDataEntry("extruder", new_stack_id) from cura.CuraApplication import CuraApplication user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_container.setDefinition(machine_definition_id) stack.setUserChanges(user_container) # Important! The order here matters, because that allows the stack to # assume the material and variant have already been set. if "definition_changes" in kwargs: stack.setDefinitionChangesById(kwargs["definition_changes"]) else: stack.setDefinitionChanges(cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")) if "variant" in kwargs: stack.setVariantById(kwargs["variant"]) if "material" in kwargs: stack.setMaterialById(kwargs["material"]) if "quality" in kwargs: stack.setQualityById(kwargs["quality"]) if "quality_changes" in kwargs: stack.setQualityChangesById(kwargs["quality_changes"]) # Only add the created containers to the registry after we have set all the other # properties. This makes the create operation more transactional, since any problems # setting properties will not result in incomplete containers being added. registry = ContainerRegistry.getInstance() registry.addContainer(stack) registry.addContainer(user_container) return stack
def addExtruderStackForSingleExtrusionMachine( self, machine, extruder_id, new_global_quality_changes=None, create_new_ids=True): new_extruder_id = extruder_id application = CuraApplication.getInstance() extruder_definitions = self.findDefinitionContainers( id=new_extruder_id) if not extruder_definitions: Logger.log("w", "Could not find definition containers for extruder %s", new_extruder_id) return extruder_definition = extruder_definitions[0] unique_name = self.uniqueName( machine.getName() + " " + new_extruder_id ) if create_new_ids else machine.getName() + " " + new_extruder_id extruder_stack = ExtruderStack.ExtruderStack(unique_name, parent=machine) extruder_stack.setName(extruder_definition.getName()) extruder_stack.setDefinition(extruder_definition) extruder_stack.addMetaDataEntry( "position", extruder_definition.getMetaDataEntry("position")) # create a new definition_changes container for the extruder stack definition_changes_id = self.uniqueName( extruder_stack.getId() + "_settings" ) if create_new_ids else extruder_stack.getId() + "_settings" definition_changes_name = definition_changes_id definition_changes = InstanceContainer(definition_changes_id, parent=application) definition_changes.setName(definition_changes_name) definition_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) definition_changes.addMetaDataEntry("type", "definition_changes") definition_changes.addMetaDataEntry("definition", extruder_definition.getId()) # move definition_changes settings if exist for setting_key in definition_changes.getAllKeys(): if machine.definition.getProperty(setting_key, "settable_per_extruder"): setting_value = machine.definitionChanges.getProperty( setting_key, "value") if setting_value is not None: # move it to the extruder stack's definition_changes setting_definition = machine.getSettingDefinition( setting_key) new_instance = SettingInstance(setting_definition, definition_changes) new_instance.setProperty("value", setting_value) new_instance.resetState( ) # Ensure that the state is not seen as a user state. definition_changes.addInstance(new_instance) definition_changes.setDirty(True) machine.definitionChanges.removeInstance( setting_key, postpone_emit=True) self.addContainer(definition_changes) extruder_stack.setDefinitionChanges(definition_changes) # create empty user changes container otherwise user_container_id = self.uniqueName( extruder_stack.getId() + "_user") if create_new_ids else extruder_stack.getId() + "_user" user_container_name = user_container_id user_container = InstanceContainer(user_container_id, parent=application) user_container.setName(user_container_name) user_container.addMetaDataEntry("type", "user") user_container.addMetaDataEntry("machine", machine.getId()) user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_container.setDefinition(machine.definition.getId()) user_container.setMetaDataEntry( "position", extruder_stack.getMetaDataEntry("position")) if machine.userChanges: # for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes # container to the extruder stack. for user_setting_key in machine.userChanges.getAllKeys(): settable_per_extruder = machine.getProperty( user_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = machine.getProperty( user_setting_key, "value") setting_definition = machine.getSettingDefinition( user_setting_key) new_instance = SettingInstance(setting_definition, definition_changes) new_instance.setProperty("value", setting_value) new_instance.resetState( ) # Ensure that the state is not seen as a user state. user_container.addInstance(new_instance) user_container.setDirty(True) machine.userChanges.removeInstance(user_setting_key, postpone_emit=True) self.addContainer(user_container) extruder_stack.setUserChanges(user_container) empty_variant = application.empty_variant_container empty_material = application.empty_material_container empty_quality = application.empty_quality_container if machine.variant.getId() not in ("empty", "empty_variant"): variant = machine.variant else: variant = empty_variant extruder_stack.variant = variant if machine.material.getId() not in ("empty", "empty_material"): material = machine.material else: material = empty_material extruder_stack.material = material if machine.quality.getId() not in ("empty", "empty_quality"): quality = machine.quality else: quality = empty_quality extruder_stack.quality = quality machine_quality_changes = machine.qualityChanges if new_global_quality_changes is not None: machine_quality_changes = new_global_quality_changes if machine_quality_changes.getId() not in ("empty", "empty_quality_changes"): extruder_quality_changes_container = self.findInstanceContainers( name=machine_quality_changes.getName(), extruder=extruder_id) if extruder_quality_changes_container: extruder_quality_changes_container = extruder_quality_changes_container[ 0] quality_changes_id = extruder_quality_changes_container.getId() extruder_stack.qualityChanges = self.findInstanceContainers( id=quality_changes_id)[0] else: # Some extruder quality_changes containers can be created at runtime as files in the qualities # folder. Those files won't be loaded in the registry immediately. So we also need to search # the folder to see if the quality_changes exists. extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder( machine_quality_changes.getName()) if extruder_quality_changes_container: quality_changes_id = extruder_quality_changes_container.getId( ) extruder_quality_changes_container.addMetaDataEntry( "position", extruder_definition.getMetaDataEntry("position")) extruder_stack.qualityChanges = self.findInstanceContainers( id=quality_changes_id)[0] else: # if we still cannot find a quality changes container for the extruder, create a new one container_name = machine_quality_changes.getName() container_id = self.uniqueName(extruder_stack.getId() + "_qc_" + container_name) extruder_quality_changes_container = InstanceContainer( container_id, parent=application) extruder_quality_changes_container.setName(container_name) extruder_quality_changes_container.addMetaDataEntry( "type", "quality_changes") extruder_quality_changes_container.addMetaDataEntry( "setting_version", CuraApplication.SettingVersion) extruder_quality_changes_container.addMetaDataEntry( "position", extruder_definition.getMetaDataEntry("position")) extruder_quality_changes_container.addMetaDataEntry( "quality_type", machine_quality_changes.getMetaDataEntry( "quality_type")) extruder_quality_changes_container.setDefinition( machine_quality_changes.getDefinition().getId()) self.addContainer(extruder_quality_changes_container) extruder_stack.qualityChanges = extruder_quality_changes_container if not extruder_quality_changes_container: Logger.log( "w", "Could not find quality_changes named [%s] for extruder [%s]", machine_quality_changes.getName(), extruder_stack.getId()) else: # move all per-extruder settings to the extruder's quality changes for qc_setting_key in machine_quality_changes.getAllKeys(): settable_per_extruder = machine.getProperty( qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = machine_quality_changes.getProperty( qc_setting_key, "value") setting_definition = machine.getSettingDefinition( qc_setting_key) new_instance = SettingInstance(setting_definition, definition_changes) new_instance.setProperty("value", setting_value) new_instance.resetState( ) # Ensure that the state is not seen as a user state. extruder_quality_changes_container.addInstance( new_instance) extruder_quality_changes_container.setDirty(True) machine_quality_changes.removeInstance( qc_setting_key, postpone_emit=True) else: extruder_stack.qualityChanges = self.findInstanceContainers( id="empty_quality_changes")[0] self.addContainer(extruder_stack) # Also need to fix the other qualities that are suitable for this machine. Those quality changes may still have # per-extruder settings in the container for the machine instead of the extruder. if machine_quality_changes.getId() not in ("empty", "empty_quality_changes"): quality_changes_machine_definition_id = machine_quality_changes.getDefinition( ).getId() else: whole_machine_definition = machine.definition machine_entry = machine.definition.getMetaDataEntry("machine") if machine_entry is not None: container_registry = ContainerRegistry.getInstance() whole_machine_definition = container_registry.findDefinitionContainers( id=machine_entry)[0] quality_changes_machine_definition_id = "fdmprinter" if whole_machine_definition.getMetaDataEntry( "has_machine_quality"): quality_changes_machine_definition_id = machine.definition.getMetaDataEntry( "quality_definition", whole_machine_definition.getId()) qcs = self.findInstanceContainers( type="quality_changes", definition=quality_changes_machine_definition_id) qc_groups = {} # map of qc names -> qc containers for qc in qcs: qc_name = qc.getName() if qc_name not in qc_groups: qc_groups[qc_name] = [] qc_groups[qc_name].append(qc) # try to find from the quality changes cura directory too quality_changes_container = self._findQualityChangesContainerInCuraFolder( machine_quality_changes.getName()) if quality_changes_container: qc_groups[qc_name].append(quality_changes_container) for qc_name, qc_list in qc_groups.items(): qc_dict = {"global": None, "extruders": []} for qc in qc_list: extruder_position = qc.getMetaDataEntry("position") if extruder_position is not None: qc_dict["extruders"].append(qc) else: qc_dict["global"] = qc if qc_dict["global"] is not None and len( qc_dict["extruders"]) == 1: # move per-extruder settings for qc_setting_key in qc_dict["global"].getAllKeys(): settable_per_extruder = machine.getProperty( qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = qc_dict["global"].getProperty( qc_setting_key, "value") setting_definition = machine.getSettingDefinition( qc_setting_key) new_instance = SettingInstance(setting_definition, definition_changes) new_instance.setProperty("value", setting_value) new_instance.resetState( ) # Ensure that the state is not seen as a user state. qc_dict["extruders"][0].addInstance(new_instance) qc_dict["extruders"][0].setDirty(True) qc_dict["global"].removeInstance(qc_setting_key, postpone_emit=True) # Set next stack at the end extruder_stack.setNextStack(machine) return extruder_stack
class Script: def __init__(self): super().__init__() self._settings = None self._stack = None setting_data = self.getSettingData() self._stack = ContainerStack(stack_id = str(id(self))) self._stack.setDirty(False) # This stack does not need to be saved. ## Check if the definition of this script already exists. If not, add it to the registry. if "key" in setting_data: definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"]) if definitions: # Definition was found self._definition = definitions[0] else: self._definition = DefinitionContainer(setting_data["key"]) self._definition.deserialize(json.dumps(setting_data)) ContainerRegistry.getInstance().addContainer(self._definition) self._stack.addContainer(self._definition) self._instance = InstanceContainer(container_id="ScriptInstanceContainer") self._instance.setDefinition(self._definition.getId()) self._instance.addMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0)) self._stack.addContainer(self._instance) self._stack.propertyChanged.connect(self._onPropertyChanged) ContainerRegistry.getInstance().addContainer(self._stack) settingsLoaded = Signal() valueChanged = Signal() # Signal emitted whenever a value of a setting is changed def _onPropertyChanged(self, key, property_name): if property_name == "value": self.valueChanged.emit() # Property changed: trigger reslice # To do this we use the global container stack propertyChanged. # Reslicing is necessary for setting changes in this plugin, because the changes # are applied only once per "fresh" gcode global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack.propertyChanged.emit(key, property_name) ## Needs to return a dict that can be used to construct a settingcategory file. # See the example script for an example. # It follows the same style / guides as the Uranium settings. # Scripts can either override getSettingData directly, or use getSettingDataString # to return a string that will be parsed as json. The latter has the benefit over # returning a dict in that the order of settings is maintained. def getSettingData(self): setting_data = self.getSettingDataString() if type(setting_data) == str: setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict) return setting_data def getSettingDataString(self): raise NotImplementedError() def getDefinitionId(self): if self._stack: return self._stack.getBottom().getId() def getStackId(self): if self._stack: return self._stack.getId() ## Convenience function that retrieves value of a setting from the stack. def getSettingValueByKey(self, key): return self._stack.getProperty(key, "value") ## Convenience function that finds the value in a line of g-code. # When requesting key = x from line "G1 X100" the value 100 is returned. def getValue(self, line, key, default = None): if not key in line or (';' in line and line.find(key) > line.find(';')): return default sub_part = line[line.find(key) + 1:] m = re.search('^-?[0-9]+\.?[0-9]*', sub_part) if m is None: return default try: return float(m.group(0)) except: return default ## Convenience function to produce a line of g-code. # # You can put in an original g-code line and it'll re-use all the values # in that line. # All other keyword parameters are put in the result in g-code's format. # For instance, if you put ``G=1`` in the parameters, it will output # ``G1``. If you put ``G=1, X=100`` in the parameters, it will output # ``G1 X100``. The parameters G and M will always be put first. The # parameters T and S will be put second (or first if there is no G or M). # The rest of the parameters will be put in arbitrary order. # \param line The original g-code line that must be modified. If not # provided, an entirely new g-code line will be produced. # \return A line of g-code with the desired parameters filled in. def putValue(self, line = "", **kwargs): #Strip the comment. comment = "" if ";" in line: comment = line[line.find(";"):] line = line[:line.find(";")] #Strip the comment. #Parse the original g-code line. for part in line.split(" "): if part == "": continue parameter = part[0] if parameter in kwargs: continue #Skip this one. The user-provided parameter overwrites the one in the line. value = part[1:] kwargs[parameter] = value #Write the new g-code line. result = "" priority_parameters = ["G", "M", "T", "S", "F", "X", "Y", "Z", "E"] #First some parameters that get priority. In order of priority! for priority_key in priority_parameters: if priority_key in kwargs: if result != "": result += " " result += priority_key + str(kwargs[priority_key]) del kwargs[priority_key] for key, value in kwargs.items(): if result != "": result += " " result += key + str(value) #Put the comment back in. if comment != "": if result != "": result += " " result += ";" + comment return result ## This is called when the script is executed. # It gets a list of g-code strings and needs to return a (modified) list. def execute(self, data): raise NotImplementedError()
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 addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id): new_extruder_id = extruder_id extruder_definitions = self.findDefinitionContainers(id = new_extruder_id) if not extruder_definitions: Logger.log("w", "Could not find definition containers for extruder %s", new_extruder_id) return extruder_definition = extruder_definitions[0] unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) extruder_stack = ExtruderStack.ExtruderStack(unique_name) extruder_stack.setName(extruder_definition.getName()) extruder_stack.setDefinition(extruder_definition) extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_stack.setNextStack(machine) # create empty user changes container otherwise user_container = InstanceContainer(extruder_stack.id + "_user") user_container.addMetaDataEntry("type", "user") user_container.addMetaDataEntry("machine", extruder_stack.getId()) from cura.CuraApplication import CuraApplication user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_container.setDefinition(machine.definition) if machine.userChanges: # for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes # container to the extruder stack. for user_setting_key in machine.userChanges.getAllKeys(): settable_per_extruder = machine.getProperty(user_setting_key, "settable_per_extruder") if settable_per_extruder: user_container.addInstance(machine.userChanges.getInstance(user_setting_key)) machine.userChanges.removeInstance(user_setting_key, postpone_emit = True) extruder_stack.setUserChanges(user_container) self.addContainer(user_container) variant_id = "default" if machine.variant.getId() not in ("empty", "empty_variant"): variant_id = machine.variant.getId() else: variant_id = "empty_variant" extruder_stack.setVariantById(variant_id) material_id = "default" if machine.material.getId() not in ("empty", "empty_material"): material_id = machine.material.getId() else: material_id = "empty_material" extruder_stack.setMaterialById(material_id) quality_id = "default" if machine.quality.getId() not in ("empty", "empty_quality"): quality_id = machine.quality.getId() else: quality_id = "empty_quality" extruder_stack.setQualityById(quality_id) if machine.qualityChanges.getId() not in ("empty", "empty_quality_changes"): extruder_quality_changes_container = self.findInstanceContainers(name = machine.qualityChanges.getName(), extruder = extruder_id) if extruder_quality_changes_container: extruder_quality_changes_container = extruder_quality_changes_container[0] quality_changes_id = extruder_quality_changes_container.getId() extruder_stack.setQualityChangesById(quality_changes_id) else: # Some extruder quality_changes containers can be created at runtime as files in the qualities # folder. Those files won't be loaded in the registry immediately. So we also need to search # the folder to see if the quality_changes exists. extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine.qualityChanges.getName()) if extruder_quality_changes_container: quality_changes_id = extruder_quality_changes_container.getId() extruder_stack.setQualityChangesById(quality_changes_id) if not extruder_quality_changes_container: Logger.log("w", "Could not find quality_changes named [%s] for extruder [%s]", machine.qualityChanges.getName(), extruder_stack.getId()) else: extruder_stack.setQualityChangesById("empty_quality_changes") self.addContainer(extruder_stack) return extruder_stack
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 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 createExtruderTrain(self, extruder_definition: DefinitionContainer, machine_definition: DefinitionContainer, position, machine_id: str) -> None: # Cache some things. container_registry = ContainerRegistry.getInstance() machine_definition_id = Application.getInstance().getMachineManager( ).getQualityDefinitionId(machine_definition) # Create a container stack for this extruder. extruder_stack_id = container_registry.uniqueName( extruder_definition.getId()) container_stack = ContainerStack(extruder_stack_id) container_stack.setName(extruder_definition.getName( )) # Take over the display name to display the stack with. container_stack.addMetaDataEntry("type", "extruder_train") container_stack.addMetaDataEntry("machine", machine_id) container_stack.addMetaDataEntry("position", position) container_stack.addContainer(extruder_definition) # Find the variant to use for this extruder. variant = container_registry.findInstanceContainers( id="empty_variant")[0] if machine_definition.getMetaDataEntry("has_variants"): # First add any variant. Later, overwrite with preference if the preference is valid. variants = container_registry.findInstanceContainers( definition=machine_definition_id, type="variant") if len(variants) >= 1: variant = variants[0] preferred_variant_id = machine_definition.getMetaDataEntry( "preferred_variant") if preferred_variant_id: preferred_variants = container_registry.findInstanceContainers( id=preferred_variant_id, definition=machine_definition_id, type="variant") if len(preferred_variants) >= 1: variant = preferred_variants[0] else: Logger.log( "w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id) # And leave it at the default variant. container_stack.addContainer(variant) # Find a material to use for this variant. material = container_registry.findInstanceContainers( id="empty_material")[0] if machine_definition.getMetaDataEntry("has_materials"): # First add any material. Later, overwrite with preference if the preference is valid. machine_has_variant_materials = machine_definition.getMetaDataEntry( "has_variant_materials", default=False) if machine_has_variant_materials or machine_has_variant_materials == "True": materials = container_registry.findInstanceContainers( type="material", definition=machine_definition_id, variant=variant.getId()) else: materials = container_registry.findInstanceContainers( type="material", definition=machine_definition_id) if len(materials) >= 1: material = materials[0] preferred_material_id = machine_definition.getMetaDataEntry( "preferred_material") if preferred_material_id: global_stack = ContainerRegistry.getInstance( ).findContainerStacks(id=machine_id) if global_stack: approximate_material_diameter = round( global_stack[0].getProperty("material_diameter", "value")) else: approximate_material_diameter = round( machine_definition.getProperty("material_diameter", "value")) search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter } if machine_definition.getMetaDataEntry( "has_machine_materials"): search_criteria["definition"] = machine_definition_id if machine_definition.getMetaDataEntry( "has_variants") and variant: search_criteria["variant"] = variant.id else: search_criteria["definition"] = "fdmprinter" preferred_materials = container_registry.findInstanceContainers( **search_criteria) if len(preferred_materials) >= 1: # In some cases we get multiple materials. In that case, prefer materials that are marked as read only. read_only_preferred_materials = [ preferred_material for preferred_material in preferred_materials if preferred_material.isReadOnly() ] if len(read_only_preferred_materials) >= 1: material = read_only_preferred_materials[0] else: material = preferred_materials[0] else: Logger.log( "w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id) # And leave it at the default material. container_stack.addContainer(material) # Find a quality to use for this extruder. quality = container_registry.getEmptyInstanceContainer() search_criteria = {"type": "quality"} if machine_definition.getMetaDataEntry("has_machine_quality"): search_criteria["definition"] = machine_definition_id if machine_definition.getMetaDataEntry( "has_materials") and material: search_criteria["material"] = material.id else: search_criteria["definition"] = "fdmprinter" preferred_quality = machine_definition.getMetaDataEntry( "preferred_quality") if preferred_quality: search_criteria["id"] = preferred_quality containers = ContainerRegistry.getInstance().findInstanceContainers( **search_criteria) if not containers and preferred_quality: Logger.log( "w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id) search_criteria.pop("id", None) containers = ContainerRegistry.getInstance( ).findInstanceContainers(**search_criteria) if containers: quality = containers[0] container_stack.addContainer(quality) empty_quality_changes = container_registry.findInstanceContainers( id="empty_quality_changes")[0] container_stack.addContainer(empty_quality_changes) user_profile = container_registry.findInstanceContainers( type="user", extruder=extruder_stack_id) if user_profile: # There was already a user profile, loaded from settings. user_profile = user_profile[0] else: user_profile = InstanceContainer( extruder_stack_id + "_current_settings") # Add an empty user profile. user_profile.addMetaDataEntry("type", "user") user_profile.addMetaDataEntry("extruder", extruder_stack_id) user_profile.setDefinition(machine_definition) container_registry.addContainer(user_profile) container_stack.addContainer(user_profile) # regardless of what the next stack is, we have to set it again, because of signal routing. container_stack.setNextStack( Application.getInstance().getGlobalContainerStack()) container_registry.addContainer(container_stack)
class Script: def __init__(self): super().__init__() self._settings = None self._stack = None setting_data = self.getSettingData() self._stack = ContainerStack(stack_id=id(self)) self._stack.setDirty(False) # This stack does not need to be saved. ## Check if the definition of this script already exists. If not, add it to the registry. if "key" in setting_data: definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"]) if definitions: # Definition was found self._definition = definitions[0] else: self._definition = DefinitionContainer(setting_data["key"]) self._definition.deserialize(json.dumps(setting_data)) ContainerRegistry.getInstance().addContainer(self._definition) self._stack.addContainer(self._definition) self._instance = InstanceContainer(container_id="ScriptInstanceContainer") self._instance.setDefinition(self._definition) self._instance.addMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0)) self._stack.addContainer(self._instance) self._stack.propertyChanged.connect(self._onPropertyChanged) ContainerRegistry.getInstance().addContainer(self._stack) settingsLoaded = Signal() valueChanged = Signal() # Signal emitted whenever a value of a setting is changed def _onPropertyChanged(self, key, property_name): if property_name == "value": self.valueChanged.emit() # Property changed: trigger reslice # To do this we use the global container stack propertyChanged. # Reslicing is necessary for setting changes in this plugin, because the changes # are applied only once per "fresh" gcode global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack.propertyChanged.emit(key, property_name) ## Needs to return a dict that can be used to construct a settingcategory file. # See the example script for an example. # It follows the same style / guides as the Uranium settings. # Scripts can either override getSettingData directly, or use getSettingDataString # to return a string that will be parsed as json. The latter has the benefit over # returning a dict in that the order of settings is maintained. def getSettingData(self): setting_data = self.getSettingDataString() if type(setting_data) == str: setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict) return setting_data def getSettingDataString(self): raise NotImplementedError() def getDefinitionId(self): if self._stack: return self._stack.getBottom().getId() def getStackId(self): if self._stack: return self._stack.getId() ## Convenience function that retrieves value of a setting from the stack. def getSettingValueByKey(self, key): return self._stack.getProperty(key, "value") ## Convenience function that finds the value in a line of g-code. # When requesting key = x from line "G1 X100" the value 100 is returned. def getValue(self, line, key, default = None): if not key in line or (';' in line and line.find(key) > line.find(';')): return default sub_part = line[line.find(key) + 1:] m = re.search('^-?[0-9]+\.?[0-9]*', sub_part) if m is None: return default try: return float(m.group(0)) except: return default ## This is called when the script is executed. # It gets a list of g-code strings and needs to return a (modified) list. def execute(self, data): raise NotImplementedError()
def read(self, file_name): if file_name.split(".")[-1] != "ini": return None multi_extrusion = Application.getInstance().getGlobalContainerStack( ).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 + ".") profile = InstanceContainer( "Imported Legacy Profile") # Create an empty profile. parser = configparser.ConfigParser(interpolation=None) try: with open(file_name) as f: parser.readfp(f) #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 = Application.getInstance( ).getGlobalContainerStack().getBottom() profile.setDefinition(current_printer_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.setDirty(True) profile.addMetaDataEntry("type", "quality_changes") profile.addMetaDataEntry("quality", "normal") return 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 + ".") profile = InstanceContainer("Imported Legacy Profile") # Create an empty profile. parser = configparser.ConfigParser(interpolation = None) try: with open(file_name) as f: parser.readfp(f) # 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.getBottom() profile.setDefinition(current_printer_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.setDirty(True) profile.addMetaDataEntry("type", "quality_changes") profile.addMetaDataEntry("quality_type", "normal") return profile
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id, new_global_quality_changes = None, create_new_ids = True): new_extruder_id = extruder_id application = CuraApplication.getInstance() extruder_definitions = self.findDefinitionContainers(id = new_extruder_id) if not extruder_definitions: Logger.log("w", "Could not find definition containers for extruder %s", new_extruder_id) return extruder_definition = extruder_definitions[0] unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) if create_new_ids else machine.getName() + " " + new_extruder_id extruder_stack = ExtruderStack.ExtruderStack(unique_name) extruder_stack.setName(extruder_definition.getName()) extruder_stack.setDefinition(extruder_definition) extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) # create a new definition_changes container for the extruder stack definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings" definition_changes_name = definition_changes_id definition_changes = InstanceContainer(definition_changes_id, parent = application) definition_changes.setName(definition_changes_name) definition_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) definition_changes.addMetaDataEntry("type", "definition_changes") definition_changes.addMetaDataEntry("definition", extruder_definition.getId()) # move definition_changes settings if exist for setting_key in definition_changes.getAllKeys(): if machine.definition.getProperty(setting_key, "settable_per_extruder"): setting_value = machine.definitionChanges.getProperty(setting_key, "value") if setting_value is not None: # move it to the extruder stack's definition_changes setting_definition = machine.getSettingDefinition(setting_key) new_instance = SettingInstance(setting_definition, definition_changes) new_instance.setProperty("value", setting_value) new_instance.resetState() # Ensure that the state is not seen as a user state. definition_changes.addInstance(new_instance) definition_changes.setDirty(True) machine.definitionChanges.removeInstance(setting_key, postpone_emit = True) self.addContainer(definition_changes) extruder_stack.setDefinitionChanges(definition_changes) # create empty user changes container otherwise user_container_id = self.uniqueName(extruder_stack.getId() + "_user") if create_new_ids else extruder_stack.getId() + "_user" user_container_name = user_container_id user_container = InstanceContainer(user_container_id, parent = application) user_container.setName(user_container_name) user_container.addMetaDataEntry("type", "user") user_container.addMetaDataEntry("machine", machine.getId()) user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_container.setDefinition(machine.definition.getId()) user_container.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position")) if machine.userChanges: # for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes # container to the extruder stack. for user_setting_key in machine.userChanges.getAllKeys(): settable_per_extruder = machine.getProperty(user_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = machine.getProperty(user_setting_key, "value") setting_definition = machine.getSettingDefinition(user_setting_key) new_instance = SettingInstance(setting_definition, definition_changes) new_instance.setProperty("value", setting_value) new_instance.resetState() # Ensure that the state is not seen as a user state. user_container.addInstance(new_instance) user_container.setDirty(True) machine.userChanges.removeInstance(user_setting_key, postpone_emit = True) self.addContainer(user_container) extruder_stack.setUserChanges(user_container) empty_variant = application.empty_variant_container empty_material = application.empty_material_container empty_quality = application.empty_quality_container if machine.variant.getId() not in ("empty", "empty_variant"): variant = machine.variant else: variant = empty_variant extruder_stack.variant = variant if machine.material.getId() not in ("empty", "empty_material"): material = machine.material else: material = empty_material extruder_stack.material = material if machine.quality.getId() not in ("empty", "empty_quality"): quality = machine.quality else: quality = empty_quality extruder_stack.quality = quality machine_quality_changes = machine.qualityChanges if new_global_quality_changes is not None: machine_quality_changes = new_global_quality_changes if machine_quality_changes.getId() not in ("empty", "empty_quality_changes"): extruder_quality_changes_container = self.findInstanceContainers(name = machine_quality_changes.getName(), extruder = extruder_id) if extruder_quality_changes_container: extruder_quality_changes_container = extruder_quality_changes_container[0] quality_changes_id = extruder_quality_changes_container.getId() extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0] else: # Some extruder quality_changes containers can be created at runtime as files in the qualities # folder. Those files won't be loaded in the registry immediately. So we also need to search # the folder to see if the quality_changes exists. extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName()) if extruder_quality_changes_container: quality_changes_id = extruder_quality_changes_container.getId() extruder_quality_changes_container.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0] else: # if we still cannot find a quality changes container for the extruder, create a new one container_name = machine_quality_changes.getName() container_id = self.uniqueName(extruder_stack.getId() + "_qc_" + container_name) extruder_quality_changes_container = InstanceContainer(container_id, parent = application) extruder_quality_changes_container.setName(container_name) extruder_quality_changes_container.addMetaDataEntry("type", "quality_changes") extruder_quality_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) extruder_quality_changes_container.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_quality_changes_container.addMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type")) extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId()) self.addContainer(extruder_quality_changes_container) extruder_stack.qualityChanges = extruder_quality_changes_container if not extruder_quality_changes_container: Logger.log("w", "Could not find quality_changes named [%s] for extruder [%s]", machine_quality_changes.getName(), extruder_stack.getId()) else: # move all per-extruder settings to the extruder's quality changes for qc_setting_key in machine_quality_changes.getAllKeys(): settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = machine_quality_changes.getProperty(qc_setting_key, "value") setting_definition = machine.getSettingDefinition(qc_setting_key) new_instance = SettingInstance(setting_definition, definition_changes) new_instance.setProperty("value", setting_value) new_instance.resetState() # Ensure that the state is not seen as a user state. extruder_quality_changes_container.addInstance(new_instance) extruder_quality_changes_container.setDirty(True) machine_quality_changes.removeInstance(qc_setting_key, postpone_emit=True) else: extruder_stack.qualityChanges = self.findInstanceContainers(id = "empty_quality_changes")[0] self.addContainer(extruder_stack) # Also need to fix the other qualities that are suitable for this machine. Those quality changes may still have # per-extruder settings in the container for the machine instead of the extruder. if machine_quality_changes.getId() not in ("empty", "empty_quality_changes"): quality_changes_machine_definition_id = machine_quality_changes.getDefinition().getId() else: whole_machine_definition = machine.definition machine_entry = machine.definition.getMetaDataEntry("machine") if machine_entry is not None: container_registry = ContainerRegistry.getInstance() whole_machine_definition = container_registry.findDefinitionContainers(id = machine_entry)[0] quality_changes_machine_definition_id = "fdmprinter" if whole_machine_definition.getMetaDataEntry("has_machine_quality"): quality_changes_machine_definition_id = machine.definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId()) qcs = self.findInstanceContainers(type = "quality_changes", definition = quality_changes_machine_definition_id) qc_groups = {} # map of qc names -> qc containers for qc in qcs: qc_name = qc.getName() if qc_name not in qc_groups: qc_groups[qc_name] = [] qc_groups[qc_name].append(qc) # try to find from the quality changes cura directory too quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName()) if quality_changes_container: qc_groups[qc_name].append(quality_changes_container) for qc_name, qc_list in qc_groups.items(): qc_dict = {"global": None, "extruders": []} for qc in qc_list: extruder_position = qc.getMetaDataEntry("position") if extruder_position is not None: qc_dict["extruders"].append(qc) else: qc_dict["global"] = qc if qc_dict["global"] is not None and len(qc_dict["extruders"]) == 1: # move per-extruder settings for qc_setting_key in qc_dict["global"].getAllKeys(): settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = qc_dict["global"].getProperty(qc_setting_key, "value") setting_definition = machine.getSettingDefinition(qc_setting_key) new_instance = SettingInstance(setting_definition, definition_changes) new_instance.setProperty("value", setting_value) new_instance.resetState() # Ensure that the state is not seen as a user state. qc_dict["extruders"][0].addInstance(new_instance) qc_dict["extruders"][0].setDirty(True) qc_dict["global"].removeInstance(qc_setting_key, postpone_emit=True) # Set next stack at the end extruder_stack.setNextStack(machine) return extruder_stack
def importProfile(self, file_name): Logger.log("d", "Attempting to import profile %s", file_name) if not file_name: return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")} plugin_registry = PluginRegistry.getInstance() extension = file_name.split(".")[-1] global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return machine_extruders = [] for position in sorted(global_stack.extruders): machine_extruders.append(global_stack.extruders[position]) for plugin_id, meta_data in self._getIOPlugins("profile_reader"): if meta_data["profile_reader"][0]["extension"] != extension: continue profile_reader = plugin_registry.getPluginObject(plugin_id) try: profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader. except NoProfileException: return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "No custom profile to import in file <filename>{0}</filename>", file_name)} except Exception as e: # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None. Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e)) return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "\n" + str(e))} if profile_or_list: # Ensure it is always a list of profiles if not isinstance(profile_or_list, list): profile_or_list = [profile_or_list] # First check if this profile is suitable for this machine global_profile = None extruder_profiles = [] if len(profile_or_list) == 1: global_profile = profile_or_list[0] else: for profile in profile_or_list: if not profile.getMetaDataEntry("position"): global_profile = profile else: extruder_profiles.append(profile) extruder_profiles = sorted(extruder_profiles, key = lambda x: int(x.getMetaDataEntry("position"))) profile_or_list = [global_profile] + extruder_profiles if not global_profile: Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name) return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)} profile_definition = global_profile.getMetaDataEntry("definition") # Make sure we have a profile_definition in the file: if profile_definition is None: break machine_definition = self.findDefinitionContainers(id = profile_definition) if not machine_definition: Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition) return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name) } machine_definition = machine_definition[0] # Get the expected machine definition. # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode... profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition) expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition) # And check if the profile_definition matches either one (showing error if not): if profile_definition != expected_machine_definition: Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition) return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)} # Fix the global quality profile's definition field in case it's not correct global_profile.setMetaDataEntry("definition", expected_machine_definition) quality_name = global_profile.getName() quality_type = global_profile.getMetaDataEntry("quality_type") name_seed = os.path.splitext(os.path.basename(file_name))[0] new_name = self.uniqueName(name_seed) # Ensure it is always a list of profiles if type(profile_or_list) is not list: profile_or_list = [profile_or_list] # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack if len(profile_or_list) == 1: global_profile = profile_or_list[0] extruder_profiles = [] for idx, extruder in enumerate(global_stack.extruders.values()): profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1)) profile = InstanceContainer(profile_id) profile.setName(quality_name) profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) profile.addMetaDataEntry("type", "quality_changes") profile.addMetaDataEntry("definition", expected_machine_definition) profile.addMetaDataEntry("quality_type", quality_type) profile.addMetaDataEntry("position", "0") profile.setDirty(True) if idx == 0: # move all per-extruder settings to the first extruder's quality_changes for qc_setting_key in global_profile.getAllKeys(): settable_per_extruder = global_stack.getProperty(qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = global_profile.getProperty(qc_setting_key, "value") setting_definition = global_stack.getSettingDefinition(qc_setting_key) new_instance = SettingInstance(setting_definition, profile) new_instance.setProperty("value", setting_value) new_instance.resetState() # Ensure that the state is not seen as a user state. profile.addInstance(new_instance) profile.setDirty(True) global_profile.removeInstance(qc_setting_key, postpone_emit=True) extruder_profiles.append(profile) for profile in extruder_profiles: profile_or_list.append(profile) # Import all profiles for profile_index, profile in enumerate(profile_or_list): if profile_index == 0: # This is assumed to be the global profile profile_id = (global_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") elif profile_index < len(machine_extruders) + 1: # This is assumed to be an extruder profile extruder_id = machine_extruders[profile_index - 1].definition.getId() extruder_position = str(profile_index - 1) if not profile.getMetaDataEntry("position"): profile.addMetaDataEntry("position", extruder_position) else: profile.setMetaDataEntry("position", extruder_position) profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_") else: #More extruders in the imported file than in the machine. continue #Delete the additional profiles. result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition) if result is not None: return {"status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, result)} return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())} # This message is throw when the profile reader doesn't find any profile in the file return {"status": "error", "message": catalog.i18nc("@info:status", "File {0} does not contain any valid profile.", file_name)} # If it hasn't returned by now, none of the plugins loaded the profile successfully. return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
def importProfile(self, file_name): Logger.log("d", "Attempting to import profile %s", file_name) if not file_name: return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path") } plugin_registry = PluginRegistry.getInstance() extension = file_name.split(".")[-1] global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return machine_extruders = [] for position in sorted(global_stack.extruders): machine_extruders.append(global_stack.extruders[position]) for plugin_id, meta_data in self._getIOPlugins("profile_reader"): if meta_data["profile_reader"][0]["extension"] != extension: continue profile_reader = plugin_registry.getPluginObject(plugin_id) try: profile_or_list = profile_reader.read( file_name) # Try to open the file with the profile reader. except NoProfileException: return { "status": "ok", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "No custom profile to import in file <filename>{0}</filename>", file_name) } except Exception as e: # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None. Logger.log( "e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e)) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "\n" + str(e)) } if profile_or_list: # Ensure it is always a list of profiles if not isinstance(profile_or_list, list): profile_or_list = [profile_or_list] # First check if this profile is suitable for this machine global_profile = None extruder_profiles = [] if len(profile_or_list) == 1: global_profile = profile_or_list[0] else: for profile in profile_or_list: if not profile.getMetaDataEntry("position"): global_profile = profile else: extruder_profiles.append(profile) extruder_profiles = sorted( extruder_profiles, key=lambda x: int(x.getMetaDataEntry("position"))) profile_or_list = [global_profile] + extruder_profiles if not global_profile: Logger.log( "e", "Incorrect profile [%s]. Could not find global profile", file_name) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name) } profile_definition = global_profile.getMetaDataEntry( "definition") # Make sure we have a profile_definition in the file: if profile_definition is None: break machine_definition = self.findDefinitionContainers( id=profile_definition) if not machine_definition: Logger.log( "e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name) } machine_definition = machine_definition[0] # Get the expected machine definition. # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode... profile_definition = getMachineDefinitionIDForQualitySearch( machine_definition) expected_machine_definition = getMachineDefinitionIDForQualitySearch( global_stack.definition) # And check if the profile_definition matches either one (showing error if not): if profile_definition != expected_machine_definition: Logger.log( "e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition) return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition) } # Fix the global quality profile's definition field in case it's not correct global_profile.setMetaDataEntry("definition", expected_machine_definition) quality_name = global_profile.getName() quality_type = global_profile.getMetaDataEntry("quality_type") name_seed = os.path.splitext(os.path.basename(file_name))[0] new_name = self.uniqueName(name_seed) # Ensure it is always a list of profiles if type(profile_or_list) is not list: profile_or_list = [profile_or_list] # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack if len(profile_or_list) == 1: global_profile = profile_or_list[0] extruder_profiles = [] for idx, extruder in enumerate( global_stack.extruders.values()): profile_id = ContainerRegistry.getInstance( ).uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1)) profile = InstanceContainer(profile_id) profile.setName(quality_name) profile.addMetaDataEntry( "setting_version", CuraApplication.SettingVersion) profile.addMetaDataEntry("type", "quality_changes") profile.addMetaDataEntry("definition", expected_machine_definition) profile.addMetaDataEntry("quality_type", quality_type) profile.addMetaDataEntry("position", "0") profile.setDirty(True) if idx == 0: # move all per-extruder settings to the first extruder's quality_changes for qc_setting_key in global_profile.getAllKeys(): settable_per_extruder = global_stack.getProperty( qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = global_profile.getProperty( qc_setting_key, "value") setting_definition = global_stack.getSettingDefinition( qc_setting_key) new_instance = SettingInstance( setting_definition, profile) new_instance.setProperty( "value", setting_value) new_instance.resetState( ) # Ensure that the state is not seen as a user state. profile.addInstance(new_instance) profile.setDirty(True) global_profile.removeInstance( qc_setting_key, postpone_emit=True) extruder_profiles.append(profile) for profile in extruder_profiles: profile_or_list.append(profile) # Import all profiles for profile_index, profile in enumerate(profile_or_list): if profile_index == 0: # This is assumed to be the global profile profile_id = (global_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") elif profile_index < len(machine_extruders) + 1: # This is assumed to be an extruder profile extruder_id = machine_extruders[profile_index - 1].definition.getId() extruder_position = str(profile_index - 1) if not profile.getMetaDataEntry("position"): profile.addMetaDataEntry("position", extruder_position) else: profile.setMetaDataEntry("position", extruder_position) profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_") else: #More extruders in the imported file than in the machine. continue #Delete the additional profiles. result = self._configureProfile( profile, profile_id, new_name, expected_machine_definition) if result is not None: return { "status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, result) } return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName()) } # This message is throw when the profile reader doesn't find any profile in the file return { "status": "error", "message": catalog.i18nc("@info:status", "File {0} does not contain any valid profile.", file_name) } # If it hasn't returned by now, none of the plugins loaded the profile successfully. return { "status": "error", "message": catalog.i18nc( "@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name) }
def getInstanceContainer(container_type) -> InstanceContainer: container = InstanceContainer(container_id = "InstanceContainer") container.addMetaDataEntry("type", container_type) return container
def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]: profile.setDirty(True) # Ensure the profiles are correctly saved new_id = self.createUniqueName( "quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile")) profile._id = new_id profile.setName(new_name) if "type" in profile.getMetaData(): profile.setMetaDataEntry("type", "quality_changes") else: profile.addMetaDataEntry("type", "quality_changes") quality_type = profile.getMetaDataEntry("quality_type") if not quality_type: return catalog.i18nc("@info:status", "Profile is missing a quality type.") quality_type_criteria = {"quality_type": quality_type} if self._machineHasOwnQualities(): profile.setDefinition(self._activeQualityDefinition().getId()) if self._machineHasOwnMaterials(): active_material_id = self._activeMaterialId() if active_material_id and active_material_id != "empty": # only update if there is an active material profile.addMetaDataEntry("material", active_material_id) quality_type_criteria["material"] = active_material_id quality_type_criteria["definition"] = profile.getDefinition( ).getId() else: profile.setDefinition(fdmprinter) quality_type_criteria["definition"] = "fdmprinter" machine_definition = Application.getInstance().getGlobalContainerStack( ).getBottom() del quality_type_criteria["definition"] # materials = None if "material" in quality_type_criteria: # materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"]) del quality_type_criteria["material"] # Do not filter quality containers here with materials because we are trying to import a profile, so it should # NOT be restricted by the active materials on the current machine. materials = None # Check to make sure the imported profile actually makes sense in context of the current configuration. # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as # successfully imported but then fail to show up. from cura.QualityManager import QualityManager qualities = QualityManager.getInstance( )._getFilteredContainersForStack(machine_definition, materials, **quality_type_criteria) if not qualities: return catalog.i18nc( "@info:status", "Could not find a quality type {0} for the current configuration.", quality_type) ContainerRegistry.getInstance().addContainer(profile) return None
def getInstanceContainer(container_type) -> InstanceContainer: container = InstanceContainer(container_id = "InstanceContainer") container.addMetaDataEntry("type", container_type) return container
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id): new_extruder_id = extruder_id extruder_definitions = self.findDefinitionContainers( id=new_extruder_id) if not extruder_definitions: Logger.log("w", "Could not find definition containers for extruder %s", new_extruder_id) return extruder_definition = extruder_definitions[0] unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) extruder_stack = ExtruderStack.ExtruderStack(unique_name) extruder_stack.setName(extruder_definition.getName()) extruder_stack.setDefinition(extruder_definition) extruder_stack.addMetaDataEntry( "position", extruder_definition.getMetaDataEntry("position")) extruder_stack.setNextStack(machine) # create empty user changes container otherwise user_container = InstanceContainer(extruder_stack.id + "_user") user_container.addMetaDataEntry("type", "user") user_container.addMetaDataEntry("machine", extruder_stack.getId()) from cura.CuraApplication import CuraApplication user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_container.setDefinition(machine.definition) if machine.userChanges: # for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes # container to the extruder stack. for user_setting_key in machine.userChanges.getAllKeys(): settable_per_extruder = machine.getProperty( user_setting_key, "settable_per_extruder") if settable_per_extruder: user_container.addInstance( machine.userChanges.getInstance(user_setting_key)) machine.userChanges.removeInstance(user_setting_key, postpone_emit=True) extruder_stack.setUserChanges(user_container) self.addContainer(user_container) variant_id = "default" if machine.variant.getId() not in ("empty", "empty_variant"): variant_id = machine.variant.getId() else: variant_id = "empty_variant" extruder_stack.setVariantById(variant_id) material_id = "default" if machine.material.getId() not in ("empty", "empty_material"): material_id = machine.material.getId() else: material_id = "empty_material" extruder_stack.setMaterialById(material_id) quality_id = "default" if machine.quality.getId() not in ("empty", "empty_quality"): quality_id = machine.quality.getId() else: quality_id = "empty_quality" extruder_stack.setQualityById(quality_id) if machine.qualityChanges.getId() not in ("empty", "empty_quality_changes"): extruder_quality_changes_container = self.findInstanceContainers( name=machine.qualityChanges.getName(), extruder=extruder_id) if extruder_quality_changes_container: extruder_quality_changes_container = extruder_quality_changes_container[ 0] quality_changes_id = extruder_quality_changes_container.getId() extruder_stack.setQualityChangesById(quality_changes_id) else: # Some extruder quality_changes containers can be created at runtime as files in the qualities # folder. Those files won't be loaded in the registry immediately. So we also need to search # the folder to see if the quality_changes exists. extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder( machine.qualityChanges.getName()) if extruder_quality_changes_container: quality_changes_id = extruder_quality_changes_container.getId( ) extruder_stack.setQualityChangesById(quality_changes_id) if not extruder_quality_changes_container: Logger.log( "w", "Could not find quality_changes named [%s] for extruder [%s]", machine.qualityChanges.getName(), extruder_stack.getId()) else: extruder_stack.setQualityChangesById("empty_quality_changes") self.addContainer(extruder_stack) return extruder_stack
def createExtruderTrain(self, extruder_definition: DefinitionContainerInterface, machine_definition: DefinitionContainerInterface, position, machine_id: str) -> None: # Cache some things. container_registry = ContainerRegistry.getInstance() machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_definition) # Create a container stack for this extruder. extruder_stack_id = container_registry.uniqueName(extruder_definition.getId()) container_stack = ContainerStack(extruder_stack_id) container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with. container_stack.addMetaDataEntry("type", "extruder_train") container_stack.addMetaDataEntry("machine", machine_id) container_stack.addMetaDataEntry("position", position) container_stack.addContainer(extruder_definition) # Find the variant to use for this extruder. variant = container_registry.findInstanceContainers(id = "empty_variant")[0] if machine_definition.getMetaDataEntry("has_variants"): # First add any variant. Later, overwrite with preference if the preference is valid. variants = container_registry.findInstanceContainers(definition = machine_definition_id, type = "variant") if len(variants) >= 1: variant = variants[0] preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant") if preferred_variant_id: preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, definition = machine_definition_id, type = "variant") if len(preferred_variants) >= 1: variant = preferred_variants[0] else: Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id) # And leave it at the default variant. container_stack.addContainer(variant) # Find a material to use for this variant. material = container_registry.findInstanceContainers(id = "empty_material")[0] if machine_definition.getMetaDataEntry("has_materials"): # First add any material. Later, overwrite with preference if the preference is valid. machine_has_variant_materials = machine_definition.getMetaDataEntry("has_variant_materials", default = False) if machine_has_variant_materials or machine_has_variant_materials == "True": materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId()) else: materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id) if len(materials) >= 1: material = materials[0] preferred_material_id = machine_definition.getMetaDataEntry("preferred_material") if preferred_material_id: global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) if global_stack: approximate_material_diameter = str(round(global_stack[0].getProperty("material_diameter", "value"))) else: approximate_material_diameter = str(round(machine_definition.getProperty("material_diameter", "value"))) search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter} if machine_definition.getMetaDataEntry("has_machine_materials"): search_criteria["definition"] = machine_definition_id if machine_definition.getMetaDataEntry("has_variants") and variant: search_criteria["variant"] = variant.id else: search_criteria["definition"] = "fdmprinter" preferred_materials = container_registry.findInstanceContainers(**search_criteria) if len(preferred_materials) >= 1: # In some cases we get multiple materials. In that case, prefer materials that are marked as read only. read_only_preferred_materials = [preferred_material for preferred_material in preferred_materials if preferred_material.isReadOnly()] if len(read_only_preferred_materials) >= 1: material = read_only_preferred_materials[0] else: material = preferred_materials[0] else: Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id) # And leave it at the default material. container_stack.addContainer(material) # Find a quality to use for this extruder. quality = container_registry.getEmptyInstanceContainer() search_criteria = { "type": "quality" } if machine_definition.getMetaDataEntry("has_machine_quality"): search_criteria["definition"] = machine_definition_id if machine_definition.getMetaDataEntry("has_materials") and material: search_criteria["material"] = material.id else: search_criteria["definition"] = "fdmprinter" preferred_quality = machine_definition.getMetaDataEntry("preferred_quality") if preferred_quality: search_criteria["id"] = preferred_quality containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) if not containers and preferred_quality: Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id) search_criteria.pop("id", None) containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) if containers: quality = containers[0] container_stack.addContainer(quality) empty_quality_changes = container_registry.findInstanceContainers(id = "empty_quality_changes")[0] container_stack.addContainer(empty_quality_changes) user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id) if user_profile: # There was already a user profile, loaded from settings. user_profile = user_profile[0] else: user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile. user_profile.addMetaDataEntry("type", "user") user_profile.addMetaDataEntry("extruder", extruder_stack_id) from cura.CuraApplication import CuraApplication user_profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_profile.setDefinition(machine_definition) container_registry.addContainer(user_profile) container_stack.addContainer(user_profile) # regardless of what the next stack is, we have to set it again, because of signal routing. container_stack.setNextStack(Application.getInstance().getGlobalContainerStack()) container_registry.addContainer(container_stack)
class Script: def __init__(self): super().__init__() self._settings = None self._stack = None setting_data = self.getSettingData() self._stack = ContainerStack(stack_id = str(id(self))) self._stack.setDirty(False) # This stack does not need to be saved. ## Check if the definition of this script already exists. If not, add it to the registry. if "key" in setting_data: definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"]) if definitions: # Definition was found self._definition = definitions[0] else: self._definition = DefinitionContainer(setting_data["key"]) try: self._definition.deserialize(json.dumps(setting_data)) ContainerRegistry.getInstance().addContainer(self._definition) except ContainerFormatError: self._definition = None return self._stack.addContainer(self._definition) self._instance = InstanceContainer(container_id="ScriptInstanceContainer") self._instance.setDefinition(self._definition.getId()) self._instance.addMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0)) self._stack.addContainer(self._instance) self._stack.propertyChanged.connect(self._onPropertyChanged) ContainerRegistry.getInstance().addContainer(self._stack) settingsLoaded = Signal() valueChanged = Signal() # Signal emitted whenever a value of a setting is changed def _onPropertyChanged(self, key, property_name): if property_name == "value": self.valueChanged.emit() # Property changed: trigger reslice # To do this we use the global container stack propertyChanged. # Reslicing is necessary for setting changes in this plugin, because the changes # are applied only once per "fresh" gcode global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack.propertyChanged.emit(key, property_name) ## Needs to return a dict that can be used to construct a settingcategory file. # See the example script for an example. # It follows the same style / guides as the Uranium settings. # Scripts can either override getSettingData directly, or use getSettingDataString # to return a string that will be parsed as json. The latter has the benefit over # returning a dict in that the order of settings is maintained. def getSettingData(self): setting_data = self.getSettingDataString() if type(setting_data) == str: setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict) return setting_data def getSettingDataString(self): raise NotImplementedError() def getDefinitionId(self): if self._stack: return self._stack.getBottom().getId() def getStackId(self): if self._stack: return self._stack.getId() ## Convenience function that retrieves value of a setting from the stack. def getSettingValueByKey(self, key): return self._stack.getProperty(key, "value") ## Convenience function that finds the value in a line of g-code. # When requesting key = x from line "G1 X100" the value 100 is returned. def getValue(self, line, key, default = None): if not key in line or (';' in line and line.find(key) > line.find(';')): return default sub_part = line[line.find(key) + 1:] m = re.search('^-?[0-9]+\.?[0-9]*', sub_part) if m is None: return default try: return float(m.group(0)) except: return default ## Convenience function to produce a line of g-code. # # You can put in an original g-code line and it'll re-use all the values # in that line. # All other keyword parameters are put in the result in g-code's format. # For instance, if you put ``G=1`` in the parameters, it will output # ``G1``. If you put ``G=1, X=100`` in the parameters, it will output # ``G1 X100``. The parameters G and M will always be put first. The # parameters T and S will be put second (or first if there is no G or M). # The rest of the parameters will be put in arbitrary order. # \param line The original g-code line that must be modified. If not # provided, an entirely new g-code line will be produced. # \return A line of g-code with the desired parameters filled in. def putValue(self, line = "", **kwargs): #Strip the comment. comment = "" if ";" in line: comment = line[line.find(";"):] line = line[:line.find(";")] #Strip the comment. #Parse the original g-code line. for part in line.split(" "): if part == "": continue parameter = part[0] if parameter in kwargs: continue #Skip this one. The user-provided parameter overwrites the one in the line. value = part[1:] kwargs[parameter] = value #Write the new g-code line. result = "" priority_parameters = ["G", "M", "T", "S", "F", "X", "Y", "Z", "E"] #First some parameters that get priority. In order of priority! for priority_key in priority_parameters: if priority_key in kwargs: if result != "": result += " " result += priority_key + str(kwargs[priority_key]) del kwargs[priority_key] for key, value in kwargs.items(): if result != "": result += " " result += key + str(value) #Put the comment back in. if comment != "": if result != "": result += " " result += ";" + comment return result ## This is called when the script is executed. # It gets a list of g-code strings and needs to return a (modified) list. def execute(self, data): raise NotImplementedError()