Exemple #1
0
    def findAllQualityChangesForMachine(self, machine_definition: DefinitionContainer) -> List[InstanceContainer]:
        if machine_definition.getMetaDataEntry("has_machine_quality"):
            definition_id = machine_definition.getId()
        else:
            definition_id = "fdmprinter"

        filter_dict = { "type": "quality_changes", "extruder": None, "definition": definition_id }
        quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
        return quality_changes_list
def test_findDefinitionContainers(container_registry, data):
    for container in data["containers"]: # Fill the registry with mock containers.
        container = container.copy()
        container_id = container["id"]
        del container["id"]
        definition_container = DefinitionContainer(container_id)
        for key, value in container.items(): # Copy data into metadata.
            definition_container.getMetaData()[key] = value
        container_registry.addContainer(definition_container)

    results = container_registry.findDefinitionContainers(**data["filter"]) # The actual function call we're testing.

    _verifyMetaDataMatches(results, data["result"])
Exemple #3
0
    def initialize(self) -> 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
        if self._definition is None:
            return
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.setMetaDataEntry("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)
def test_getHasErrors(container_stack):
    definition_container = DefinitionContainer(str(uuid.uuid4()))
    container_stack.addContainer(definition_container)
    definition_container.getAllKeys = MagicMock(return_value = {"test_key"})

    container = MagicMock()
    container_stack.addContainer(container)

    # We won't get any wrong validation states, so it shouldn't have errors.
    assert not container_stack.hasErrors()

    # Fake the property so it does return validation state
    container.getProperty = MagicMock(return_value = ValidatorState.MaximumError)
    assert container_stack.hasErrors() # Now the container stack has errors!

    assert container_stack.getErrorKeys() == ["test_key"]
    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._stack.addContainer(self._instance)

        ContainerRegistry.getInstance().addContainer(self._stack)
def test_roundtrip_stack(tmpdir, process_count, loaded_container_registry):
    definition = loaded_container_registry.findDefinitionContainers(id = "multiple_settings")[0]

    temp_file = tmpdir.join("container_stack_test")

    mp_run(process_count, write_data, temp_file, definition)

    assert len(list(tmpdir.listdir())) == 1

    results = mp_run(process_count, read_data, temp_file)

    for result in results:
        deserialized_definition = DefinitionContainer("test_definition")
        deserialized_definition.deserialize(result)

        assert deserialized_definition.getName() == definition.getName()
        assert deserialized_definition.getMetaData() == definition.getMetaData()
def createModel(definition = "multiple_settings.def.json"):
    model = SettingDefinitionsModel()

    # Get a basic definition container
    uid = str(uuid.uuid4())
    definition_container = DefinitionContainer(uid)
    with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "definitions", definition),
              encoding="utf-8") as data:
        json = data.read()

    definition_container.deserialize(json)
    model._container = definition_container
    model.setShowAll(True)
    model.forceUpdate()
    model._updateVisibleRows()

    return model
Exemple #8
0
    def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainer, machine_definition: DefinitionContainer, **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)

        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
Exemple #9
0
    def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainer) -> str:
        quality_definition = machine_definition.getMetaDataEntry("quality_definition")
        if not quality_definition:
            return machine_definition.id

        definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition)
        if not definitions:
            Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id)
            return machine_definition.id

        return cls._findInstanceContainerDefinitionId(definitions[0])
    def load_definitions(self):
        """
		Load all the setting definitions into a custom container stack.

		This container stack also contains extra entries for the articles that
		are not settings.

		The result is stored in self._container_stack.
		"""
        if self._container_stack:
            return  # Already done earlier. Don't re-load.
        with open(
                os.path.join(os.path.dirname(__file__), "resources",
                             "settings_guide_definitions.def.json")) as f:
            definitions_serialised = f.read()
        definition_container = DefinitionContainer(
            "settings_guide_definitions")
        definition_container.deserialize(definitions_serialised)
        ContainerRegistry.getInstance().addContainer(definition_container)
        self._container_stack = ContainerStack("settings_guide_stack")
        self._container_stack.addContainer(definition_container)
        ContainerRegistry.getInstance().addContainer(self._container_stack)
def assertIsDefinitionValid(definition_container, path, file_name):
    with open(os.path.join(path, file_name), encoding = "utf-8") as data:
        json = data.read()
        parser, is_valid = definition_container.readAndValidateSerialized(json)
        assert is_valid #The definition has invalid JSON structure.
        metadata = DefinitionContainer.deserializeMetadata(json, "whatever")

        # If the definition defines a platform file, it should be in /resources/meshes/
        if "platform" in metadata[0]:
            assert metadata[0]["platform"] in all_meshes

        if "platform_texture" in metadata[0]:
            assert metadata[0]["platform_texture"] in all_images
    def _saveCachedDefinition(self, definition: DefinitionContainer) -> None:
        """Cache a definition container on disk.
        
        Definition containers can be quite expensive to parse and load, so this
        pickles a container and saves the pre-parsed definition on disk.
        
        :param definition: The definition container to store.
        """

        cache_path = Resources.getStoragePath(
            Resources.Cache, "definitions",
            Application.getInstance().getVersion(), definition.id)

        # Ensure the cache path exists.
        try:
            os.makedirs(os.path.dirname(cache_path), exist_ok=True)
        except PermissionError:
            Logger.log(
                "w",
                "The definition cache for definition {definition_id} failed to save because you don't have permissions to write in the cache directory."
                .format(definition_id=definition.getId()))
            return  # No rights to save it. Better give up.

        try:
            with open(cache_path, "wb") as f:
                pickle.dump(definition, f, pickle.HIGHEST_PROTOCOL)
        except RecursionError:
            # Sometimes a recursion error in pickling occurs here.
            # The cause is unknown. It must be some circular reference in the definition instances or definition containers.
            # Instead of saving a partial cache and raising an exception, simply fail to save the cache.
            # See CURA-4024.
            Logger.log(
                "w",
                "The definition cache for definition {definition_id} failed to pickle."
                .format(definition_id=definition.getId()))
            if os.path.exists(cache_path):
                os.remove(
                    cache_path
                )  # The pickling might be half-complete, which causes EOFError in Pickle when you load it later.
def test_deserializeRemovesWrongInstanceContainer(extruder_stack):
    extruder_stack._containers[
        cura.Settings.CuraContainerStack._ContainerIndexes.
        Quality] = getInstanceContainer(container_type="wrong type")
    extruder_stack._containers[
        cura.Settings.CuraContainerStack._ContainerIndexes.
        Definition] = DefinitionContainer(container_id="some definition")

    with unittest.mock.patch(
            "UM.Settings.ContainerStack.ContainerStack.deserialize",
            unittest.mock.MagicMock()):  #Prevent calling super().deserialize.
        extruder_stack.deserialize("")

    assert extruder_stack.quality == extruder_stack._empty_instance_container  #Replaced with empty.
Exemple #14
0
def test_addContainer(container_registry):
    definition_container_0 = DefinitionContainer("a", {})
    assert definition_container_0 not in container_registry.findDefinitionContainers() # Sanity check.
    container_registry.addContainer(definition_container_0)
    assert definition_container_0 in container_registry.findDefinitionContainers()

    # Add a second one of the same type.
    definition_container_1 = DefinitionContainer("b", {})
    assert definition_container_1 not in container_registry.findDefinitionContainers() # Sanity check.
    container_registry.addContainer(definition_container_1)
    assert definition_container_1 in container_registry.findDefinitionContainers()
    assert definition_container_0 in container_registry.findDefinitionContainers()

    # Add a container with the same type and same ID.
    definition_container_1_clone = DefinitionContainer("b", {})
    container_registry.addContainer(definition_container_1_clone)
    assert definition_container_1_clone not in container_registry.findDefinitionContainers() # Didn't get added!

    # For good measure, add a container with a different type too.
    instance_container_1 = InstanceContainer("a")
    assert instance_container_1 not in container_registry.findDefinitionContainers() # Sanity check.
    container_registry.addContainer(instance_container_1)
    assert instance_container_1 not in container_registry.findDefinitionContainers()
def test_deserializeRemovesWrongInstanceContainer(global_stack):
    global_stack._containers[steslicer.Settings.SteSlicerContainerStack.
                             _ContainerIndexes.Quality] = getInstanceContainer(
                                 container_type="wrong type")
    global_stack._containers[
        steslicer.Settings.SteSlicerContainerStack._ContainerIndexes.
        Definition] = DefinitionContainer(container_id="some definition")

    with unittest.mock.patch(
            "UM.Settings.ContainerStack.ContainerStack.deserialize",
            unittest.mock.MagicMock()):  #Prevent calling super().deserialize.
        global_stack.deserialize("")

    assert global_stack.quality == global_stack._empty_instance_container  #Replaced with empty.
Exemple #16
0
    def getParentMachineDefinition(self, machine_definition: DefinitionContainer) -> DefinitionContainer:
        container_registry = ContainerRegistry.getInstance()

        machine_entry = machine_definition.getMetaDataEntry("machine")
        if machine_entry is None:
            # We have a normal (whole) machine defintion
            quality_definition = machine_definition.getMetaDataEntry("quality_definition")
            if quality_definition is not None:
                parent_machine_definition = container_registry.findDefinitionContainers(id=quality_definition)[0]
                return self.getParentMachineDefinition(parent_machine_definition)
            else:
                return machine_definition
        else:
            # This looks like an extruder. Find the rest of the machine.
            whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
            parent_machine = self.getParentMachineDefinition(whole_machine)
            if whole_machine is parent_machine:
                # This extruder already belongs to a 'parent' machine def.
                return machine_definition
            else:
                # Look up the corresponding extruder definition in the parent machine definition.
                extruder_position = machine_definition.getMetaDataEntry("position")
                parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
                return container_registry.findDefinitionContainers(id=parent_extruder_id)[0]
Exemple #17
0
def test_deserializeMoveDefinitionContainer(extruder_stack):
    """Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising."""

    extruder_stack._containers[
        cura.Settings.CuraContainerStack.
        _ContainerIndexes.Material] = DefinitionContainer(
            container_id="some definition")  #Not in the correct spot.

    with unittest.mock.patch(
            "UM.Settings.ContainerStack.ContainerStack.deserialize",
            unittest.mock.MagicMock()):  #Prevent calling super().deserialize.
        extruder_stack.deserialize("")

    assert extruder_stack.material == empty_container
    assert extruder_stack.definition != empty_container
def test_deserializeMoveDefinitionContainer(extruder_stack):
    extruder_stack._containers[
        cura.Settings.CuraContainerStack.
        _ContainerIndexes.Material] = DefinitionContainer(
            container_id="some definition")  #Not in the correct spot.

    with unittest.mock.patch(
            "UM.Settings.ContainerStack.ContainerStack.deserialize",
            unittest.mock.MagicMock()):  #Prevent calling super().deserialize.
        extruder_stack.deserialize("")

    assert extruder_stack.material.getId() == "empty"
    assert extruder_stack.definition.getId() != "empty"

    UM.Settings.ContainerStack._containerRegistry = None
Exemple #19
0
    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)
Exemple #20
0
    def getParentMachineDefinition(self, machine_definition: DefinitionContainer) -> DefinitionContainer:
        container_registry = ContainerRegistry.getInstance()

        machine_entry = machine_definition.getMetaDataEntry("machine")
        if machine_entry is None:
            # We have a normal (whole) machine defintion
            quality_definition = machine_definition.getMetaDataEntry("quality_definition")
            if quality_definition is not None:
                parent_machine_definition = container_registry.findDefinitionContainers(id=quality_definition)[0]
                return self.getParentMachineDefinition(parent_machine_definition)
            else:
                return machine_definition
        else:
            # This looks like an extruder. Find the rest of the machine.
            whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
            parent_machine = self.getParentMachineDefinition(whole_machine)
            if whole_machine is parent_machine:
                # This extruder already belongs to a 'parent' machine def.
                return machine_definition
            else:
                # Look up the corresponding extruder definition in the parent machine definition.
                extruder_position = machine_definition.getMetaDataEntry("position")
                parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
                return container_registry.findDefinitionContainers(id=parent_extruder_id)[0]
def test_deserializeMoveInstanceContainer(extruder_stack):
    extruder_stack._containers[
        cura.Settings.CuraContainerStack.
        _ContainerIndexes.Quality] = getInstanceContainer(
            container_type="material")  #Not in the correct spot.
    extruder_stack._containers[
        cura.Settings.CuraContainerStack._ContainerIndexes.
        Definition] = DefinitionContainer(container_id="some definition")

    with unittest.mock.patch(
            "UM.Settings.ContainerStack.ContainerStack.deserialize",
            unittest.mock.MagicMock()):  #Prevent calling super().deserialize.
        extruder_stack.deserialize("")

    assert extruder_stack.quality.getId() == "empty"
    assert extruder_stack.material.getId() != "empty"
def isDefinitionValid(definition_container, path, file_name):
    with open(os.path.join(path, file_name), encoding = "utf-8") as data:
        json = data.read()
        parser, is_valid = definition_container.readAndValidateSerialized(json)
        if not is_valid:
            print("The definition '{0}', has invalid data.".format(file_name))
        metadata = DefinitionContainer.deserializeMetadata(json, "whatever")

        # If the definition defines a platform file, it should be in /resources/meshes/
        if "platform" in metadata[0]:
            assert metadata[0]["platform"] in all_meshes

        if "platform_texture" in metadata[0]:
            assert metadata[0]["platform_texture"] in all_images

        return is_valid
def test_deserializeMoveInstanceContainer(global_stack):
    global_stack._containers[
        steslicer.Settings.SteSlicerContainerStack.
        _ContainerIndexes.Quality] = getInstanceContainer(
            container_type="material")  #Not in the correct spot.
    global_stack._containers[
        steslicer.Settings.SteSlicerContainerStack._ContainerIndexes.
        Definition] = DefinitionContainer(container_id="some definition")

    with unittest.mock.patch(
            "UM.Settings.ContainerStack.ContainerStack.deserialize",
            unittest.mock.MagicMock()):  #Prevent calling super().deserialize.
        global_stack.deserialize("")

    assert global_stack.quality == empty_container
    assert global_stack.material != empty_container
def isDefinitionValid(definition_container, path, file_name):
    with open(os.path.join(path, file_name), encoding="utf-8") as data:
        json = data.read()
        parser, is_valid = definition_container.readAndValidateSerialized(json)
        if not is_valid:
            print("The definition '{0}', has invalid data.".format(file_name))
        metadata = DefinitionContainer.deserializeMetadata(json, "whatever")

        # If the definition defines a platform file, it should be in /resources/meshes/
        if "platform" in metadata[0]:
            assert metadata[0]["platform"] in all_meshes

        if "platform_texture" in metadata[0]:
            assert metadata[0]["platform_texture"] in all_images

        return is_valid
Exemple #25
0
def test_deserializeCompletesEmptyContainers(global_stack):
    """Tests whether deserialising completes the missing containers with empty ones. The initial containers are just the

    definition and the definition_changes (that cannot be empty after CURA-5281)
    """

    global_stack._containers = [DefinitionContainer(container_id = "definition"), global_stack.definitionChanges] #Set the internal state of this stack manually.

    with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
        global_stack.deserialize("")

    assert len(global_stack.getContainers()) == len(cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap) #Needs a slot for every type.
    for container_type_index in cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap:
        if container_type_index in \
                (cura.Settings.CuraContainerStack._ContainerIndexes.Definition, cura.Settings.CuraContainerStack._ContainerIndexes.DefinitionChanges): #We're not checking the definition or definition_changes
            continue
        assert global_stack.getContainer(container_type_index) == empty_container #All others need to be empty.
Exemple #26
0
def test_deserializeMoveInstanceContainer(extruder_stack):
    """Tests whether an instance container with the wrong type is moved into the correct slot by deserialising."""

    extruder_stack._containers[
        cura.Settings.CuraContainerStack.
        _ContainerIndexes.Quality] = getInstanceContainer(
            container_type="material")  #Not in the correct spot.
    extruder_stack._containers[
        cura.Settings.CuraContainerStack._ContainerIndexes.
        Definition] = DefinitionContainer(container_id="some definition")

    with unittest.mock.patch(
            "UM.Settings.ContainerStack.ContainerStack.deserialize",
            unittest.mock.MagicMock()):  #Prevent calling super().deserialize.
        extruder_stack.deserialize("")

    assert extruder_stack.quality == empty_container
    assert extruder_stack.material != empty_container
Exemple #27
0
    def _findInstanceContainerDefinitionId(
            cls, machine_definition: DefinitionContainer) -> str:
        quality_definition = machine_definition.getMetaDataEntry(
            "quality_definition")
        if not quality_definition:
            return machine_definition.id

        definitions = ContainerRegistry.getInstance().findDefinitionContainers(
            id=quality_definition)
        if not definitions:
            Logger.log(
                "w",
                "Unable to find parent definition {parent} for machine {machine}",
                parent=quality_definition,
                machine=machine_definition.id)
            return machine_definition.id

        return cls._findInstanceContainerDefinitionId(definitions[0])
def test_deserializeCompletesEmptyContainers(
        extruder_stack: cura.Settings.ExtruderStack):
    extruder_stack._containers = [
        DefinitionContainer(container_id="definition")
    ]  #Set the internal state of this stack manually.

    with unittest.mock.patch(
            "UM.Settings.ContainerStack.ContainerStack.deserialize",
            unittest.mock.MagicMock()):  #Prevent calling super().deserialize.
        extruder_stack.deserialize("")

    assert len(extruder_stack.getContainers()) == len(
        cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap
    )  #Needs a slot for every type.
    for container_type_index in cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap:
        if container_type_index == cura.Settings.CuraContainerStack._ContainerIndexes.Definition:  #We're not checking the definition.
            continue
        assert extruder_stack.getContainer(container_type_index).getId(
        ) == "empty"  #All others need to be empty.
Exemple #29
0
def test_containers_provider(container_provider: ContainerProvider, upgrade_manager: VersionUpgradeManager) -> ContainerProvider:
    my_folder = os.path.dirname(os.path.abspath(__file__))

    definition_ids = {"basic_definition", "children", "functions", "inherits", "metadata_definition", "multiple_settings", "single_setting"}
    for definition_id in definition_ids:
        container = DefinitionContainer(definition_id)
        container.deserialize(open(os.path.join(my_folder, "definitions", definition_id + ".def.json"), encoding = "utf-8").read())
        container_provider._containers[definition_id] = container
        container_provider.addMetadata(container.getMetaData())

    instance_ids = {"basic_instance", "metadata_instance", "setting_values"}
    for instance_id in instance_ids:
        container = UM.Settings.InstanceContainer.InstanceContainer(instance_id)
        container.deserialize(open(os.path.join(my_folder, "instances", instance_id + ".inst.cfg"), encoding = "utf-8").read())
        container_provider._containers[instance_id] = container
        container_provider.addMetadata(container.getMetaData())

    return container_provider
Exemple #30
0
    def addMachineExtruders(self, machine_definition: DefinitionContainer,
                            machine_id: str) -> None:
        changed = False
        machine_definition_id = machine_definition.getId()
        if machine_id not in self._extruder_trains:
            self._extruder_trains[machine_id] = {}
            changed = True
        container_registry = ContainerRegistry.getInstance()
        if container_registry:
            # Add the extruder trains that don't exist yet.
            for extruder_definition in container_registry.findDefinitionContainers(
                    machine=machine_definition_id):
                position = extruder_definition.getMetaDataEntry(
                    "position", None)
                if not position:
                    Logger.log(
                        "w",
                        "Extruder definition %s specifies no position metadata entry.",
                        extruder_definition.getId())
                if not container_registry.findContainerStacks(
                        machine=machine_id,
                        position=position):  # Doesn't exist yet.
                    self.createExtruderTrain(extruder_definition,
                                             machine_definition, position,
                                             machine_id)
                    changed = True

            # Gets the extruder trains that we just created as well as any that still existed.
            extruder_trains = container_registry.findContainerStacks(
                type="extruder_train", machine=machine_id)
            for extruder_train in extruder_trains:
                self._extruder_trains[machine_id][
                    extruder_train.getMetaDataEntry(
                        "position")] = extruder_train

                # regardless of what the next stack is, we have to set it again, because of signal routing.
                extruder_train.setNextStack(
                    Application.getInstance().getGlobalContainerStack())
                changed = True
        if changed:
            self.extrudersChanged.emit(machine_id)
def test_deserializeCompletesEmptyContainers(global_stack):
    global_stack._containers = [
        DefinitionContainer(container_id="definition"),
        global_stack.definitionChanges
    ]  #Set the internal state of this stack manually.

    with unittest.mock.patch(
            "UM.Settings.ContainerStack.ContainerStack.deserialize",
            unittest.mock.MagicMock()):  #Prevent calling super().deserialize.
        global_stack.deserialize("")

    assert len(global_stack.getContainers()) == len(
        steslicer.Settings.SteSlicerContainerStack._ContainerIndexes.
        IndexTypeMap)  #Needs a slot for every type.
    for container_type_index in steslicer.Settings.SteSlicerContainerStack._ContainerIndexes.IndexTypeMap:
        if container_type_index in \
                (steslicer.Settings.SteSlicerContainerStack._ContainerIndexes.Definition, steslicer.Settings.SteSlicerContainerStack._ContainerIndexes.DefinitionChanges): #We're not checking the definition or definition_changes
            continue
        assert global_stack.getContainer(
            container_type_index
        ) == empty_container  #All others need to be empty.
def test_roundtrip_definition(tmpdir, process_count, loaded_container_registry):
    definition = loaded_container_registry.findDefinitionContainers(id = "multiple_settings")[0]

    temp_file = tmpdir.join("container_stack_test")

    mp_run(process_count, write_data, temp_file, definition)

    assert len(list(tmpdir.listdir())) == 1

    results = mp_run(process_count, read_data, temp_file)

    for result in results:
        deserialized_definition = DefinitionContainer("multiple_settings")
        deserialized_definition.deserialize(result)

        assert deserialized_definition.getName() == definition.getName()
        assert deserialized_definition.getMetaData() == definition.getMetaData()
    def _saveCachedDefinition(self, definition: DefinitionContainer):
        cache_path = Resources.getStoragePath(Resources.Cache, "definitions",
                                              self._application.getVersion(),
                                              definition.id)

        # Ensure the cache path exists
        os.makedirs(os.path.dirname(cache_path), exist_ok=True)

        try:
            with open(cache_path, "wb") as f:
                pickle.dump(definition, f, pickle.HIGHEST_PROTOCOL)
        except RecursionError:
            #Sometimes a recursion error in pickling occurs here.
            #The cause is unknown. It must be some circular reference in the definition instances or definition containers.
            #Instead of saving a partial cache and raising an exception, simply fail to save the cache.
            #See CURA-4024.
            Logger.log(
                "w",
                "The definition cache for definition {definition_id} failed to pickle."
                .format(definition_id=definition.getId()))
            if os.path.exists(cache_path):
                os.remove(
                    cache_path
                )  #The pickling might be half-complete, which causes EOFError in Pickle when you load it later.

##  Tests whether adding a container is properly forbidden.
def test_addContainer(extruder_stack):
    with pytest.raises(InvalidOperationError):
        extruder_stack.addContainer(unittest.mock.MagicMock())


#Tests setting user changes profiles to invalid containers.
@pytest.mark.parametrize(
    "container",
    [
        getInstanceContainer(container_type="wrong container type"),
        getInstanceContainer(
            container_type="material"),  #Existing, but still wrong type.
        DefinitionContainer(container_id="wrong class")
    ])
def test_constrainUserChangesInvalid(container, extruder_stack):
    with pytest.raises(
            InvalidContainerError):  #Invalid container, should raise an error.
        extruder_stack.userChanges = container


#Tests setting user changes profiles.
@pytest.mark.parametrize("container", [
    getInstanceContainer(container_type="user"),
    InstanceContainerSubClass(container_type="user")
])
def test_constrainUserChangesValid(container, extruder_stack):
    extruder_stack.userChanges = container  #Should not give an error.
Exemple #35
0
def definition_container():
    return DefinitionContainer(container_id="Test Definition")
class CuraSettingsGuide(Extension, QObject):
	"""
	The main manager and entry point for the Cura Settings Guide.

	This adds a menu item to the extensions menu and to the context menu of
	every article. It creates a dialogue window to display the guide in, and
	makes the menu items open said dialogue window.

	This class is also exposed to the QML code, which can use it to request the
	articles and metadata about the article.
	"""

	def __init__(self, parent=None) -> None:
		"""
		Executed during registration of the plug-in.

		This adds a menu item to the extensions menu and the context menu of the
		settings.
		:param parent: The parent QObject this is located in. Unused by this
		particular object.
		"""
		QObject.__init__(self, parent)
		Extension.__init__(self)

		self.setMenuName("Ѕettings Guide")  # Using Cyrillic Ѕ instead of normal S to prevent MacOS detecting the word "setting" and pulling this menu item out of context.
		self.addMenuItem("Ѕettings Guide", self.startWelcomeGuide)
		self.addMenuItem("Рreferences", lambda: self.startWelcomeGuideAndSelectArticle("preferences"))
		self._dialog = None  # Cached instance of the dialogue window.
		self.definition_container = None  # Setting definitions that provide not only the normal settings but also the extra articles added by this guide.

		self._markdown_per_folder = {}  # For each directory containing Markdown files, create one renderer that correctly dereferences images relatively.

		self.articles = {}  # type: Dict[str, Dict[str, List[List[str]]]]  # All of the articles by key and language. Key: article ID, value: Dict with language as key and lists of items in each article as value.
		self.articles_rich_text = {}  # type: Dict[str, Dict[str, str]]  # For each article and language, the complete Rich Text that should get shown in the tooltip.
		self.articles_source = {}  # type: Dict[str, Dict[str, str]]  # For each article and language, the original Markdown source the article is derived from.
		self.load_definitions()
		self.article_locations = self.find_articles()
		self._selected_article_id = ""  # Which article is currently shown for the user. Empty string indicates it's the welcome screen.
		self.completed_loading = False  # Only after loading is completed can we set the tooltips.

		# Add context menu item to the settings list to open the guide for that setting.
		application = CuraApplication.getInstance()
		application.getCuraAPI().interface.settings.addContextMenuItem({
			"name": "Settings Guide",
			"icon_name": "help-contents",
			"actions": ["sidebarMenuItemOnClickHandler"],
			"menu_item": MenuItemHandler.MenuItemHandler(self)
		})

		preferences = application.getPreferences()
		preferences.addPreference("settings_guide/language", "cura_default")
		preferences.addPreference("settings_guide/show+articles+in+setting+tooltips+%28requires+restart%29", True)
		preferences.addPreference("settings_guide/window+always+in+front", False)
		preferences.addPreference("settings_guide/screenshot_tool", False)

		self.adjust_theme()
		application.initializationFinished.connect(self.load_all_in_background)
		application.initializationFinished.connect(self.widen_tooltips)
		application.globalContainerStackChanged.connect(self.set_tooltips)

	def adjust_theme(self):
		"""
		Makes the tooltips wider, if displaying articles in the tooltips.
		"""
		# Previous versions of the Settings Guide may create a theme, which may become outdated in newer versions.
		# We need to remove it and widen the tooltips in a different way.
		application = CuraApplication.getInstance()
		preferences = application.getPreferences()
		preferences.addPreference("general/theme", application.default_theme)  # May not exist yet at this point.
		current_theme = preferences.getValue("general/theme")
		if current_theme and current_theme.endswith("_settingsguideadjust"):
			preferences.setValue("general/theme", current_theme[:-len("_settingsguideadjust")])
		# Look for Settings Guide adjusted themes in the resources folder.
		theme_path = os.path.dirname(Resources.getStoragePath(Resources.Themes, ""))
		try:
			for theme_folder in os.listdir(theme_path):
				if theme_folder.endswith("_settingsguideadjust"):
					shutil.rmtree(os.path.join(theme_path, theme_folder))
		except EnvironmentError as e:
			# Perhaps no rights? Well, just leave the extra theme in then. Nothing to be done about it.
			Logger.error("Unable to remove Settings Guide deprecated theme: {err}".format(err=str(e)))

	def widen_tooltips(self):
		"""
		Make tooltips wider, so that they are easier to read.

		This really only works in Qt 5.14 and higher, so only Cura 4.9 and up can enjoy the wider tooltips.
		"""
		application = CuraApplication.getInstance()
		if application.getPreferences().getValue("settings_guide/show+articles+in+setting+tooltips+%28requires+restart%29"):
			try:
				main_window = application._qml_engine.rootObjects()[0]
				if main_window is not None:
					tooltips = main_window.findChildren(PointingRectangle)  # There are multiple instances of this (currently 3). It's indistinguishable which is the setting tooltip. Collateral damage!
					for tooltip in tooltips:
						tooltip.setWidth(tooltip.width() * 2.5)
			except IndexError:  # rootObjects() returned an empty list, meaning the main window failed to load.
				pass  # Let's skip widening the tooltips then.

	def load_all_in_background(self):
		"""
		Runs the load_all() function as a background task.
		"""
		class ArticleLoadJob(Job):
			"""
			A background task that loads all articles of the guide.
			"""

			def __init__(self, guide):
				"""
				Creates the background task.
				:param guide: The CuraSettingsGuide object which has the
				function to call.
				"""
				super().__init__()
				self.guide = guide

			def run(self):
				"""
				Runs the background task.

				Cura will call this function from a different thread.
				"""
				self.guide.load_all()
		JobQueue.getInstance().add(ArticleLoadJob(self))

	def load_all(self):
		"""
		Pre-cache all articles in all languages.

		This is meant to run as a background task. This makes sure all setting
		descriptions are loaded so that they can load faster next time the
		article is requested. It also makes sure that the setting description
		is correctly displayed (after a while).
		"""
		for article_id in self.article_locations:
			for language in self.article_locations[article_id]:
				self._getArticle(article_id, language)  # Load articles one by one.
		self.completed_loading = True
		self.set_tooltips()
		Logger.log("i", "Finished loading Settings Guide articles.")

	def load_definitions(self):
		"""
		Load all the setting definitions into a custom definition container.

		This definition container also contains extra entries for the articles
		that are not settings.

		The result is stored in `self.definition_container`.
		"""
		if self.definition_container:
			return  # Already done earlier. Don't re-load.
		with open(os.path.join(os.path.dirname(__file__), "resources", "settings_guide_definitions.def.json")) as f:
			definitions_serialised = f.read()
		self.definition_container = DefinitionContainer("settings_guide_definitions")
		self.definition_container.deserialize(definitions_serialised)
		ContainerRegistry.getInstance().addContainer(self.definition_container)

	def set_tooltips(self):
		"""
		Set the tooltips to the contents of the articles in the current
		language.
		"""
		if not self.completed_loading:
			return  # This got triggered via the global stack changed signal before all articles completed loading. It will trigger again when loading is complete.
		if not CuraApplication.getInstance().getPreferences().getValue("settings_guide/show+articles+in+setting+tooltips+%28requires+restart%29"):
			return  # User doesn't want tooltips changed.
		language = CuraApplication.getInstance().getPreferences().getValue("settings_guide/language")
		global_stack = CuraApplication.getInstance().getGlobalContainerStack()
		if not global_stack:
			return  # Fail.
		for article_id in self.articles_rich_text:
			if article_id in global_stack.getAllKeys():
				definition = global_stack.definition.findDefinitions(key=article_id)[0]
				if language in self.articles_rich_text[article_id]:
					definition._SettingDefinition__property_values["description"] = self.preprocess_tooltips(self.articles_rich_text[article_id][language])
				elif "en_US" in self.articles_rich_text[article_id]:  # Fall back to English. Or don't change it if the article doesn't exist.
					definition._SettingDefinition__property_values["description"] = self.preprocess_tooltips(self.articles_rich_text[article_id]["en_US"])

	def preprocess_tooltips(self, original_text):
		"""
		Preprocess the articles for display in tooltips.

		The tooltips need slightly different formatting. This function transforms text for display in the tooltips.
		:param original_text: Rich text that is being displayed in the main articles.
		:return: Rich text that can be displayed in the tooltips.
		"""
		# Remove all hyperlinks from this text.
		# Hyperlinks don't work when clicked on here (Cura's tooltip display code won't activate weblinks, and it can't open up the Settings Guide to display different articles).
		# However they do break through having a different colour in system-styled tooltips in the setting visibility list.
		remove_links_regex = re.compile(r"(?:<a href=\".*\">|</a>)")
		return re.sub(remove_links_regex, "", original_text)

	def find_articles(self):
		"""
		For each article and language, find where the Markdown file is
		located.
		:return: A nested dictionary mapping article ID to language to file
		path.
		"""
		result = {}
		# Find the English translations first.
		for root, _, files in os.walk(os.path.join(os.path.dirname(__file__), "resources", "articles")):
			for filename in files:
				base_name, extension = os.path.splitext(filename)
				if extension != ".md":
					continue  # Only interested in article files.
				result[base_name] = {"en_US": os.path.join(root, filename)}

		# Find the translated articles in the translations folder.
		for language in os.listdir(os.path.join(os.path.dirname(__file__), "resources", "translations")):
			language_path = os.path.join(os.path.dirname(__file__), "resources", "translations", language)
			if not os.path.isdir(language_path):
				continue  # Not a translation folder.
			for root, _, files in os.walk(language_path):
				for filename in files:
					base_name, extension = os.path.splitext(filename)
					if extension != ".md":
						continue  # Only interested in article files.
					if base_name not in result:
						continue  # Translation for an article that doesn't exist in English?
					result[base_name][language] = os.path.join(root, filename)

		return result

	def load_window(self):
		"""
		Create the GUI window for the guide.

		The window's QML element is stored in a field. There can only be one
		instance at a time.
		"""
		self.load_definitions()

		if self._dialog:  # Dialogue already open.
			self._dialog.requestActivate()  # Bring focus to dialogue.
			return
		Logger.log("d", "Creating Settings Guide window.")
		plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
		if plugin_path is None:
			plugin_path = os.path.dirname(__file__)
		application = CuraApplication.getInstance()
		version = Version(application.getVersion())
		if application.getVersion() != "source" and version.getMajor() <= 4:
			path = os.path.join(plugin_path, "resources", "qml_cura4", "SettingsGuide.qml")
		else:  # In Cura 5+ the QML interface changed significantly, and the settings layout too. We use a different set of QML files to use the new interface and to mimic the new look.
			path = os.path.join(plugin_path, "resources", "qml", "SettingsGuide.qml")
		self._dialog = application.createQmlComponent(path, {"manager": self})
		if not self._dialog:
			Logger.log("e", "Unable to create settings guide dialogue.")

	def startWelcomeGuide(self) -> None:
		"""
		Opens the guide in the welcome page.
		"""
		self.load_window()
		if not self._dialog:
			return
		self.setSelectedArticleId("")  # Display welcome page.
		self._dialog.show()

	def startWelcomeGuideAndSelectArticle(self, article_key: str) -> None:
		"""
		Opens the guide and immediately selects a certain article.
		:param article_key: The key of the article to show the guide of.
		"""
		self.load_window()
		if not self._dialog:
			return
		self.currentArticleReset.emit()
		self.setSelectedArticleId(article_key)
		self._dialog.show()

	currentArticleReset = pyqtSignal() #Signal to indicate that the article list should reset its current index.

	def _getArticle(self, article_id, language="en_US") -> List[List[str]]:
		"""
		Gets the rich text of a specified article.

		This function lazily loads an article from a file. If it's never been
		loaded before the article gets parsed and stored. Otherwise it'll get
		taken from the cache.
		:param article_id: The ID of the article to get.
		:param language: The language to get the article in.
		:return: A list of article "parts". Each article part is a list, where
		the first element indicates the type of part and the rest contains the
		content. Possible types of parts are "rich_text", "images" or
		"checkbox".
		"""
		if article_id in self.articles and language in self.articles[article_id]:
			return self.articles[article_id][language]

		images_path = os.path.join(os.path.dirname(__file__), "resources", "articles", "images")
		try:
			if language not in self.article_locations[article_id]:
				language = "en_US"  # Fall back to English if the preferred language is not available.
			markdown_file = self.article_locations[article_id][language]
			with open(markdown_file, encoding="utf-8") as f:
				markdown_str = f.read()
			images_path = os.path.dirname(markdown_file)
		except (OSError, KeyError):  # File doesn't exist or is otherwise not readable.
			if self.definition_container and article_id in self.definition_container.getAllKeys():
				markdown_str = self.definition_container.getProperty(article_id, "label") + "\n====\n"
				markdown_str += "*" + self.definition_container.getProperty(article_id, "description") + "*"  # Use the setting description as fallback.
			else:
				markdown_str = "There is no article on this topic."

		# Store this unparsed for later.
		if language not in self.articles_source:
			self.articles_source[language] = {}
		self.articles_source[language][article_id] = markdown_str

		if images_path not in self._markdown_per_folder:
			renderer = QtMarkdownRenderer.QtMarkdownRenderer(images_path)
			self._markdown_per_folder[images_path] = mistune.Markdown(renderer=renderer)  # Renders the Markdown articles into the subset of HTML supported by Qt.

		# Pre-process so that comments and conditionals don't influence the business.
		markdown_str = QtMarkdownRenderer.QtMarkdownRenderer.preprocess_conditionals(markdown_str)
		markdown_str = QtMarkdownRenderer.QtMarkdownRenderer.preprocess_comments(markdown_str)

		find_images = re.compile(r"!\[(.*)\]\(([^\)]+)\)")
		find_checkboxes = re.compile(r"\[ \]\s*([^\n]+)")
		image_description = None
		parts = []  # type: List[List[str]]  # List of items in the article. Each item starts with a type ID, and then a variable number of data items.
		for index, part_between_images in enumerate(find_images.split(markdown_str)):
			# The parts of the regex split alternate between text, image description and image URL.
			if index % 3 == 0:
				part_between_images = part_between_images.strip()
				if part_between_images or index == 0:
					parts_between_checkboxes = find_checkboxes.split(part_between_images)
					for index2, part_between_checkboxes in enumerate(parts_between_checkboxes):
						part_between_checkboxes = part_between_checkboxes.strip()
						# The parts of the regex split alternate between text and checkbox description.
						if index2 % 2 == 0:
							if part_between_checkboxes:
								rich_text = self._markdown_per_folder[images_path](part_between_checkboxes)
								parts.append(["rich_text", rich_text])
						else:  # if index2 == 1:
							preference_key = "settings_guide/" + urllib.parse.quote_plus(part_between_checkboxes).lower()
							parts.append(["checkbox", preference_key, part_between_checkboxes])
			elif index % 3 == 1:
				image_description = mistune.markdown(part_between_images)
			else:  # if index % 3 == 2:
				if image_description is not None:
					if parts[-1][0] != "images":  # List of images.
						parts.append(["images"])
					image_url = os.path.join(images_path, part_between_images)
					parts[-1].append(QUrl.fromLocalFile(image_url).toString() + "|" + image_description)
					image_description = None

		if article_id not in self.articles:
			self.articles[article_id] = {}
		self.articles[article_id][language] = parts
		if article_id not in self.articles_rich_text:
			self.articles_rich_text[article_id] = {}
		self.articles_rich_text[article_id][language] = self._markdown_per_folder[images_path](markdown_str)

		return self.articles[article_id][language]

	@pyqtSlot(str, result=bool)
	def isArticleFile(self, filename: str) -> bool:
		"""
		Tests whether a file name is the file name of an existing article.

		This test is used to determine if a link should refer to a different
		article or to the internet.
		:param filename: The file name to test for.
		:return: True if the file name is the file name of an existing article,
		or False if it isn't.
		"""
		return os.path.exists(filename)

	@pyqtProperty(str, constant=True)
	def pluginVersion(self) -> str:
		"""
		Get the version number of this plug-in.

		This plug-in version is a semantic version number in three parts
		("1.2.3").
		:return: The version number of this plug-in.
		"""
		return self.getVersion()

	selectedArticleChanged = pyqtSignal()

	@pyqtSlot(str)
	def setSelectedArticleId(self, article_key: str) -> None:
		"""
		Show a certain article in the dialogue.
		:param article_key: The key of the article to display.
		"""
		if self._selected_article_id != article_key:
			self._selected_article_id = article_key
			self.selectedArticleChanged.emit()

	@pyqtProperty(str, fset=setSelectedArticleId, notify=selectedArticleChanged)
	def selectedArticleId(self) -> str:
		"""
		Returns the key of the article that is currently selected.

		If no article has been selected (the logo is being shown), an empty
		string is returned.
		:return: The key of the article that is currently selected.
		"""
		return self._selected_article_id

	@pyqtProperty("QVariantList", notify=selectedArticleChanged)
	def selectedArticle(self) -> List[List[str]]:
		"""
		Returns the currently selected article.

		This article data is a rich text document, properly formatted from the
		Markdown files in the article directory. Each article is a list of
		items, some of which are text and some of which are image lists.
		:return: The the currently selected article.
		"""
		preferences = CuraApplication.getInstance().getPreferences()
		language = preferences.getValue("settings_guide/language")
		if language == "cura_default":
			language = preferences.getValue("general/language")
		return self._getArticle(self._selected_article_id, language)

	@pyqtSlot(str, result="QVariantList")
	def language_list(self, article_key: str) -> List[str]:
		"""
		Gets a list of available languages for a certain article.
		:param article_key: The article ID to get the languages for.
		:return: A list of language codes (e.g. [nl_NL, en_US]).
		"""
		if article_key not in self.article_locations:
			return []  # We have no articles about this setting at all.
		return list(self.article_locations[article_key].keys())

	@pyqtSlot(str)
	def set_language(self, language_code: str) -> None:
		"""
		Changes the viewing language.
		:param language_code: The new language code.
		"""
		preferences = CuraApplication.getInstance().getPreferences()
		preferences.setValue("settings_guide/language", language_code)
		if preferences.getValue("settings_guide/show+articles+in+setting+tooltips+%28requires+restart%29"):
			self.set_tooltips()
		self.selectedArticleChanged.emit()

	@pyqtSlot()
	def refresh_screenshots(self) -> None:
		"""
		Refresh the screenshots on the current article.

		This starts the process outlined in the ScreenshotTool class.
		"""
		if not has_screenshot_tool:
			Logger.error("The screenshot tool is not installed in this version of the Settings Guide. Please use the version from the source repository.")
			return
		if self.selectedArticleId:  # Refresh a particular article.
			preferences = CuraApplication.getInstance().getPreferences()
			language = preferences.getValue("settings_guide/language")
			if language == "cura_default":
				language = preferences.getValue("general/language")
			refresh_job = threading.Thread(target=ScreenshotTool.refresh_screenshots, kwargs={"article_text": self.articles_source[language][self.selectedArticleId], "refreshed_set": set()})
			refresh_job.start()
		else:  # Refresh everything.
			refreshed_set = set()  # Don't refresh the same image multiple times. Share the same set among all calls.
			def refresh_everything():
				Logger.info("Refreshing ALL screenshots.")
				for article_per_language in self.articles_source.values():
					for article in article_per_language.values():
						ScreenshotTool.refresh_screenshots(article_text=article, refreshed_set=refreshed_set)
			refresh_job = threading.Thread(target=refresh_everything)
			refresh_job.start()
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._stack.addContainer(self._instance)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()

    ##  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.
    def getSettingData(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 preRead(self, file_name, show_dialog=True, *args, **kwargs):
        self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
        if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
            pass
        else:
            Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
            return WorkspaceReader.PreReadResult.failed

        machine_name = ""
        machine_type = ""
        variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")

        # Check if there are any conflicts, so we can ask the user.
        archive = zipfile.ZipFile(file_name, "r")
        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]

        # A few lists of containers in this project files.
        # When loading the global stack file, it may be associated with those containers, which may or may not be
        # in Cura already, so we need to provide them as alternative search lists.
        definition_container_list = []
        instance_container_list = []
        material_container_list = []

        #
        # Read definition containers
        #
        machine_definition_container_count = 0
        extruder_definition_container_count = 0
        definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
        for each_definition_container_file in definition_container_files:
            container_id = self._stripFileToId(each_definition_container_file)
            definitions = self._container_registry.findDefinitionContainers(id=container_id)

            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"))

            else:
                definition_container = definitions[0]
            definition_container_list.append(definition_container)

            definition_container_type = definition_container.getMetaDataEntry("type")
            if definition_container_type == "machine":
                machine_type = definition_container.getName()
                variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)

                machine_definition_container_count += 1
            elif definition_container_type == "extruder":
                extruder_definition_container_count += 1
            else:
                Logger.log("w", "Unknown definition container type %s for %s",
                           definition_container_type, each_definition_container_file)
            Job.yieldThread()
        # sanity check
        if machine_definition_container_count != 1:
            msg = "Expecting one machine definition container but got %s" % machine_definition_container_count
            Logger.log("e", msg)
            raise RuntimeError(msg)

        material_labels = []
        material_conflict = False
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
        if xml_material_profile:
            material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
            for material_container_file in material_container_files:
                container_id = self._stripFileToId(material_container_file)
                materials = self._container_registry.findInstanceContainers(id=container_id)
                material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8")))
                if materials and not materials[0].isReadOnly():  # Only non readonly materials can be in conflict
                    material_conflict = True
                Job.yieldThread()

        # Check if any quality_changes instance container is in conflict.
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        quality_name = ""
        quality_type = ""
        num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
        num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes
        num_user_settings = 0
        quality_changes_conflict = False
        definition_changes_conflict = False

        for each_instance_container_file in instance_container_files:
            container_id = self._stripFileToId(each_instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"))
            instance_container_list.append(instance_container)

            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "quality_changes":
                quality_name = instance_container.getName()
                num_settings_overriden_by_quality_changes += len(instance_container._instances)
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(id = container_id)
                if quality_changes:
                    # Check if there really is a conflict by comparing the values
                    if quality_changes[0] != instance_container:
                        quality_changes_conflict = True
            elif container_type == "definition_changes":
                definition_name = instance_container.getName()
                num_settings_overriden_by_definition_changes += len(instance_container._instances)
                definition_changes = self._container_registry.findDefinitionContainers(id = container_id)
                if definition_changes:
                    if definition_changes[0] != instance_container:
                        definition_changes_conflict = True
            elif container_type == "user":
                num_user_settings += len(instance_container._instances)
            elif container_type in self._ignored_instance_container_types:
                # Ignore certain instance container types
                Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
                continue

            Job.yieldThread()

        # Load ContainerStack files and ExtruderStack files
        global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(
            file_name, cura_file_names)
        self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
        machine_conflict = False
        for container_stack_file in [global_stack_file] + extruder_stack_files:
            container_id = self._stripFileToId(container_stack_file)
            serialized = archive.open(container_stack_file).read().decode("utf-8")
            if machine_name == "":
                machine_name = self._getMachineNameFromSerializedStack(serialized)
            stacks = self._container_registry.findContainerStacks(id = container_id)
            if stacks:
                # Check if there are any changes at all in any of the container stacks.
                id_list = self._getContainerIdListFromSerialized(serialized)
                for index, container_id in enumerate(id_list):
                    if stacks[0].getContainer(index).getId() != container_id:
                        machine_conflict = True
            Job.yieldThread()

        num_visible_settings = 0
        try:
            temp_preferences = Preferences()
            temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

            visible_settings_string = temp_preferences.getValue("general/visible_settings")
            if visible_settings_string is not None:
                num_visible_settings = len(visible_settings_string.split(";"))
            active_mode = temp_preferences.getValue("cura/active_mode")
            if not active_mode:
                active_mode = Preferences.getInstance().getValue("cura/active_mode")
        except KeyError:
            # If there is no preferences file, it's not a workspace, so notify user of failure.
            Logger.log("w", "File %s is not a valid workspace.", file_name)
            return WorkspaceReader.PreReadResult.failed

        # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
        if not show_dialog:
            return WorkspaceReader.PreReadResult.accepted

        # prepare data for the dialog
        num_extruders = extruder_definition_container_count
        if num_extruders == 0:
            num_extruders = 1  # No extruder stacks found, which means there is one extruder

        extruders = num_extruders * [""]

        # Show the dialog, informing the user what is about to happen.
        self._dialog.setMachineConflict(machine_conflict)
        self._dialog.setQualityChangesConflict(quality_changes_conflict)
        self._dialog.setDefinitionChangesConflict(definition_changes_conflict)
        self._dialog.setMaterialConflict(material_conflict)
        self._dialog.setNumVisibleSettings(num_visible_settings)
        self._dialog.setQualityName(quality_name)
        self._dialog.setQualityType(quality_type)
        self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
        self._dialog.setNumUserSettings(num_user_settings)
        self._dialog.setActiveMode(active_mode)
        self._dialog.setMachineName(machine_name)
        self._dialog.setMaterialLabels(material_labels)
        self._dialog.setMachineType(machine_type)
        self._dialog.setExtruders(extruders)
        self._dialog.setVariantType(variant_type_name)
        self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
        self._dialog.show()

        # Block until the dialog is closed.
        self._dialog.waitForClose()

        if self._dialog.getResult() == {}:
            return WorkspaceReader.PreReadResult.cancelled

        self._resolve_strategies = self._dialog.getResult()
        #
        # There can be 3 resolve strategies coming from the dialog:
        #  - new:       create a new container
        #  - override:  override the existing container
        #  - None:      There is no conflict, which means containers with the same IDs may or may not be there already.
        #               If they are there, there is no conflict between the them.
        #               In this case, you can either create a new one, or safely override the existing one.
        #
        # Default values
        for k, v in self._resolve_strategies.items():
            if v is None:
                self._resolve_strategies[k] = "new"

        return WorkspaceReader.PreReadResult.accepted
Exemple #39
0
    def preRead(self, file_name, show_dialog=True, *args, **kwargs):
        self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
        if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
            pass
        else:
            Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
            return WorkspaceReader.PreReadResult.failed

        machine_name = ""
        machine_type = ""
        variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")

        num_extruders = 0
        # Check if there are any conflicts, so we can ask the user.
        archive = zipfile.ZipFile(file_name, "r")
        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
        container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
        self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
        machine_conflict = False
        quality_changes_conflict = False
        for container_stack_file in container_stack_files:
            container_id = self._stripFileToId(container_stack_file)
            serialized = archive.open(container_stack_file).read().decode("utf-8")
            if machine_name == "":
                machine_name = self._getMachineNameFromSerializedStack(serialized)
            stacks = self._container_registry.findContainerStacks(id=container_id)
            if stacks:
                # Check if there are any changes at all in any of the container stacks.
                id_list = self._getContainerIdListFromSerialized(serialized)
                for index, container_id in enumerate(id_list):
                    if stacks[0].getContainer(index).getId() != container_id:
                        machine_conflict = True
            Job.yieldThread()

        definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
        for definition_container_file in definition_container_files:
            container_id = self._stripFileToId(definition_container_file)
            definitions = self._container_registry.findDefinitionContainers(id=container_id)

            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))

            else:
                definition_container = definitions[0]

            if definition_container.getMetaDataEntry("type") != "extruder":
                machine_type = definition_container.getName()
                variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)
            else:
                num_extruders += 1
            Job.yieldThread()

        if num_extruders == 0:
            num_extruders = 1 # No extruder stacks found, which means there is one extruder

        extruders = num_extruders * [""]

        material_labels = []
        material_conflict = False
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
        if xml_material_profile:
            material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
            for material_container_file in material_container_files:
                container_id = self._stripFileToId(material_container_file)
                materials = self._container_registry.findInstanceContainers(id=container_id)
                material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8")))
                if materials and not materials[0].isReadOnly():  # Only non readonly materials can be in conflict
                    material_conflict = True
                Job.yieldThread()
        # Check if any quality_changes instance container is in conflict.
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        quality_name = ""
        quality_type = ""
        num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
        num_user_settings = 0
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "quality_changes":
                quality_name = instance_container.getName()
                num_settings_overriden_by_quality_changes += len(instance_container._instances)
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(id = container_id)
                if quality_changes:
                    # Check if there really is a conflict by comparing the values
                    if quality_changes[0] != instance_container:
                        quality_changes_conflict = True
            elif container_type == "quality":
                # If the quality name is not set (either by quality or changes, set it now)
                # Quality changes should always override this (as they are "on top")
                if quality_name == "":
                    quality_name = instance_container.getName()
                quality_type = instance_container.getName()
            elif container_type == "user":
                num_user_settings += len(instance_container._instances)

            Job.yieldThread()
        num_visible_settings = 0
        try:
            temp_preferences = Preferences()
            temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

            visible_settings_string = temp_preferences.getValue("general/visible_settings")
            if visible_settings_string is not None:
                num_visible_settings = len(visible_settings_string.split(";"))
            active_mode = temp_preferences.getValue("cura/active_mode")
            if not active_mode:
                active_mode = Preferences.getInstance().getValue("cura/active_mode")
        except KeyError:
            # If there is no preferences file, it's not a workspace, so notify user of failure.
            Logger.log("w", "File %s is not a valid workspace.", file_name)
            return WorkspaceReader.PreReadResult.failed

        # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
        if not show_dialog:
            return WorkspaceReader.PreReadResult.accepted

        # Show the dialog, informing the user what is about to happen.
        self._dialog.setMachineConflict(machine_conflict)
        self._dialog.setQualityChangesConflict(quality_changes_conflict)
        self._dialog.setMaterialConflict(material_conflict)
        self._dialog.setNumVisibleSettings(num_visible_settings)
        self._dialog.setQualityName(quality_name)
        self._dialog.setQualityType(quality_type)
        self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
        self._dialog.setNumUserSettings(num_user_settings)
        self._dialog.setActiveMode(active_mode)
        self._dialog.setMachineName(machine_name)
        self._dialog.setMaterialLabels(material_labels)
        self._dialog.setMachineType(machine_type)
        self._dialog.setExtruders(extruders)
        self._dialog.setVariantType(variant_type_name)
        self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
        self._dialog.show()

        # Block until the dialog is closed.
        self._dialog.waitForClose()

        if self._dialog.getResult() == {}:
            return WorkspaceReader.PreReadResult.cancelled

        self._resolve_strategies = self._dialog.getResult()

        return WorkspaceReader.PreReadResult.accepted
    def preRead(self, file_name, show_dialog=True, *args, **kwargs):
        self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler(
        ).getReaderForFile(file_name)
        if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(
                file_name) == WorkspaceReader.PreReadResult.accepted:
            pass
        else:
            Logger.log(
                "w",
                "Could not find reader that was able to read the scene data for 3MF workspace"
            )
            return WorkspaceReader.PreReadResult.failed

        machine_name = ""
        machine_type = ""
        variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")

        num_extruders = 0
        # Check if there are any conflicts, so we can ask the user.
        archive = zipfile.ZipFile(file_name, "r")
        cura_file_names = [
            name for name in archive.namelist() if name.startswith("Cura/")
        ]
        container_stack_files = [
            name for name in cura_file_names
            if name.endswith(self._container_stack_suffix)
        ]
        self._resolve_strategies = {
            "machine": None,
            "quality_changes": None,
            "material": None
        }
        machine_conflict = False
        quality_changes_conflict = False
        for container_stack_file in container_stack_files:
            container_id = self._stripFileToId(container_stack_file)
            serialized = archive.open(container_stack_file).read().decode(
                "utf-8")
            if machine_name == "":
                machine_name = self._getMachineNameFromSerializedStack(
                    serialized)
            stacks = self._container_registry.findContainerStacks(
                id=container_id)
            if stacks:
                # Check if there are any changes at all in any of the container stacks.
                id_list = self._getContainerIdListFromSerialized(serialized)
                for index, container_id in enumerate(id_list):
                    if stacks[0].getContainer(index).getId() != container_id:
                        machine_conflict = True
            Job.yieldThread()

        definition_container_files = [
            name for name in cura_file_names
            if name.endswith(self._definition_container_suffix)
        ]
        for definition_container_file in definition_container_files:
            container_id = self._stripFileToId(definition_container_file)
            definitions = self._container_registry.findDefinitionContainers(
                id=container_id)

            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(
                    archive.open(definition_container_file).read().decode(
                        "utf-8"))

            else:
                definition_container = definitions[0]

            if definition_container.getMetaDataEntry("type") != "extruder":
                machine_type = definition_container.getName()
                variant_type_name = definition_container.getMetaDataEntry(
                    "variants_name", variant_type_name)
            else:
                num_extruders += 1
            Job.yieldThread()

        if num_extruders == 0:
            num_extruders = 1  # No extruder stacks found, which means there is one extruder

        extruders = num_extruders * [""]

        material_labels = []
        material_conflict = False
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(
                xml_material_profile).preferredSuffix
        if xml_material_profile:
            material_container_files = [
                name for name in cura_file_names
                if name.endswith(self._material_container_suffix)
            ]
            for material_container_file in material_container_files:
                container_id = self._stripFileToId(material_container_file)
                materials = self._container_registry.findInstanceContainers(
                    id=container_id)
                material_labels.append(
                    self._getMaterialLabelFromSerialized(
                        archive.open(material_container_file).read().decode(
                            "utf-8")))
                if materials and not materials[0].isReadOnly(
                ):  # Only non readonly materials can be in conflict
                    material_conflict = True
                Job.yieldThread()
        # Check if any quality_changes instance container is in conflict.
        instance_container_files = [
            name for name in cura_file_names
            if name.endswith(self._instance_container_suffix)
        ]
        quality_name = ""
        quality_type = ""
        num_settings_overriden_by_quality_changes = 0  # How many settings are changed by the quality changes
        num_user_settings = 0
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(
                archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "quality_changes":
                quality_name = instance_container.getName()
                num_settings_overriden_by_quality_changes += len(
                    instance_container._instances)
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(
                    id=container_id)
                if quality_changes:
                    # Check if there really is a conflict by comparing the values
                    if quality_changes[0] != instance_container:
                        quality_changes_conflict = True
            elif container_type == "quality":
                # If the quality name is not set (either by quality or changes, set it now)
                # Quality changes should always override this (as they are "on top")
                if quality_name == "":
                    quality_name = instance_container.getName()
                quality_type = instance_container.getName()
            elif container_type == "user":
                num_user_settings += len(instance_container._instances)

            Job.yieldThread()
        num_visible_settings = 0
        try:
            temp_preferences = Preferences()
            temp_preferences.readFromFile(
                io.TextIOWrapper(archive.open("Cura/preferences.cfg"))
            )  # We need to wrap it, else the archive parser breaks.

            visible_settings_string = temp_preferences.getValue(
                "general/visible_settings")
            if visible_settings_string is not None:
                num_visible_settings = len(visible_settings_string.split(";"))
            active_mode = temp_preferences.getValue("cura/active_mode")
            if not active_mode:
                active_mode = Preferences.getInstance().getValue(
                    "cura/active_mode")
        except KeyError:
            # If there is no preferences file, it's not a workspace, so notify user of failure.
            Logger.log("w", "File %s is not a valid workspace.", file_name)
            return WorkspaceReader.PreReadResult.failed

        # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
        if not show_dialog:
            return WorkspaceReader.PreReadResult.accepted

        # Show the dialog, informing the user what is about to happen.
        self._dialog.setMachineConflict(machine_conflict)
        self._dialog.setQualityChangesConflict(quality_changes_conflict)
        self._dialog.setMaterialConflict(material_conflict)
        self._dialog.setNumVisibleSettings(num_visible_settings)
        self._dialog.setQualityName(quality_name)
        self._dialog.setQualityType(quality_type)
        self._dialog.setNumSettingsOverridenByQualityChanges(
            num_settings_overriden_by_quality_changes)
        self._dialog.setNumUserSettings(num_user_settings)
        self._dialog.setActiveMode(active_mode)
        self._dialog.setMachineName(machine_name)
        self._dialog.setMaterialLabels(material_labels)
        self._dialog.setMachineType(machine_type)
        self._dialog.setExtruders(extruders)
        self._dialog.setVariantType(variant_type_name)
        self._dialog.setHasObjectsOnPlate(
            Application.getInstance().platformActivity)
        self._dialog.show()

        # Block until the dialog is closed.
        self._dialog.waitForClose()

        if self._dialog.getResult() == {}:
            return WorkspaceReader.PreReadResult.cancelled

        self._resolve_strategies = self._dialog.getResult()

        return WorkspaceReader.PreReadResult.accepted
    def read(self, file_name):
        archive = zipfile.ZipFile(file_name, "r")

        cura_file_names = [
            name for name in archive.namelist() if name.startswith("Cura/")
        ]

        # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
        # parsing code.
        temp_preferences = Preferences()
        temp_preferences.readFromFile(
            io.TextIOWrapper(archive.open("Cura/preferences.cfg"))
        )  # We need to wrap it, else the archive parser breaks.

        # Copy a number of settings from the temp preferences to the global
        global_preferences = Preferences.getInstance()

        visible_settings = temp_preferences.getValue(
            "general/visible_settings")
        if visible_settings is None:
            Logger.log(
                "w",
                "Workspace did not contain visible settings. Leaving visibility unchanged"
            )
        else:
            global_preferences.setValue("general/visible_settings",
                                        visible_settings)

        categories_expanded = temp_preferences.getValue(
            "cura/categories_expanded")
        if categories_expanded is None:
            Logger.log(
                "w",
                "Workspace did not contain expanded categories. Leaving them unchanged"
            )
        else:
            global_preferences.setValue("cura/categories_expanded",
                                        categories_expanded)

        Application.getInstance().expandedCategoriesChanged.emit(
        )  # Notify the GUI of the change

        self._id_mapping = {}

        # We don't add containers right away, but wait right until right before the stack serialization.
        # We do this so that if something goes wrong, it's easier to clean up.
        containers_to_add = []

        # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
        # TODO: cases that the container loaded is the same (most notable in materials & definitions).
        # TODO: It might be possible that we need to add smarter checking in the future.
        Logger.log("d", "Workspace loading is checking definitions...")
        # Get all the definition files & check if they exist. If not, add them.
        definition_container_files = [
            name for name in cura_file_names
            if name.endswith(self._definition_container_suffix)
        ]
        for definition_container_file in definition_container_files:
            container_id = self._stripFileToId(definition_container_file)
            definitions = self._container_registry.findDefinitionContainers(
                id=container_id)
            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(
                    archive.open(definition_container_file).read().decode(
                        "utf-8"))
                self._container_registry.addContainer(definition_container)
            Job.yieldThread()

        Logger.log("d", "Workspace loading is checking materials...")
        material_containers = []
        # Get all the material files and check if they exist. If not, add them.
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(
                xml_material_profile).suffixes[0]
        if xml_material_profile:
            material_container_files = [
                name for name in cura_file_names
                if name.endswith(self._material_container_suffix)
            ]
            for material_container_file in material_container_files:
                container_id = self._stripFileToId(material_container_file)
                materials = self._container_registry.findInstanceContainers(
                    id=container_id)
                if not materials:
                    material_container = xml_material_profile(container_id)
                    material_container.deserialize(
                        archive.open(material_container_file).read().decode(
                            "utf-8"))
                    containers_to_add.append(material_container)
                else:
                    if not materials[0].isReadOnly(
                    ):  # Only create new materials if they are not read only.
                        if self._resolve_strategies["material"] == "override":
                            materials[0].deserialize(
                                archive.open(material_container_file).read().
                                decode("utf-8"))
                        elif self._resolve_strategies["material"] == "new":
                            # Note that we *must* deserialize it with a new ID, as multiple containers will be
                            # auto created & added.
                            material_container = xml_material_profile(
                                self.getNewId(container_id))
                            material_container.deserialize(
                                archive.open(material_container_file).read().
                                decode("utf-8"))
                            containers_to_add.append(material_container)
                            material_containers.append(material_container)
                Job.yieldThread()

        Logger.log("d", "Workspace loading is checking instance containers...")
        # Get quality_changes and user profiles saved in the workspace
        instance_container_files = [
            name for name in cura_file_names
            if name.endswith(self._instance_container_suffix)
        ]
        user_instance_containers = []
        quality_changes_instance_containers = []
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(
                archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            Job.yieldThread()
            if container_type == "user":
                # Check if quality changes already exists.
                user_containers = self._container_registry.findInstanceContainers(
                    id=container_id)
                if not user_containers:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies[
                            "machine"] == "override" or self._resolve_strategies[
                                "machine"] is None:
                        user_containers[0].deserialize(
                            archive.open(instance_container_file).read().
                            decode("utf-8"))
                    elif self._resolve_strategies["machine"] == "new":
                        # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                        extruder_id = instance_container.getMetaDataEntry(
                            "extruder", None)
                        if extruder_id:
                            new_id = self.getNewId(
                                extruder_id) + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry(
                                "extruder", self.getNewId(extruder_id))
                            containers_to_add.append(instance_container)

                        machine_id = instance_container.getMetaDataEntry(
                            "machine", None)
                        if machine_id:
                            new_id = self.getNewId(
                                machine_id) + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry(
                                "machine", self.getNewId(machine_id))
                            containers_to_add.append(instance_container)
                user_instance_containers.append(instance_container)
            elif container_type == "quality_changes":
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(
                    id=container_id)
                if not quality_changes:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies[
                            "quality_changes"] == "override":
                        quality_changes[0].deserialize(
                            archive.open(instance_container_file).read().
                            decode("utf-8"))
                    elif self._resolve_strategies["quality_changes"] is None:
                        # The ID already exists, but nothing in the values changed, so do nothing.
                        pass
                quality_changes_instance_containers.append(instance_container)
            else:
                continue

        # Add all the containers right before we try to add / serialize the stack
        for container in containers_to_add:
            self._container_registry.addContainer(container)
            container.setDirty(True)

        # Get the stack(s) saved in the workspace.
        Logger.log("d", "Workspace loading is checking stacks containers...")
        container_stack_files = [
            name for name in cura_file_names
            if name.endswith(self._container_stack_suffix)
        ]
        global_stack = None
        extruder_stacks = []
        container_stacks_added = []
        try:
            for container_stack_file in container_stack_files:
                container_id = self._stripFileToId(container_stack_file)

                # Check if a stack by this ID already exists;
                container_stacks = self._container_registry.findContainerStacks(
                    id=container_id)
                if container_stacks:
                    stack = container_stacks[0]
                    if self._resolve_strategies["machine"] == "override":
                        # TODO: HACK
                        # There is a machine, check if it has authenticationd data. If so, keep that data.
                        network_authentication_id = container_stacks[
                            0].getMetaDataEntry("network_authentication_id")
                        network_authentication_key = container_stacks[
                            0].getMetaDataEntry("network_authentication_key")
                        container_stacks[0].deserialize(
                            archive.open(container_stack_file).read().decode(
                                "utf-8"))
                        if network_authentication_id:
                            container_stacks[0].addMetaDataEntry(
                                "network_authentication_id",
                                network_authentication_id)
                        if network_authentication_key:
                            container_stacks[0].addMetaDataEntry(
                                "network_authentication_key",
                                network_authentication_key)
                    elif self._resolve_strategies["machine"] == "new":
                        new_id = self.getNewId(container_id)
                        stack = ContainerStack(new_id)
                        stack.deserialize(
                            archive.open(container_stack_file).read().decode(
                                "utf-8"))

                        # Ensure a unique ID and name
                        stack._id = new_id

                        # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
                        # bound machine also needs to change.
                        if stack.getMetaDataEntry("machine", None):
                            stack.setMetaDataEntry(
                                "machine",
                                self.getNewId(
                                    stack.getMetaDataEntry("machine")))

                        if stack.getMetaDataEntry("type") != "extruder_train":
                            # Only machines need a new name, stacks may be non-unique
                            stack.setName(
                                self._container_registry.uniqueName(
                                    stack.getName()))
                        container_stacks_added.append(stack)
                        self._container_registry.addContainer(stack)
                    else:
                        Logger.log(
                            "w",
                            "Resolve strategy of %s for machine is not supported",
                            self._resolve_strategies["machine"])
                else:
                    stack = ContainerStack(container_id)
                    # Deserialize stack by converting read data from bytes to string
                    stack.deserialize(
                        archive.open(container_stack_file).read().decode(
                            "utf-8"))
                    container_stacks_added.append(stack)
                    self._container_registry.addContainer(stack)

                if stack.getMetaDataEntry("type") == "extruder_train":
                    extruder_stacks.append(stack)
                else:
                    global_stack = stack
                Job.yieldThread()
        except:
            Logger.logException(
                "w", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added.
            for container in containers_to_add:
                self._container_registry.getInstance().removeContainer(
                    container.getId())

            for container in container_stacks_added:
                self._container_registry.getInstance().removeContainer(
                    container.getId())

            return None

        if self._resolve_strategies["machine"] == "new":
            # A new machine was made, but it was serialized with the wrong user container. Fix that now.
            for container in user_instance_containers:
                extruder_id = container.getMetaDataEntry("extruder", None)
                if extruder_id:
                    for extruder in extruder_stacks:
                        if extruder.getId() == extruder_id:
                            extruder.replaceContainer(0, container)
                            continue
                machine_id = container.getMetaDataEntry("machine", None)
                if machine_id:
                    if global_stack.getId() == machine_id:
                        global_stack.replaceContainer(0, container)
                        continue

        if self._resolve_strategies["quality_changes"] == "new":
            # Quality changes needs to get a new ID, added to registry and to the right stacks
            for container in quality_changes_instance_containers:
                old_id = container.getId()
                container.setName(
                    self._container_registry.uniqueName(container.getName()))
                # We're not really supposed to change the ID in normal cases, but this is an exception.
                container._id = self.getNewId(container.getId())

                # The container was not added yet, as it didn't have an unique ID. It does now, so add it.
                self._container_registry.addContainer(container)

                # Replace the quality changes container
                old_container = global_stack.findContainer(
                    {"type": "quality_changes"})
                if old_container.getId() == old_id:
                    quality_changes_index = global_stack.getContainerIndex(
                        old_container)
                    global_stack.replaceContainer(quality_changes_index,
                                                  container)
                    continue

                for stack in extruder_stacks:
                    old_container = stack.findContainer(
                        {"type": "quality_changes"})
                    if old_container.getId() == old_id:
                        quality_changes_index = stack.getContainerIndex(
                            old_container)
                        stack.replaceContainer(quality_changes_index,
                                               container)

        if self._resolve_strategies["material"] == "new":
            for material in material_containers:
                old_material = global_stack.findContainer({"type": "material"})
                if old_material.getId() in self._id_mapping:
                    material_index = global_stack.getContainerIndex(
                        old_material)
                    global_stack.replaceContainer(material_index, material)
                    continue

                for stack in extruder_stacks:
                    old_material = stack.findContainer({"type": "material"})
                    if old_material.getId() in self._id_mapping:
                        material_index = stack.getContainerIndex(old_material)
                        stack.replaceContainer(material_index, material)
                        continue

        for stack in extruder_stacks:
            ExtruderManager.getInstance().registerExtruder(
                stack, global_stack.getId())
        else:
            # Machine has no extruders, but it needs to be registered with the extruder manager.
            ExtruderManager.getInstance().registerExtruder(
                None, global_stack.getId())

        Logger.log(
            "d",
            "Workspace loading is notifying rest of the code of changes...")

        # Notify everything/one that is to notify about changes.
        global_stack.containersChanged.emit(global_stack.getTop())

        for stack in extruder_stacks:
            stack.setNextStack(global_stack)
            stack.containersChanged.emit(stack.getTop())

        # Actually change the active machine.
        Application.getInstance().setGlobalContainerStack(global_stack)

        # Load all the nodes / meshdata of the workspace
        nodes = self._3mf_mesh_reader.read(file_name)
        if nodes is None:
            nodes = []
        return nodes
    def read(self, file_name):
        archive = zipfile.ZipFile(file_name, "r")

        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]

        # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
        # parsing code.
        temp_preferences = Preferences()
        temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

        # Copy a number of settings from the temp preferences to the global
        global_preferences = Preferences.getInstance()

        visible_settings = temp_preferences.getValue("general/visible_settings")
        if visible_settings is None:
            Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
        else:
            global_preferences.setValue("general/visible_settings", visible_settings)

        categories_expanded = temp_preferences.getValue("cura/categories_expanded")
        if categories_expanded is None:
            Logger.log("w", "Workspace did not contain expanded categories. Leaving them unchanged")
        else:
            global_preferences.setValue("cura/categories_expanded", categories_expanded)

        Application.getInstance().expandedCategoriesChanged.emit()  # Notify the GUI of the change

        self._id_mapping = {}

        # We don't add containers right away, but wait right until right before the stack serialization.
        # We do this so that if something goes wrong, it's easier to clean up.
        containers_to_add = []

        global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names)

        global_stack = None
        extruder_stacks = []
        extruder_stacks_added = []
        container_stacks_added = []

        containers_added = []

        global_stack_id_original = self._stripFileToId(global_stack_file)
        global_stack_id_new = global_stack_id_original
        global_stack_need_rename = False

        extruder_stack_id_map = {}  # new and old ExtruderStack IDs map
        if self._resolve_strategies["machine"] == "new":
            # We need a new id if the id already exists
            if self._container_registry.findContainerStacks(id = global_stack_id_original):
                global_stack_id_new = self.getNewId(global_stack_id_original)
                global_stack_need_rename = True

            for each_extruder_stack_file in extruder_stack_files:
                old_container_id = self._stripFileToId(each_extruder_stack_file)
                new_container_id = old_container_id
                if self._container_registry.findContainerStacks(id = old_container_id):
                    # get a new name for this extruder
                    new_container_id = self.getNewId(old_container_id)

                extruder_stack_id_map[old_container_id] = new_container_id

        # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
        # TODO: cases that the container loaded is the same (most notable in materials & definitions).
        # TODO: It might be possible that we need to add smarter checking in the future.
        Logger.log("d", "Workspace loading is checking definitions...")
        # Get all the definition files & check if they exist. If not, add them.
        definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
        for definition_container_file in definition_container_files:
            container_id = self._stripFileToId(definition_container_file)
            definitions = self._container_registry.findDefinitionContainers(id = container_id)
            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
                self._container_registry.addContainer(definition_container)
            Job.yieldThread()

        Logger.log("d", "Workspace loading is checking materials...")
        material_containers = []
        # Get all the material files and check if they exist. If not, add them.
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
        if xml_material_profile:
            material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
            for material_container_file in material_container_files:
                container_id = self._stripFileToId(material_container_file)
                materials = self._container_registry.findInstanceContainers(id = container_id)

                if not materials:
                    material_container = xml_material_profile(container_id)
                    material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                    containers_to_add.append(material_container)
                else:
                    material_container = materials[0]
                    if not material_container.isReadOnly():  # Only create new materials if they are not read only.
                        if self._resolve_strategies["material"] == "override":
                            material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                        elif self._resolve_strategies["material"] == "new":
                            # Note that we *must* deserialize it with a new ID, as multiple containers will be
                            # auto created & added.
                            material_container = xml_material_profile(self.getNewId(container_id))
                            material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                            containers_to_add.append(material_container)

                material_containers.append(material_container)
                Job.yieldThread()

        Logger.log("d", "Workspace loading is checking instance containers...")
        # Get quality_changes and user profiles saved in the workspace
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        user_instance_containers = []
        quality_and_definition_changes_instance_containers = []
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            serialized = archive.open(instance_container_file).read().decode("utf-8")

            # HACK! we ignore "quality" and "variant" instance containers!
            parser = configparser.ConfigParser()
            parser.read_string(serialized)
            if not parser.has_option("metadata", "type"):
                Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
                continue
            if parser.get("metadata", "type") in self._ignored_instance_container_types:
                continue

            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(serialized)
            container_type = instance_container.getMetaDataEntry("type")
            Job.yieldThread()

            #
            # IMPORTANT:
            # If an instance container (or maybe other type of container) exists, and user chooses "Create New",
            # we need to rename this container and all references to it, and changing those references are VERY
            # HARD.
            #
            if container_type in self._ignored_instance_container_types:
                # Ignore certain instance container types
                Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
                continue
            elif container_type == "user":
                # Check if quality changes already exists.
                user_containers = self._container_registry.findInstanceContainers(id = container_id)
                if not user_containers:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
                        instance_container = user_containers[0]
                        instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                        instance_container.setDirty(True)
                    elif self._resolve_strategies["machine"] == "new":
                        # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                        old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
                        if old_extruder_id:
                            new_extruder_id = extruder_stack_id_map[old_extruder_id]
                            new_id = new_extruder_id + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry("extruder", new_extruder_id)
                            containers_to_add.append(instance_container)

                        machine_id = instance_container.getMetaDataEntry("machine", None)
                        if machine_id:
                            new_machine_id = self.getNewId(machine_id)
                            new_id = new_machine_id + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry("machine", new_machine_id)
                            containers_to_add.append(instance_container)
                user_instance_containers.append(instance_container)
            elif container_type in ("quality_changes", "definition_changes"):
                # Check if quality changes already exists.
                changes_containers = self._container_registry.findInstanceContainers(id = container_id)
                if not changes_containers:
                    # no existing containers with the same ID, so we can safely add the new one
                    containers_to_add.append(instance_container)
                else:
                    # we have found existing container with the same ID, so we need to resolve according to the
                    # selected strategy.
                    if self._resolve_strategies[container_type] == "override":
                        instance_container = changes_containers[0]
                        instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                        instance_container.setDirty(True)

                    elif self._resolve_strategies[container_type] == "new":
                        # TODO: how should we handle the case "new" for quality_changes and definition_changes?

                        instance_container.setName(self._container_registry.uniqueName(instance_container.getName()))
                        new_changes_container_id = self.getNewId(instance_container.getId())
                        instance_container._id = new_changes_container_id

                        # TODO: we don't know the following is correct or not, need to verify
                        #       AND REFACTOR!!!
                        if self._resolve_strategies["machine"] == "new":
                            # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                            old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
                            if old_extruder_id:
                                new_extruder_id = extruder_stack_id_map[old_extruder_id]
                                instance_container.setMetaDataEntry("extruder", new_extruder_id)

                            machine_id = instance_container.getMetaDataEntry("machine", None)
                            if machine_id:
                                new_machine_id = self.getNewId(machine_id)
                                instance_container.setMetaDataEntry("machine", new_machine_id)

                        containers_to_add.append(instance_container)

                    elif self._resolve_strategies[container_type] is None:
                        # The ID already exists, but nothing in the values changed, so do nothing.
                        pass
                quality_and_definition_changes_instance_containers.append(instance_container)
            else:
                existing_container = self._container_registry.findInstanceContainers(id = container_id)
                if not existing_container:
                    containers_to_add.append(instance_container)
            if global_stack_need_rename:
                if instance_container.getMetaDataEntry("machine"):
                    instance_container.setMetaDataEntry("machine", global_stack_id_new)

        # Add all the containers right before we try to add / serialize the stack
        for container in containers_to_add:
            self._container_registry.addContainer(container)
            container.setDirty(True)
            containers_added.append(container)

        # Get the stack(s) saved in the workspace.
        Logger.log("d", "Workspace loading is checking stacks containers...")

        # --
        # load global stack file
        try:
            # Check if a stack by this ID already exists;
            container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
            if container_stacks:
                stack = container_stacks[0]

                if self._resolve_strategies["machine"] == "override":
                    # TODO: HACK
                    # There is a machine, check if it has authentication data. If so, keep that data.
                    network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
                    network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
                    container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"))
                    if network_authentication_id:
                        container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
                    if network_authentication_key:
                        container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
                elif self._resolve_strategies["machine"] == "new":
                    stack = GlobalStack(global_stack_id_new)
                    stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))

                    # Ensure a unique ID and name
                    stack._id = global_stack_id_new

                    # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
                    # bound machine also needs to change.
                    if stack.getMetaDataEntry("machine", None):
                        stack.setMetaDataEntry("machine", global_stack_id_new)

                    # Only machines need a new name, stacks may be non-unique
                    stack.setName(self._container_registry.uniqueName(stack.getName()))
                    container_stacks_added.append(stack)
                    self._container_registry.addContainer(stack)
                else:
                    Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
            else:
                # no existing container stack, so we create a new one
                stack = GlobalStack(global_stack_id_new)
                # Deserialize stack by converting read data from bytes to string
                stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
                container_stacks_added.append(stack)
                self._container_registry.addContainer(stack)
                containers_added.append(stack)

            global_stack = stack
            Job.yieldThread()
        except:
            Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added.
            for container in containers_added:
                self._container_registry.removeContainer(container.getId())
            return

        # --
        # load extruder stack files
        try:
            for index, extruder_stack_file in enumerate(extruder_stack_files):
                container_id = self._stripFileToId(extruder_stack_file)
                extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")

                container_stacks = self._container_registry.findContainerStacks(id = container_id)
                if container_stacks:
                    # this container stack already exists, try to resolve
                    stack = container_stacks[0]

                    if self._resolve_strategies["machine"] == "override":
                        # NOTE: This is the same code as those in the lower part
                        # deserialize new extruder stack over the current ones
                        stack = self._overrideExtruderStack(global_stack, extruder_file_content)

                    elif self._resolve_strategies["machine"] == "new":
                        # create a new extruder stack from this one
                        new_id = extruder_stack_id_map[container_id]
                        stack = ExtruderStack(new_id)

                        # HACK: the global stack can have a new name, so we need to make sure that this extruder stack
                        #       references to the new name instead of the old one. Normally, this can be done after
                        #       deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
                        #       also does addExtruder() to its machine stack, so we have to make sure that it's pointing
                        #       to the right machine BEFORE deserialization.
                        extruder_config = configparser.ConfigParser()
                        extruder_config.read_string(extruder_file_content)
                        extruder_config.set("metadata", "machine", global_stack_id_new)
                        tmp_string_io = io.StringIO()
                        extruder_config.write(tmp_string_io)
                        extruder_file_content = tmp_string_io.getvalue()

                        stack.deserialize(extruder_file_content)

                        # Ensure a unique ID and name
                        stack._id = new_id

                        self._container_registry.addContainer(stack)
                        extruder_stacks_added.append(stack)
                        containers_added.append(stack)
                else:
                    # No extruder stack with the same ID can be found
                    if self._resolve_strategies["machine"] == "override":
                        # deserialize new extruder stack over the current ones
                        stack = self._overrideExtruderStack(global_stack, extruder_file_content)

                    elif self._resolve_strategies["machine"] == "new":
                        # container not found, create a new one
                        stack = ExtruderStack(container_id)

                        # HACK: the global stack can have a new name, so we need to make sure that this extruder stack
                        #       references to the new name instead of the old one. Normally, this can be done after
                        #       deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
                        #       also does addExtruder() to its machine stack, so we have to make sure that it's pointing
                        #       to the right machine BEFORE deserialization.
                        extruder_config = configparser.ConfigParser()
                        extruder_config.read_string(extruder_file_content)
                        extruder_config.set("metadata", "machine", global_stack_id_new)
                        tmp_string_io = io.StringIO()
                        extruder_config.write(tmp_string_io)
                        extruder_file_content = tmp_string_io.getvalue()

                        stack.deserialize(extruder_file_content)
                        self._container_registry.addContainer(stack)
                        extruder_stacks_added.append(stack)
                        containers_added.append(stack)
                    else:
                        Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"]))

                extruder_stacks.append(stack)
        except:
            Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added. 
            for container in containers_added:
                self._container_registry.removeContainer(container.getId())
            return

        #
        # Replacing the old containers if resolve is "new".
        # When resolve is "new", some containers will get renamed, so all the other containers that reference to those
        # MUST get updated too.
        #
        if self._resolve_strategies["machine"] == "new":
            # A new machine was made, but it was serialized with the wrong user container. Fix that now.
            for container in user_instance_containers:
                # replacing the container ID for user instance containers for the extruders
                extruder_id = container.getMetaDataEntry("extruder", None)
                if extruder_id:
                    for extruder in extruder_stacks:
                        if extruder.getId() == extruder_id:
                            extruder.userChanges = container
                            continue

                # replacing the container ID for user instance containers for the machine
                machine_id = container.getMetaDataEntry("machine", None)
                if machine_id:
                    if global_stack.getId() == machine_id:
                        global_stack.userChanges = container
                        continue

        for changes_container_type in ("quality_changes", "definition_changes"):
            if self._resolve_strategies[changes_container_type] == "new":
                # Quality changes needs to get a new ID, added to registry and to the right stacks
                for each_changes_container in quality_and_definition_changes_instance_containers:
                    # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the
                    #       instance container loading part.
                    new_id = each_changes_container.getId()

                    # Find the old (current) changes container in the global stack
                    if changes_container_type == "quality_changes":
                        old_container = global_stack.qualityChanges
                    elif changes_container_type == "definition_changes":
                        old_container = global_stack.definitionChanges

                    # sanity checks
                    # NOTE: The following cases SHOULD NOT happen!!!!
                    if not old_container:
                        Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!",
                                   changes_container_type, global_stack.getId())

                    # Replace the quality/definition changes container if it's in the GlobalStack
                    # NOTE: we can get an empty container here, but the IDs will not match,
                    # so this comparison is fine.
                    if self._id_mapping.get(old_container.getId()) == new_id:
                        if changes_container_type == "quality_changes":
                            global_stack.qualityChanges = each_changes_container
                        elif changes_container_type == "definition_changes":
                            global_stack.definitionChanges = each_changes_container
                        continue

                    # Replace the quality/definition changes container if it's in one of the ExtruderStacks
                    for each_extruder_stack in extruder_stacks:
                        changes_container = None
                        if changes_container_type == "quality_changes":
                            changes_container = each_extruder_stack.qualityChanges
                        elif changes_container_type == "definition_changes":
                            changes_container = each_extruder_stack.definitionChanges

                        # sanity checks
                        # NOTE: The following cases SHOULD NOT happen!!!!
                        if not changes_container:
                            Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
                                       changes_container_type, each_extruder_stack.getId())

                        # NOTE: we can get an empty container here, but the IDs will not match,
                        # so this comparison is fine.
                        if self._id_mapping.get(changes_container.getId()) == new_id:
                            if changes_container_type == "quality_changes":
                                each_extruder_stack.qualityChanges = each_changes_container
                            elif changes_container_type == "definition_changes":
                                each_extruder_stack.definitionChanges = each_changes_container

        if self._resolve_strategies["material"] == "new":
            for each_material in material_containers:
                old_material = global_stack.material

                # check if the old material container has been renamed to this material container ID
                # if the container hasn't been renamed, we do nothing.
                new_id = self._id_mapping.get(old_material.getId())
                if new_id is None or new_id != each_material.getId():
                    continue

                if old_material.getId() in self._id_mapping:
                    global_stack.material = each_material

                for each_extruder_stack in extruder_stacks:
                    old_material = each_extruder_stack.material

                    # check if the old material container has been renamed to this material container ID
                    # if the container hasn't been renamed, we do nothing.
                    new_id = self._id_mapping.get(old_material.getId())
                    if new_id is None or new_id != each_material.getId():
                        continue

                    if old_material.getId() in self._id_mapping:
                        each_extruder_stack.material = each_material

        if extruder_stacks:
            for stack in extruder_stacks:
                ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
        else:
            # Machine has no extruders, but it needs to be registered with the extruder manager.
            ExtruderManager.getInstance().registerExtruder(None, global_stack.getId())

        Logger.log("d", "Workspace loading is notifying rest of the code of changes...")

        if self._resolve_strategies["machine"] == "new":
            for stack in extruder_stacks:
                stack.setNextStack(global_stack)
                stack.containersChanged.emit(stack.getTop())

        # Actually change the active machine.
        Application.getInstance().setGlobalContainerStack(global_stack)

        # Notify everything/one that is to notify about changes.
        global_stack.containersChanged.emit(global_stack.getTop())

        # Load all the nodes / meshdata of the workspace
        nodes = self._3mf_mesh_reader.read(file_name)
        if nodes is None:
            nodes = []
        return nodes
Exemple #43
0
class Script:
    def __init__(self) -> None:
        super().__init__()
        self._stack = None  # type: Optional[ContainerStack]
        self._definition = None  # type: Optional[DefinitionContainerInterface]
        self._instance = None  # type: Optional[InstanceContainer]

    def initialize(self) -> 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
        if self._definition is None:
            return
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.setMetaDataEntry("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: str, property_name: str) -> None:
        if property_name == "value":
            self.valueChanged.emit()

            # Property changed: trigger reslice
            # To do this we use the global container stack propertyChanged.
            # Re-slicing is necessary for setting changes in this plugin, because the changes
            # are applied only once per "fresh" gcode
            global_container_stack = Application.getInstance().getGlobalContainerStack()
            if global_container_stack is not None:
                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) -> Dict[str, Any]:
        setting_data_as_string = self.getSettingDataString()
        setting_data = json.loads(setting_data_as_string, object_pairs_hook = collections.OrderedDict)
        return setting_data

    def getSettingDataString(self) -> str:
        raise NotImplementedError()

    def getDefinitionId(self) -> Optional[str]:
        if self._stack:
            bottom = self._stack.getBottom()
            if bottom is not None:
                return bottom.getId()
        return None

    def getStackId(self) -> Optional[str]:
        if self._stack:
            return self._stack.getId()
        return None

    ##  Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key: str) -> Any:
        if self._stack is not None:
            return self._stack.getProperty(key, "value")
        return None

    ##  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: str, key: str, default = None) -> Any:
        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 int(m.group(0))
        except ValueError: #Not an integer.
            try:
                return float(m.group(0))
            except ValueError: #Not a number at all.
                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: str = "", **kwargs) -> str:
        #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: List[str]) -> List[str]:
        raise NotImplementedError()
