mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-11-24 11:21:14 -07:00
Merge branch 'master' into mypy_fixes
Conflicts: cura/Backups/Backup.py cura/Settings/ExtruderManager.py cura/Settings/MachineManager.py
This commit is contained in:
commit
554a3fd908
232 changed files with 565 additions and 601 deletions
|
|
@ -3,30 +3,26 @@
|
|||
from cura.Backups.BackupsManager import BackupsManager
|
||||
|
||||
|
||||
## The back-ups API provides a version-proof bridge between Cura's
|
||||
# BackupManager and plug-ins that hook into it.
|
||||
#
|
||||
# Usage:
|
||||
# ``from cura.API import CuraAPI
|
||||
# api = CuraAPI()
|
||||
# api.backups.createBackup()
|
||||
# api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})``
|
||||
class Backups:
|
||||
"""
|
||||
The backups API provides a version-proof bridge between Cura's BackupManager and plugins that hook into it.
|
||||
|
||||
Usage:
|
||||
from cura.API import CuraAPI
|
||||
api = CuraAPI()
|
||||
api.backups.createBackup()
|
||||
api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})
|
||||
"""
|
||||
|
||||
manager = BackupsManager() # Re-used instance of the backups manager.
|
||||
|
||||
## Create a new back-up using the BackupsManager.
|
||||
# \return Tuple containing a ZIP file with the back-up data and a dict
|
||||
# with metadata about the back-up.
|
||||
def createBackup(self) -> (bytes, dict):
|
||||
"""
|
||||
Create a new backup using the BackupsManager.
|
||||
:return: Tuple containing a ZIP file with the backup data and a dict with meta data about the backup.
|
||||
"""
|
||||
return self.manager.createBackup()
|
||||
|
||||
## Restore a back-up using the BackupsManager.
|
||||
# \param zip_file A ZIP file containing the actual back-up data.
|
||||
# \param meta_data Some metadata needed for restoring a back-up, like the
|
||||
# Cura version number.
|
||||
def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
|
||||
"""
|
||||
Restore a backup using the BackupManager.
|
||||
:param zip_file: A ZIP file containing the actual backup data.
|
||||
:param meta_data: Some meta data needed for restoring a backup, like the Cura version number.
|
||||
"""
|
||||
return self.manager.restoreBackup(zip_file, meta_data)
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@
|
|||
from UM.PluginRegistry import PluginRegistry
|
||||
from cura.API.Backups import Backups
|
||||
|
||||
|
||||
## The official Cura API that plug-ins can use to interact with Cura.
|
||||
#
|
||||
# Python does not technically prevent talking to other classes as well, but
|
||||
# this API provides a version-safe interface with proper deprecation warnings
|
||||
# etc. Usage of any other methods than the ones provided in this API can cause
|
||||
# plug-ins to be unstable.
|
||||
class CuraAPI:
|
||||
"""
|
||||
The official Cura API that plugins can use to interact with Cura.
|
||||
Python does not technically prevent talking to other classes as well,
|
||||
but this API provides a version-safe interface with proper deprecation warnings etc.
|
||||
Usage of any other methods than the ones provided in this API can cause plugins to be unstable.
|
||||
"""
|
||||
|
||||
# For now we use the same API version to be consistent.
|
||||
VERSION = PluginRegistry.APIVersion
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import List
|
||||
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Polygon import Polygon
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from cura.Arranging.ShapeArray import ShapeArray
|
||||
from cura.Scene import ZOffsetDecorator
|
||||
|
||||
|
|
@ -85,8 +87,7 @@ class Arrange:
|
|||
# \param node
|
||||
# \param offset_shape_arr ShapeArray with offset, for placing the shape
|
||||
# \param hull_shape_arr ShapeArray without offset, used to find location
|
||||
def findNodePlacement(self, node, offset_shape_arr, hull_shape_arr, step = 1):
|
||||
new_node = copy.deepcopy(node)
|
||||
def findNodePlacement(self, node: SceneNode, offset_shape_arr: ShapeArray, hull_shape_arr: ShapeArray, step = 1):
|
||||
best_spot = self.bestSpot(
|
||||
hull_shape_arr, start_prio = self._last_priority, step = step)
|
||||
x, y = best_spot.x, best_spot.y
|
||||
|
|
@ -95,21 +96,21 @@ class Arrange:
|
|||
self._last_priority = best_spot.priority
|
||||
|
||||
# Ensure that the object is above the build platform
|
||||
new_node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
|
||||
if new_node.getBoundingBox():
|
||||
center_y = new_node.getWorldPosition().y - new_node.getBoundingBox().bottom
|
||||
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
|
||||
if node.getBoundingBox():
|
||||
center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
|
||||
else:
|
||||
center_y = 0
|
||||
|
||||
if x is not None: # We could find a place
|
||||
new_node.setPosition(Vector(x, center_y, y))
|
||||
node.setPosition(Vector(x, center_y, y))
|
||||
found_spot = True
|
||||
self.place(x, y, offset_shape_arr) # place the object in arranger
|
||||
else:
|
||||
Logger.log("d", "Could not find spot!"),
|
||||
found_spot = False
|
||||
new_node.setPosition(Vector(200, center_y, 100))
|
||||
return new_node, found_spot
|
||||
node.setPosition(Vector(200, center_y, 100))
|
||||
return found_spot
|
||||
|
||||
## Fill priority, center is best. Lower value is better
|
||||
# This is a strategy for the arranger.
|
||||
|
|
|
|||
|
|
@ -17,12 +17,11 @@ from UM.Resources import Resources
|
|||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
|
||||
## The back-up class holds all data about a back-up.
|
||||
#
|
||||
# It is also responsible for reading and writing the zip file to the user data
|
||||
# folder.
|
||||
class Backup:
|
||||
"""
|
||||
The backup class holds all data about a backup.
|
||||
It is also responsible for reading and writing the zip file to the user data folder.
|
||||
"""
|
||||
|
||||
# These files should be ignored when making a backup.
|
||||
IGNORED_FILES = [r"cura\.log", r"plugins\.json", r"cache", r"__pycache__", r"\.qmlc", r"\.pyc"]
|
||||
|
||||
|
|
@ -33,10 +32,8 @@ class Backup:
|
|||
self.zip_file = zip_file # type: Optional[bytes]
|
||||
self.meta_data = meta_data # type: Optional[dict]
|
||||
|
||||
## Create a back-up from the current user config folder.
|
||||
def makeFromCurrent(self) -> None:
|
||||
"""
|
||||
Create a backup from the current user config folder.
|
||||
"""
|
||||
cura_release = CuraApplication.getInstance().getVersion()
|
||||
version_data_dir = Resources.getDataStoragePath()
|
||||
|
||||
|
|
@ -77,12 +74,10 @@ class Backup:
|
|||
"plugin_count": str(plugin_count)
|
||||
}
|
||||
|
||||
## Make a full archive from the given root path with the given name.
|
||||
# \param root_path The root directory to archive recursively.
|
||||
# \return The archive as bytes.
|
||||
def _makeArchive(self, buffer: "io.BytesIO", root_path: str) -> Optional[ZipFile]:
|
||||
"""
|
||||
Make a full archive from the given root path with the given name.
|
||||
:param root_path: The root directory to archive recursively.
|
||||
:return: The archive as bytes.
|
||||
"""
|
||||
ignore_string = re.compile("|".join(self.IGNORED_FILES))
|
||||
try:
|
||||
archive = ZipFile(buffer, "w", ZIP_DEFLATED)
|
||||
|
|
@ -101,15 +96,13 @@ class Backup:
|
|||
"Could not create archive from user data directory: {}".format(error)))
|
||||
return None
|
||||
|
||||
## Show a UI message.
|
||||
def _showMessage(self, message: str) -> None:
|
||||
"""Show a UI message"""
|
||||
Message(message, title=self.catalog.i18nc("@info:title", "Backup"), lifetime=30).show()
|
||||
|
||||
## Restore this back-up.
|
||||
# \return Whether we had success or not.
|
||||
def restore(self) -> bool:
|
||||
"""
|
||||
Restore this backups
|
||||
:return: A boolean whether we had success or not.
|
||||
"""
|
||||
if not self.zip_file or not self.meta_data or not self.meta_data.get("cura_release", None):
|
||||
# We can restore without the minimum required information.
|
||||
Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.")
|
||||
|
|
@ -142,14 +135,12 @@ class Backup:
|
|||
|
||||
return extracted
|
||||
|
||||
## Extract the whole archive to the given target path.
|
||||
# \param archive The archive as ZipFile.
|
||||
# \param target_path The target path.
|
||||
# \return Whether we had success or not.
|
||||
@staticmethod
|
||||
def _extractArchive(archive: "ZipFile", target_path: str) -> bool:
|
||||
"""
|
||||
Extract the whole archive to the given target path.
|
||||
:param archive: The archive as ZipFile.
|
||||
:param target_path: The target path.
|
||||
:return: A boolean whether we had success or not.
|
||||
"""
|
||||
Logger.log("d", "Removing current data in location: %s", target_path)
|
||||
Resources.factoryReset()
|
||||
Logger.log("d", "Extracting backup to location: %s", target_path)
|
||||
|
|
|
|||
|
|
@ -7,19 +7,18 @@ from cura.Backups.Backup import Backup
|
|||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
|
||||
## The BackupsManager is responsible for managing the creating and restoring of
|
||||
# back-ups.
|
||||
#
|
||||
# Back-ups themselves are represented in a different class.
|
||||
class BackupsManager:
|
||||
"""
|
||||
The BackupsManager is responsible for managing the creating and restoring of backups.
|
||||
Backups themselves are represented in a different class.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._application = CuraApplication.getInstance()
|
||||
|
||||
## Get a back-up of the current configuration.
|
||||
# \return A tuple containing a ZipFile (the actual back-up) and a dict
|
||||
# containing some metadata (like version).
|
||||
def createBackup(self) -> (Optional[bytes], Optional[dict]):
|
||||
"""
|
||||
Get a backup of the current configuration.
|
||||
:return: A Tuple containing a ZipFile (the actual backup) and a dict containing some meta data (like version).
|
||||
"""
|
||||
self._disableAutoSave()
|
||||
backup = Backup()
|
||||
backup.makeFromCurrent()
|
||||
|
|
@ -27,12 +26,11 @@ class BackupsManager:
|
|||
# We don't return a Backup here because we want plugins only to interact with our API and not full objects.
|
||||
return backup.zip_file, backup.meta_data
|
||||
|
||||
## Restore a back-up from a given ZipFile.
|
||||
# \param zip_file A bytes object containing the actual back-up.
|
||||
# \param meta_data A dict containing some metadata that is needed to
|
||||
# restore the back-up correctly.
|
||||
def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
|
||||
"""
|
||||
Restore a backup from a given ZipFile.
|
||||
:param zip_file: A bytes object containing the actual backup.
|
||||
:param meta_data: A dict containing some meta data that is needed to restore the backup correctly.
|
||||
"""
|
||||
if not meta_data.get("cura_release", None):
|
||||
# If there is no "cura_release" specified in the meta data, we don't execute a backup restore.
|
||||
Logger.log("w", "Tried to restore a backup without specifying a Cura version number.")
|
||||
|
|
@ -47,10 +45,11 @@ class BackupsManager:
|
|||
# We don't want to store the data at this point as that would override the just-restored backup.
|
||||
self._application.windowClosed(save_data=False)
|
||||
|
||||
## Here we try to disable the auto-save plug-in as it might interfere with
|
||||
# restoring a back-up.
|
||||
def _disableAutoSave(self):
|
||||
"""Here we try to disable the auto-save plugin as it might interfere with restoring a backup."""
|
||||
self._application.setSaveDataEnabled(False)
|
||||
|
||||
## Re-enable auto-save after we're done.
|
||||
def _enableAutoSave(self):
|
||||
"""Re-enable auto-save after we're done."""
|
||||
self._application.setSaveDataEnabled(True)
|
||||
|
|
|
|||
|
|
@ -225,6 +225,8 @@ class CuraApplication(QtApplication):
|
|||
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
self._container_registry_class = CuraContainerRegistry
|
||||
from cura.CuraPackageManager import CuraPackageManager
|
||||
self._package_manager_class = CuraPackageManager
|
||||
|
||||
# Adds command line options to the command line parser. This should be called after the application is created and
|
||||
# before the pre-start.
|
||||
|
|
@ -511,7 +513,6 @@ class CuraApplication(QtApplication):
|
|||
preferences.addPreference("cura/asked_dialog_on_project_save", False)
|
||||
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
||||
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
||||
preferences.addPreference("cura/not_arrange_objects_on_load", False)
|
||||
preferences.addPreference("cura/use_multi_build_plate", False)
|
||||
|
||||
preferences.addPreference("cura/currency", "€")
|
||||
|
|
@ -1601,9 +1602,7 @@ class CuraApplication(QtApplication):
|
|||
self._currently_loading_files.remove(filename)
|
||||
|
||||
self.fileLoaded.emit(filename)
|
||||
arrange_objects_on_load = (
|
||||
not self.getPreferences().getValue("cura/use_multi_build_plate") or
|
||||
not self.getPreferences().getValue("cura/not_arrange_objects_on_load"))
|
||||
arrange_objects_on_load = not self.getPreferences().getValue("cura/use_multi_build_plate")
|
||||
target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
|
||||
|
||||
root = self.getController().getScene().getRoot()
|
||||
|
|
@ -1676,7 +1675,7 @@ class CuraApplication(QtApplication):
|
|||
return
|
||||
|
||||
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
|
||||
node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
|
||||
arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
|
||||
|
||||
# This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
|
||||
# of BuildPlateDecorator produces one that's associated with build plate -1. So, here we need to check if
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@ from UM.Resources import Resources #To find storage paths for some resource type
|
|||
|
||||
|
||||
class CuraPackageManager(PackageManager):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
def __init__(self, application, parent = None):
|
||||
super().__init__(application, parent)
|
||||
|
||||
def initialize(self):
|
||||
self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
|
||||
self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
||||
|
||||
super().initialize()
|
||||
|
|
|
|||
|
|
@ -64,10 +64,11 @@ class MultiplyObjectsJob(Job):
|
|||
arranger.resetLastPriority()
|
||||
for i in range(self._count):
|
||||
# We do place the nodes one by one, as we want to yield in between.
|
||||
new_node = copy.deepcopy(node)
|
||||
solution_found = False
|
||||
if not node_too_big:
|
||||
new_node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
|
||||
else:
|
||||
new_node = copy.deepcopy(node)
|
||||
solution_found = arranger.findNodePlacement(new_node, offset_shape_arr, hull_shape_arr)
|
||||
|
||||
if node_too_big or not solution_found:
|
||||
found_solution_for_all = False
|
||||
new_location = new_node.getPosition()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from UM.Scene.SceneNode import SceneNode
|
|||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Scene.SceneNodeSettings import SceneNodeSettings
|
||||
|
||||
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
|
||||
|
||||
|
|
@ -80,6 +81,10 @@ class PlatformPhysics:
|
|||
|
||||
# only push away objects if this node is a printing mesh
|
||||
if not node.callDecoration("isNonPrintingMesh") and Application.getInstance().getPreferences().getValue("physics/automatic_push_free"):
|
||||
# Do not move locked nodes
|
||||
if node.getSetting(SceneNodeSettings.LockPosition):
|
||||
continue
|
||||
|
||||
# Check for collisions between convex hulls
|
||||
for other_node in BreadthFirstIterator(root):
|
||||
# Ignore root, ourselves and anything that is not a normal SceneNode.
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class ContainerManager(QObject):
|
|||
self._container_registry = self._application.getContainerRegistry()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
self._material_manager = self._application.getMaterialManager()
|
||||
self._quality_manager = self._application.getQualityManager()
|
||||
self._container_name_filters = {}
|
||||
|
||||
@pyqtSlot(str, str, result=str)
|
||||
|
|
@ -312,11 +313,19 @@ class ContainerManager(QObject):
|
|||
|
||||
self._machine_manager.blurSettings.emit()
|
||||
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
current_quality_changes_name = global_stack.qualityChanges.getName()
|
||||
current_quality_type = global_stack.quality.getMetaDataEntry("quality_type")
|
||||
extruder_stacks = list(global_stack.extruders.values())
|
||||
for stack in [global_stack] + extruder_stacks:
|
||||
# Find the quality_changes container for this stack and merge the contents of the top container into it.
|
||||
quality_changes = stack.qualityChanges
|
||||
|
||||
if quality_changes.getId() == "empty_quality_changes":
|
||||
quality_changes = self._quality_manager._createQualityChanges(current_quality_type, current_quality_changes_name,
|
||||
global_stack, stack)
|
||||
self._container_registry.addContainer(quality_changes)
|
||||
stack.qualityChanges = quality_changes
|
||||
|
||||
if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
|
||||
Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -467,10 +467,6 @@ class ExtruderManager(QObject):
|
|||
if global_stack.definitionChanges.hasProperty(key, "value"):
|
||||
global_stack.definitionChanges.removeInstance(key, postpone_emit = True)
|
||||
|
||||
# Update material diameter for extruders
|
||||
for position in extruder_positions_to_update:
|
||||
self.updateMaterialForDiameter(position, global_stack = global_stack)
|
||||
|
||||
## Get all extruder values for a certain setting.
|
||||
#
|
||||
# This is exposed to SettingFunction so it can be used in value functions.
|
||||
|
|
@ -562,96 +558,6 @@ class ExtruderManager(QObject):
|
|||
def getInstanceExtruderValues(self, key):
|
||||
return ExtruderManager.getExtruderValues(key)
|
||||
|
||||
## Updates the material container to a material that matches the material diameter set for the printer
|
||||
def updateMaterialForDiameter(self, extruder_position: int, global_stack = None):
|
||||
if not global_stack:
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return
|
||||
|
||||
if not global_stack.getMetaDataEntry("has_materials", False):
|
||||
return
|
||||
|
||||
extruder_stack = global_stack.extruders[str(extruder_position)]
|
||||
|
||||
material_diameter = extruder_stack.material.getProperty("material_diameter", "value")
|
||||
if not material_diameter:
|
||||
# in case of "empty" material
|
||||
material_diameter = 0
|
||||
|
||||
material_approximate_diameter = str(round(material_diameter))
|
||||
material_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
|
||||
setting_provider = extruder_stack
|
||||
if not material_diameter:
|
||||
if extruder_stack.definition.hasProperty("material_diameter", "value"):
|
||||
material_diameter = extruder_stack.definition.getProperty("material_diameter", "value")
|
||||
else:
|
||||
material_diameter = global_stack.definition.getProperty("material_diameter", "value")
|
||||
setting_provider = global_stack
|
||||
|
||||
if isinstance(material_diameter, SettingFunction):
|
||||
material_diameter = material_diameter(setting_provider)
|
||||
|
||||
machine_approximate_diameter = str(round(material_diameter))
|
||||
|
||||
if material_approximate_diameter != machine_approximate_diameter:
|
||||
Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
|
||||
|
||||
if global_stack.getMetaDataEntry("has_machine_materials", False):
|
||||
materials_definition = global_stack.definition.getId()
|
||||
has_material_variants = global_stack.getMetaDataEntry("has_variants", False)
|
||||
else:
|
||||
materials_definition = "fdmprinter"
|
||||
has_material_variants = False
|
||||
|
||||
old_material = extruder_stack.material
|
||||
search_criteria = {
|
||||
"type": "material",
|
||||
"approximate_diameter": machine_approximate_diameter,
|
||||
"material": old_material.getMetaDataEntry("material", "value"),
|
||||
"brand": old_material.getMetaDataEntry("brand", "value"),
|
||||
"supplier": old_material.getMetaDataEntry("supplier", "value"),
|
||||
"color_name": old_material.getMetaDataEntry("color_name", "value"),
|
||||
"definition": materials_definition
|
||||
}
|
||||
if has_material_variants:
|
||||
search_criteria["variant"] = extruder_stack.variant.getId()
|
||||
|
||||
container_registry = self._application.getContainerRegistry()
|
||||
empty_material = container_registry.findInstanceContainers(id = "empty_material")[0]
|
||||
|
||||
if old_material == empty_material:
|
||||
search_criteria.pop("material", None)
|
||||
search_criteria.pop("supplier", None)
|
||||
search_criteria.pop("brand", None)
|
||||
search_criteria.pop("definition", None)
|
||||
search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
|
||||
|
||||
materials = container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Same material with new diameter is not found, search for generic version of the same material type
|
||||
search_criteria.pop("supplier", None)
|
||||
search_criteria.pop("brand", None)
|
||||
search_criteria["color_name"] = "Generic"
|
||||
materials = container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Generic material with new diameter is not found, search for preferred material
|
||||
search_criteria.pop("color_name", None)
|
||||
search_criteria.pop("material", None)
|
||||
search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
|
||||
materials = container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Preferred material with new diameter is not found, search for any material
|
||||
search_criteria.pop("id", None)
|
||||
materials = container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Just use empty material as a final fallback
|
||||
materials = [empty_material]
|
||||
|
||||
Logger.log("i", "Selecting new material: %s", materials[0].getId())
|
||||
|
||||
extruder_stack.material = materials[0]
|
||||
|
||||
## Get the value for a setting from a specific extruder.
|
||||
#
|
||||
# This is exposed to SettingFunction to use in value functions.
|
||||
|
|
|
|||
|
|
@ -307,6 +307,11 @@ class MachineManager(QObject):
|
|||
for position, extruder in global_stack.extruders.items():
|
||||
material_dict[position] = extruder.material.getMetaDataEntry("base_file")
|
||||
self._current_root_material_id = material_dict
|
||||
|
||||
# Update materials to make sure that the diameters match with the machine's
|
||||
for position in global_stack.extruders:
|
||||
self.updateMaterialWithVariant(position)
|
||||
|
||||
global_quality = global_stack.quality
|
||||
quality_type = global_quality.getMetaDataEntry("quality_type")
|
||||
global_quality_changes = global_stack.qualityChanges
|
||||
|
|
@ -1235,7 +1240,7 @@ class MachineManager(QObject):
|
|||
current_quality_type, quality_type)
|
||||
self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True)
|
||||
|
||||
def _updateMaterialWithVariant(self, position: Optional[str]) -> None:
|
||||
def updateMaterialWithVariant(self, position: Optional[str]) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
if position is None:
|
||||
|
|
@ -1323,7 +1328,7 @@ class MachineManager(QObject):
|
|||
self._setMaterial(position, material_container_node)
|
||||
else:
|
||||
self._global_container_stack.extruders[position].material = self._empty_material_container
|
||||
self._updateMaterialWithVariant(position)
|
||||
self.updateMaterialWithVariant(position)
|
||||
|
||||
if configuration.buildplateConfiguration is not None:
|
||||
global_variant_container_node = self._variant_manager.getBuildplateVariantNode(self._global_container_stack.definition.getId(), configuration.buildplateConfiguration)
|
||||
|
|
@ -1369,7 +1374,7 @@ class MachineManager(QObject):
|
|||
self.blurSettings.emit()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._setGlobalVariant(container_node)
|
||||
self._updateMaterialWithVariant(None) # Update all materials
|
||||
self.updateMaterialWithVariant(None) # Update all materials
|
||||
self._updateQualityWithMaterial()
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
|
|
@ -1410,7 +1415,7 @@ class MachineManager(QObject):
|
|||
self.blurSettings.emit()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._setVariantNode(position, container_node)
|
||||
self._updateMaterialWithVariant(position)
|
||||
self.updateMaterialWithVariant(position)
|
||||
self._updateQualityWithMaterial()
|
||||
|
||||
# See if we need to show the Discard or Keep changes screen
|
||||
|
|
@ -1476,5 +1481,5 @@ class MachineManager(QObject):
|
|||
if self._global_container_stack is None:
|
||||
return
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._updateMaterialWithVariant(None)
|
||||
self.updateMaterialWithVariant(None)
|
||||
self._updateQualityWithMaterial()
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
|||
instance_container = copy.deepcopy(self._stack.getContainer(0), memo)
|
||||
|
||||
# A unique name must be added, or replaceContainer will not replace it
|
||||
instance_container.setMetaDataEntry("id", self._generateUniqueName)
|
||||
instance_container.setMetaDataEntry("id", self._generateUniqueName())
|
||||
|
||||
## Set the copied instance as the first (and only) instance container of the stack.
|
||||
deep_copy._stack.replaceContainer(0, instance_container)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue