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"])
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
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
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
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
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
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
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
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
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"])
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