def test_setDefinitionByIdExists(extruder_stack, container_registry):
    container_registry.return_value = DefinitionContainer(
        container_id="some_definition")
    extruder_stack.setDefinitionById("some_definition")
    assert extruder_stack.definition.getId() == "some_definition"
Exemple #45
0
def test_approximateMaterialDiameterNoDiameter(global_stack):
    global_stack.definition = DefinitionContainer(
        container_id="TestDefinition")
    assert global_stack.approximateMaterialDiameter == "-1"
    def _saveCachedDefinition(self, definition: DefinitionContainer):
        cache_path = Resources.getStoragePath(Resources.Cache, "definitions", self._application.getVersion(), definition.id)

        # Ensure the cache path exists
        os.makedirs(os.path.dirname(cache_path), exist_ok=True)

        try:
            with open(cache_path, "wb") as f:
                pickle.dump(definition, f, pickle.HIGHEST_PROTOCOL)
        except RecursionError:
            #Sometimes a recursion error in pickling occurs here.
            #The cause is unknown. It must be some circular reference in the definition instances or definition containers.
            #Instead of saving a partial cache and raising an exception, simply fail to save the cache.
            #See CURA-4024.
            Logger.log("w", "The definition cache for definition {definition_id} failed to pickle.".format(definition_id = definition.getId()))
            if os.path.exists(cache_path):
                os.remove(cache_path) #The pickling might be half-complete, which causes EOFError in Pickle when you load it later.
