Ejemplo n.º 1
0
    def removeMaterial(self, material_node: "MaterialNode") -> None:
        """Deletes a material from Cura.

        This function does not do any safety checking any more. Please call this function only if:
            - The material is not read-only.
            - The material is not used in any stacks.

        If the material was not lazy-loaded yet, this will fully load the container. When removing this material
        node, all other materials with the same base fill will also be removed.

        :param material_node: The material to remove.
        """

        container_registry = CuraContainerRegistry.getInstance()
        materials_this_base_file = container_registry.findContainersMetadata(
            base_file=material_node.base_file)

        # The material containers belonging to the same material file are supposed to work together. This postponeSignals()
        # does two things:
        #   - optimizing the signal emitting.
        #   - making sure that the signals will only be emitted after all the material containers have been removed.
        with postponeSignals(
                container_registry.containerRemoved,
                compress=CompressTechnique.CompressPerParameterValue):
            # CURA-6886: Some containers may not have been loaded. If remove one material container, its material file
            # will be removed. If later we remove a sub-material container which hasn't been loaded previously, it will
            # crash because removeContainer() requires to load the container first, but the material file was already
            # gone.
            for material_metadata in materials_this_base_file:
                container_registry.findInstanceContainers(
                    id=material_metadata["id"])
            for material_metadata in materials_this_base_file:
                container_registry.removeContainer(material_metadata["id"])
Ejemplo n.º 2
0
def test_connectWhilePostponed():
    test = SignalReceiver()

    signal = Signal(type=Signal.Direct)
    with postponeSignals(signal):
        signal.connect(test.slot)  # This won't do anything, as we're postponing at the moment!
        signal.emit()
    assert test.getEmitCount() == 0  # The connection was never made, so we should get 0
Ejemplo n.º 3
0
def test_disconnectWhilePostponed():
    test = SignalReceiver()

    signal = Signal(type=Signal.Direct)
    signal.connect(test.slot)
    with postponeSignals(signal):
        signal.disconnect(test.slot)  # This won't do anything, as we're postponing at the moment!
        signal.disconnectAll()  # Same holds true for the disconnect all
        signal.emit()
    assert test.getEmitCount() == 1  # Despite attempting to disconnect, this didn't happen because of the postpone
Ejemplo n.º 4
0
def test_postponeEmitCompressSingle():
    test = SignalReceiver()

    signal = Signal(type=Signal.Direct)
    signal.connect(test.slot)
    with postponeSignals(signal, compress=CompressTechnique.CompressSingle):
        signal.emit()
        assert test.getEmitCount() == 0  # as long as we're in this context, nothing should happen!
        signal.emit()
        assert test.getEmitCount() == 0
    assert test.getEmitCount() == 1
Ejemplo n.º 5
0
def test_connectWhilePostponed():
    test = SignalReceiver()

    signal = Signal(type=Signal.Direct)
    with postponeSignals(signal):
        signal.connect(
            test.slot
        )  # This won't do anything, as we're postponing at the moment!
        signal.emit()
    assert test.getEmitCount(
    ) == 0  # The connection was never made, so we should get 0
Ejemplo n.º 6
0
def test_postponeEmitCompressSingle():
    test = SignalReceiver()

    signal = Signal(type=Signal.Direct)
    signal.connect(test.slot)
    with postponeSignals(signal, compress=CompressTechnique.CompressSingle):
        signal.emit()
        assert test.getEmitCount(
        ) == 0  # as long as we're in this context, nothing should happen!
        signal.emit()
        assert test.getEmitCount() == 0
    assert test.getEmitCount() == 1
Ejemplo n.º 7
0
def test_postponeEmitCompressPerParameterValue():
    test = SignalReceiver()

    signal = Signal(type=Signal.Direct)
    signal.connect(test.slot)
    with postponeSignals(signal, compress=CompressTechnique.CompressPerParameterValue):
        signal.emit("ZOMG")
        assert test.getEmitCount() == 0  # as long as we're in this context, nothing should happen!
        signal.emit("ZOMG")
        assert test.getEmitCount() == 0
        signal.emit("BEEP")
    # We got 3 signal emits, but 2 of them were the same, so we end up with 2 unique emits.
    assert test.getEmitCount() == 2
Ejemplo n.º 8
0
def test_disconnectWhilePostponed():
    test = SignalReceiver()

    signal = Signal(type=Signal.Direct)
    signal.connect(test.slot)
    with postponeSignals(signal):
        signal.disconnect(
            test.slot
        )  # This won't do anything, as we're postponing at the moment!
        signal.disconnectAll()  # Same holds true for the disconnect all
        signal.emit()
    assert test.getEmitCount(
    ) == 1  # Despite attempting to disconnect, this didn't happen because of the postpone
Ejemplo n.º 9
0
def test_postponeEmitCompressPerParameterValue():
    test = SignalReceiver()

    signal = Signal(type=Signal.Direct)
    signal.connect(test.slot)
    with postponeSignals(signal,
                         compress=CompressTechnique.CompressPerParameterValue):
        signal.emit("ZOMG")
        assert test.getEmitCount(
        ) == 0  # as long as we're in this context, nothing should happen!
        signal.emit("ZOMG")
        assert test.getEmitCount() == 0
        signal.emit("BEEP")
    # We got 3 signal emits, but 2 of them were the same, so we end up with 2 unique emits.
    assert test.getEmitCount() == 2