def test_valueChanges(container_registry):
    setting_property_provider = SettingPropertyProvider()

    # First create the basics; A container stack that will hold 2 instance containers and a single definition container
    container_stack = ContainerStack("test!")
    instance_container = InstanceContainer("data")
    second_instance_container = InstanceContainer("data2")
    definition_container = DefinitionContainer("foo")

    # Create a setting definition for our one and only setting!
    setting_definition = SettingDefinition("test_setting")
    setting_definition._deserialize_dict({"value": 10, "label": "blorp", "type": "float", "description": "nah", "maximum_value": 23, "maximum_value_warning": 21, "enabled": "test_setting != 20"})
    definition_container.addDefinition(setting_definition)

    # Make a single setting instance for our one and only setting.
    setting_instance = SettingInstance(setting_definition, instance_container)
    setting_instance.setProperty("value", 20)
    setting_instance.setProperty("enabled", False)
    instance_container.addInstance(setting_instance)

    # Make a setting instance that lives in our second instance_container
    setting_instance2 = SettingInstance(setting_definition, second_instance_container)
    second_instance_container.addInstance(setting_instance2)

    # Now that both containers are done, actually add them.
    container_stack.addContainer(definition_container)
    container_stack.addContainer(second_instance_container)
    container_stack.addContainer(instance_container)

    setting_property_provider.setContainerStack(container_stack)
    setting_property_provider.setKey("test_setting")
    assert setting_property_provider.key == "test_setting"

    assert setting_property_provider.getPropertyValue("value", 0) == 20

    setting_property_provider.setWatchedProperties(["enabled", "value", "validationState"])
    setting_property_provider._update()
    assert setting_property_provider.watchedProperties == ["enabled", "value", "validationState"]
    assert setting_property_provider.properties.value("enabled") == "False"
    assert setting_property_provider.properties.value("value") == "20"

    # Validator state is a special property that gets added based on the type and the value
    assert setting_property_provider.properties.value("validationState") == str(ValidatorState.Valid)

    # Set the property value to something that will trigger a warning
    setting_property_provider.setPropertyValue("value", 22)
    assert setting_property_provider.properties.value("validationState") == str(ValidatorState.MaximumWarning)
    assert setting_property_provider.getPropertyValue("value", 0) == 22
    # The setting doesn't exist in our second instance container, so this should be None
    assert setting_property_provider.getPropertyValue("value", 1) is None

    # Set the property value to something that will trigger an error
    setting_property_provider.setPropertyValue("value", 25)
    # The Qtimer that we use to prevent flooding doesn't work in tests, so tickle the change manually.
    setting_property_provider._update()

    assert setting_property_provider.properties.value("validationState") == str(ValidatorState.MaximumError)

    # We now ask for the second instance container to be targeted
    setting_property_provider.setStoreIndex(1)
    assert setting_property_provider.storeIndex == 1

    setting_property_provider.setPropertyValue("value", 2)
    setting_property_provider._update()
    # So now we should see a change in that instance container
    assert setting_property_provider.getPropertyValue("value", 1) == 2
    # But not if we ask the provider, because the container above it still has a 25 as value!
    assert setting_property_provider.getPropertyValue("value", 0) == 25

    assert setting_property_provider.stackLevels == [0, 1, 2]

    # We're asking for an index that doesn't exist.
    assert setting_property_provider.getPropertyValue("value", 2000) is None

    # The value is used, so the property must be true
    assert setting_property_provider.isValueUsed

    # Try to remove the setting from the container
    setting_property_provider.removeFromContainer(0)
    assert setting_property_provider.getPropertyValue("value", 0) is None

    # Ensure that a weird index doesn't break
    setting_property_provider.removeFromContainer(90001)