Ejemplo n.º 10
0
    def removeMaterial(self, material_node: "MaterialNode") -> None:
        container_registry = CuraContainerRegistry.getInstance()
        materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file)

        # The material containers belonging to the same material file are supposed to work together. This postponeSignals()
        # does two things:
        #   - optimizing the signal emitting.
        #   - making sure that the signals will only be emitted after all the material containers have been removed.
        with postponeSignals(container_registry.containerRemoved, compress = CompressTechnique.CompressPerParameterValue):
            # CURA-6886: Some containers may not have been loaded. If remove one material container, its material file
            # will be removed. If later we remove a sub-material container which hasn't been loaded previously, it will
            # crash because removeContainer() requires to load the container first, but the material file was already
            # gone.
            for material_metadata in materials_this_base_file:
                container_registry.findInstanceContainers(id = material_metadata["id"])
            for material_metadata in materials_this_base_file:
                container_registry.removeContainer(material_metadata["id"])
Ejemplo n.º 11
0
    def duplicateMaterialByBaseFile(
            self,
            base_file: str,
            new_base_id: Optional[str] = None,
            new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]:
        """Creates a duplicate of a material with the same GUID and base_file metadata

        :param base_file: The base file of the material to duplicate.
        :param new_base_id: A new material ID for the base material. The IDs of the submaterials will be based off this
        one. If not provided, a material ID will be generated automatically.
        :param new_metadata: Metadata for the new material. If not provided, this will be duplicated from the original
        material.

        :return: The root material ID of the duplicate material.
        """

        container_registry = CuraContainerRegistry.getInstance()

        root_materials = container_registry.findContainers(id=base_file)
        if not root_materials:
            Logger.log(
                "i",
                "Unable to duplicate the root material with ID {root_id}, because it doesn't exist."
                .format(root_id=base_file))
            return None
        root_material = root_materials[0]

        # Ensure that all settings are saved.
        application = cura.CuraApplication.CuraApplication.getInstance()
        application.saveSettings()

        # Create a new ID and container to hold the data.
        if new_base_id is None:
            new_base_id = container_registry.uniqueName(root_material.getId())
        new_root_material = copy.deepcopy(root_material)
        new_root_material.getMetaData()["id"] = new_base_id
        new_root_material.getMetaData()["base_file"] = new_base_id
        if new_metadata is not None:
            new_root_material.getMetaData().update(new_metadata)
        new_containers = [new_root_material]

        # Clone all submaterials.
        for container_to_copy in container_registry.findInstanceContainers(
                base_file=base_file):
            if container_to_copy.getId() == base_file:
                continue  # We already have that one. Skip it.
            new_id = new_base_id
            definition = container_to_copy.getMetaDataEntry("definition")
            if definition != "fdmprinter":
                new_id += "_" + definition
                variant_name = container_to_copy.getMetaDataEntry(
                    "variant_name")
                if variant_name:
                    new_id += "_" + variant_name.replace(" ", "_")

            new_container = copy.deepcopy(container_to_copy)
            new_container.getMetaData()["id"] = new_id
            new_container.getMetaData()["base_file"] = new_base_id
            if new_metadata is not None:
                new_container.getMetaData().update(new_metadata)
            new_containers.append(new_container)

        # CURA-6863: Nodes in ContainerTree will be updated upon ContainerAdded signals, one at a time. It will use the
        # best fit material container at the time it sees one. For example, if you duplicate and get generic_pva #2,
        # if the node update function sees the containers in the following order:
        #
        #   - generic_pva #2
        #   - generic_pva #2_um3_aa04
        #
        # It will first use "generic_pva #2" because that's the best fit it has ever seen, and later "generic_pva #2_um3_aa04"
        # once it sees that. Because things run in the Qt event loop, they don't happen at the same time. This means if
        # between those two events, the ContainerTree will have nodes that contain invalid data.
        #
        # This sort fixes the problem by emitting the most specific containers first.
        new_containers = sorted(new_containers,
                                key=lambda x: x.getId(),
                                reverse=True)

        # Optimization. Serving the same purpose as the postponeSignals() in removeMaterial()
        # postpone the signals emitted when duplicating materials. This is easier on the event loop; changes the
        # behavior to be like a transaction. Prevents concurrency issues.
        with postponeSignals(
                container_registry.containerAdded,
                compress=CompressTechnique.CompressPerParameterValue):
            for container_to_add in new_containers:
                container_to_add.setDirty(True)
                container_registry.addContainer(container_to_add)

            # If the duplicated material was favorite then the new material should also be added to the favorites.
            favorites_set = set(application.getPreferences().getValue(
                "cura/favorite_materials").split(";"))
            if base_file in favorites_set:
                favorites_set.add(new_base_id)
                application.getPreferences().setValue(
                    "cura/favorite_materials", ";".join(favorites_set))

        return new_base_id