Exemple #48
0
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()
Exemple #49
0
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
from UM.Settings.Validator import Validator
from UM.Settings.DefinitionContainer import DefinitionContainer

if len(sys.argv) < 3:
    print("Usage: query.py [file] [query]")
    exit(1)

file_path = sys.argv[1]

# These are defined by Cura but we would still like to be able to query them.
SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True)
SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True, read_only = True)
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True, read_only = True)
SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1")
SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None)
SettingDefinition.addSettingType("extruder", None, str, Validator)

definition = DefinitionContainer(os.path.basename(file_path))
with open(file_path, encoding = "utf-8") as f:
    definition.deserialize(f.read())

query = json.loads(sys.argv[2])

results = definition.findDefinitions(**query)

for result in results:
    print("Found setting:", result.key)
def test_valueChanges(container_registry):
    setting_property_provider = SettingPropertyProvider()
    # First create the basics; A container stack that will hold 2 instance containers and a single definition container
    container_stack = ContainerStack("test!")
    instance_container = InstanceContainer("data")
    second_instance_container = InstanceContainer("data2")
    definition_container = DefinitionContainer("foo")

    # Create a setting definition for our one and only setting!
    setting_definition = SettingDefinition("test_setting")
    setting_definition._deserialize_dict({"value": 10, "label": "blorp", "type": "float", "description": "nah", "maximum_value": 23, "maximum_value_warning": 21})
    definition_container.addDefinition(setting_definition)

    # Make a single setting instance for our one and only setting.
    setting_instance = SettingInstance(setting_definition, instance_container)
    setting_instance.setProperty("value", 20)
    setting_instance.setProperty("enabled", False)
    instance_container.addInstance(setting_instance)

    # Make a setting instance that lives in our second instance_container
    setting_instance2 = SettingInstance(setting_definition, second_instance_container)
    second_instance_container.addInstance(setting_instance2)

    # Now that both containers are done, actually add them.
    container_stack.addContainer(definition_container)
    container_stack.addContainer(second_instance_container)
    container_stack.addContainer(instance_container)

    setting_property_provider.setContainerStack(container_stack)
    setting_property_provider.setKey("test_setting")

    assert setting_property_provider.getPropertyValue("value", 0) == 20

    setting_property_provider.setWatchedProperties(["enabled", "value", "validationState"])
    assert setting_property_provider.properties.value("enabled") == "False"
    assert setting_property_provider.properties.value("value") == "20"

    # Validator state is a special property that gets added based on the type and the value
    assert setting_property_provider.properties.value("validationState") == str(ValidatorState.Valid)

    # Set the property value to something that will trigger a warning
    setting_property_provider.setPropertyValue("value", 22)
    assert setting_property_provider.properties.value("validationState") == str(ValidatorState.MaximumWarning)
    assert setting_property_provider.getPropertyValue("value", 0) == 22
    # The setting doesn't exist in our second instance container, so this should be None
    assert setting_property_provider.getPropertyValue("value", 1) is None

    # Set the property value to something that will trigger an error
    setting_property_provider.setPropertyValue("value", 25)
    # The Qtimer that we use to prevent flooding doesn't work in tests, so tickle the change manually.
    setting_property_provider._update()

    assert setting_property_provider.properties.value("validationState") == str(ValidatorState.MaximumError)

    # We now ask for the second instance container to be targeted
    setting_property_provider.setStoreIndex(1)
    assert setting_property_provider.storeIndex == 1

    setting_property_provider.setPropertyValue("value", 2)
    setting_property_provider._update()
    # So now we should see a change in that instance container
    assert setting_property_provider.getPropertyValue("value", 1) == 2
    # But not if we ask the provider, because the container above it still has a 25 as value!
    assert setting_property_provider.getPropertyValue("value", 0) == 25

    assert setting_property_provider.stackLevels == [0, 1, 2]

    # We're asking for an index that doesn't exist.
    assert setting_property_provider.getPropertyValue("value", 2000) is None

    # The value is used, so the property must be true
    assert setting_property_provider.isValueUsed

    # Try to remove the setting from the container
    setting_property_provider.removeFromContainer(0)
    assert setting_property_provider.getPropertyValue("value", 0) is None

    # Ensure that a weird index doesn't break
    setting_property_provider.removeFromContainer(90001)
    def read(self, file_name):
        # Load all the nodes / meshdata of the workspace
        nodes = self._3mf_mesh_reader.read(file_name)
        if nodes is None:
            nodes = []

        archive = zipfile.ZipFile(file_name, "r")

        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]

        # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
        # parsing code.
        temp_preferences = Preferences()
        temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

        # Copy a number of settings from the temp preferences to the global
        global_preferences = Preferences.getInstance()
        global_preferences.setValue("general/visible_settings", temp_preferences.getValue("general/visible_settings"))
        global_preferences.setValue("cura/categories_expanded", temp_preferences.getValue("cura/categories_expanded"))
        Application.getInstance().expandedCategoriesChanged.emit()  # Notify the GUI of the change

        self._id_mapping = {}

        # We don't add containers right away, but wait right until right before the stack serialization.
        # We do this so that if something goes wrong, it's easier to clean up.
        containers_to_add = []

        # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
        # TODO: cases that the container loaded is the same (most notable in materials & definitions).
        # TODO: It might be possible that we need to add smarter checking in the future.
        Logger.log("d", "Workspace loading is checking definitions...")
        # Get all the definition files & check if they exist. If not, add them.
        definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
        for definition_container_file in definition_container_files:
            container_id = self._stripFileToId(definition_container_file)
            definitions = self._container_registry.findDefinitionContainers(id=container_id)
            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
                self._container_registry.addContainer(definition_container)

        Logger.log("d", "Workspace loading is checking materials...")
        material_containers = []
        # Get all the material files and check if they exist. If not, add them.
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
        if xml_material_profile:
            material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
            for material_container_file in material_container_files:
                container_id = self._stripFileToId(material_container_file)
                materials = self._container_registry.findInstanceContainers(id=container_id)
                if not materials:
                    material_container = xml_material_profile(container_id)
                    material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                    containers_to_add.append(material_container)
                else:
                    if not materials[0].isReadOnly():  # Only create new materials if they are not read only.
                        if self._resolve_strategies["material"] == "override":
                            materials[0].deserialize(archive.open(material_container_file).read().decode("utf-8"))
                        elif self._resolve_strategies["material"] == "new":
                            # Note that we *must* deserialize it with a new ID, as multiple containers will be
                            # auto created & added.
                            material_container = xml_material_profile(self.getNewId(container_id))
                            material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                            containers_to_add.append(material_container)
                            material_containers.append(material_container)

        Logger.log("d", "Workspace loading is checking instance containers...")
        # Get quality_changes and user profiles saved in the workspace
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        user_instance_containers = []
        quality_changes_instance_containers = []
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "user":
                # Check if quality changes already exists.
                user_containers = self._container_registry.findInstanceContainers(id=container_id)
                if not user_containers:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies["machine"] == "override":
                        user_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                    elif self._resolve_strategies["machine"] == "new":
                        # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                        extruder_id = instance_container.getMetaDataEntry("extruder", None)
                        if extruder_id:
                            new_id = self.getNewId(extruder_id) + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry("extruder", self.getNewId(extruder_id))
                            containers_to_add.append(instance_container)

                        machine_id = instance_container.getMetaDataEntry("machine", None)
                        if machine_id:
                            new_id = self.getNewId(machine_id) + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry("machine", self.getNewId(machine_id))
                            containers_to_add.append(instance_container)
                user_instance_containers.append(instance_container)
            elif container_type == "quality_changes":
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(id = container_id)
                if not quality_changes:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies["quality_changes"] == "override":
                        quality_changes[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                    elif self._resolve_strategies["quality_changes"] is None:
                        # The ID already exists, but nothing in the values changed, so do nothing.
                        pass
                quality_changes_instance_containers.append(instance_container)
            else:
                continue

        # Add all the containers right before we try to add / serialize the stack
        for container in containers_to_add:
            self._container_registry.addContainer(container)

        # Get the stack(s) saved in the workspace.
        Logger.log("d", "Workspace loading is checking stacks containers...")
        container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
        global_stack = None
        extruder_stacks = []
        container_stacks_added = []
        try:
            for container_stack_file in container_stack_files:
                container_id = self._stripFileToId(container_stack_file)

                # Check if a stack by this ID already exists;
                container_stacks = self._container_registry.findContainerStacks(id=container_id)
                if container_stacks:
                    stack = container_stacks[0]
                    if self._resolve_strategies["machine"] == "override":
                        container_stacks[0].deserialize(archive.open(container_stack_file).read().decode("utf-8"))
                    elif self._resolve_strategies["machine"] == "new":
                        new_id = self.getNewId(container_id)
                        stack = ContainerStack(new_id)
                        stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))

                        # Ensure a unique ID and name
                        stack._id = new_id

                        # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
                        # bound machine also needs to change.
                        if stack.getMetaDataEntry("machine", None):
                            stack.setMetaDataEntry("machine", self.getNewId(stack.getMetaDataEntry("machine")))

                        if stack.getMetaDataEntry("type") != "extruder_train":
                            # Only machines need a new name, stacks may be non-unique
                            stack.setName(self._container_registry.uniqueName(stack.getName()))
                        container_stacks_added.append(stack)
                        self._container_registry.addContainer(stack)
                    else:
                        Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
                else:
                    stack = ContainerStack(container_id)
                    # Deserialize stack by converting read data from bytes to string
                    stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
                    container_stacks_added.append(stack)
                    self._container_registry.addContainer(stack)

                if stack.getMetaDataEntry("type") == "extruder_train":
                    extruder_stacks.append(stack)
                else:
                    global_stack = stack
        except:
            Logger.log("W", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added. 
            for container in containers_to_add:
                self._container_registry.getInstance().removeContainer(container.getId())

            for container in container_stacks_added:
                self._container_registry.getInstance().removeContainer(container.getId())

            return None

        if self._resolve_strategies["machine"] == "new":
            # A new machine was made, but it was serialized with the wrong user container. Fix that now.
            for container in user_instance_containers:
                extruder_id = container.getMetaDataEntry("extruder", None)
                if extruder_id:
                    for extruder in extruder_stacks:
                        if extruder.getId() == extruder_id:
                            extruder.replaceContainer(0, container)
                            continue
                machine_id = container.getMetaDataEntry("machine", None)
                if machine_id:
                    if global_stack.getId() == machine_id:
                        global_stack.replaceContainer(0, container)
                        continue

        if self._resolve_strategies["quality_changes"] == "new":
            # Quality changes needs to get a new ID, added to registry and to the right stacks
            for container in quality_changes_instance_containers:
                old_id = container.getId()
                container.setName(self._container_registry.uniqueName(container.getName()))
                # We're not really supposed to change the ID in normal cases, but this is an exception.
                container._id = self.getNewId(container.getId())

                # The container was not added yet, as it didn't have an unique ID. It does now, so add it.
                self._container_registry.addContainer(container)

                # Replace the quality changes container
                old_container = global_stack.findContainer({"type": "quality_changes"})
                if old_container.getId() == old_id:
                    quality_changes_index = global_stack.getContainerIndex(old_container)
                    global_stack.replaceContainer(quality_changes_index, container)
                    continue

                for stack in extruder_stacks:
                    old_container = stack.findContainer({"type": "quality_changes"})
                    if old_container.getId() == old_id:
                        quality_changes_index = stack.getContainerIndex(old_container)
                        stack.replaceContainer(quality_changes_index, container)

        if self._resolve_strategies["material"] == "new":
            for material in material_containers:
                old_material = global_stack.findContainer({"type": "material"})
                if old_material.getId() in self._id_mapping:
                    material_index = global_stack.getContainerIndex(old_material)
                    global_stack.replaceContainer(material_index, material)
                    continue

                for stack in extruder_stacks:
                    old_material = stack.findContainer({"type": "material"})
                    if old_material.getId() in self._id_mapping:
                        material_index = stack.getContainerIndex(old_material)
                        stack.replaceContainer(material_index, material)
                        continue

        for stack in extruder_stacks:
            ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
        else:
            # Machine has no extruders, but it needs to be registered with the extruder manager.
            ExtruderManager.getInstance().registerExtruder(None, global_stack.getId())

        Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
        # Notify everything/one that is to notify about changes.
        for container in global_stack.getContainers():
            global_stack.containersChanged.emit(container)

        for stack in extruder_stacks:
            stack.setNextStack(global_stack)
            for container in stack.getContainers():
                stack.containersChanged.emit(container)

        # Actually change the active machine.
        Application.getInstance().setGlobalContainerStack(global_stack)
        return nodes
Exemple #52
0
    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:
                search_criteria = { "type": "material",  "id": preferred_material_id}
                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)