Merge remote-tracking branch 'origin/feature_intent_container_tree' into feature_intent_upgrade

This commit is contained in:
Lipu Fei 2019-09-19 14:14:29 +02:00
commit 823f7e5921
14 changed files with 159 additions and 206 deletions

View File

@ -131,8 +131,8 @@ class MaterialManager(QObject):
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup. # Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
materials = self.getAvailableMaterials(machine.definition.getId(), nozzle_name) materials = self.getAvailableMaterials(machine.definition.getId(), nozzle_name)
compatible_material_diameter = str(round(extruder_stack.getCompatibleMaterialDiameter())) compatible_material_diameter = extruder_stack.getApproximateMaterialDiameter()
result = {key: material for key, material in materials.items() if material.container and material.container.getMetaDataEntry("approximate_diameter") == compatible_material_diameter} result = {key: material for key, material in materials.items() if material.container and float(material.container.getMetaDataEntry("approximate_diameter")) == compatible_material_diameter}
return result return result
# #
@ -171,9 +171,11 @@ class MaterialManager(QObject):
def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, nozzle_name: str, def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, nozzle_name: str,
buildplate_name: Optional[str], material_guid: str) -> Optional["MaterialNode"]: buildplate_name: Optional[str], material_guid: str) -> Optional["MaterialNode"]:
machine_definition = global_stack.definition machine_definition = global_stack.definition
variant_name = global_stack.extruders[position].variant.getName() extruder = global_stack.extruderList[int(position)]
variant_name = extruder.variant.getName()
approximate_diameter = extruder.getApproximateMaterialDiameter()
return self.getMaterialNode(machine_definition.getId(), variant_name, buildplate_name, 3, material_guid) return self.getMaterialNode(machine_definition.getId(), variant_name, buildplate_name, approximate_diameter, material_guid)
# There are 2 ways to get fallback materials; # There are 2 ways to get fallback materials;
# - A fallback by type (@sa getFallbackMaterialIdByMaterialType), which adds the generic version of this material # - A fallback by type (@sa getFallbackMaterialIdByMaterialType), which adds the generic version of this material
@ -266,128 +268,59 @@ class MaterialManager(QObject):
return False return False
return True return True
## Change the user-visible name of a material.
# \param material_node The ContainerTree node of the material to rename.
# \param name The new name for the material.
@pyqtSlot("QVariant", str) @pyqtSlot("QVariant", str)
def setMaterialName(self, material_node: "MaterialNode", name: str) -> None: def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
if material_node.container is None: return cura.CuraApplication.CuraApplication.getMaterialManagementModel().setMaterialName(material_node, name)
return
root_material_id = material_node.container.getMetaDataEntry("base_file")
if root_material_id is None:
return
if CuraContainerRegistry.getInstance().isReadOnly(root_material_id):
Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
return
containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = root_material_id)
containers[0].setName(name)
## 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.
@pyqtSlot("QVariant") @pyqtSlot("QVariant")
def removeMaterial(self, material_node: "MaterialNode") -> None: def removeMaterial(self, material_node: "MaterialNode") -> None:
if material_node.container is None: return cura.CuraApplication.CuraApplication.getMaterialManagementModel().setMaterialName(material_node)
return
root_material_id = material_node.container.getMetaDataEntry("base_file")
if root_material_id is not None:
self.removeMaterialByRootId(root_material_id)
def duplicateMaterialByRootId(self, root_material_id: str, new_base_id: Optional[str] = None, def duplicateMaterialByRootId(self, root_material_id: str, new_base_id: Optional[str] = None, new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]:
new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]: result = cura.CuraApplication.CuraApplication.getInstance().getMaterialManagementModel().duplicateMaterialByBaseFile(root_material_id, new_base_id, new_metadata)
container_registry = CuraContainerRegistry.getInstance() if result is None:
results = container_registry.findContainers(id=root_material_id)
if not results:
Logger.log("i", "Unable to duplicate the material with id %s, because it doesn't exist.", root_material_id)
return None
base_container = results[0]
# Ensure all settings are saved.
cura.CuraApplication.CuraApplication.getInstance().saveSettings()
# Create a new ID & container to hold the data.
new_containers = []
if new_base_id is None:
new_base_id = container_registry.uniqueName(base_container.getId())
new_base_container = copy.deepcopy(base_container)
new_base_container.getMetaData()["id"] = new_base_id
new_base_container.getMetaData()["base_file"] = new_base_id
if new_metadata is not None:
for key, value in new_metadata.items():
new_base_container.getMetaData()[key] = value
new_containers.append(new_base_container)
# Clone all of them.
for container_to_copy in container_registry.findContainers(base_file=root_material_id):
if container_to_copy.getId() == root_material_id:
continue # We already have that one, skip it
new_id = new_base_id
if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
new_id += "_" + container_to_copy.getMetaDataEntry("definition")
if container_to_copy.getMetaDataEntry("variant_name"):
nozzle_name = container_to_copy.getMetaDataEntry("variant_name")
new_id += "_" + nozzle_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:
for key, value in new_metadata.items():
new_container.getMetaData()[key] = value
new_containers.append(new_container)
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 favorite.
if root_material_id in self.getFavorites():
cura.CuraApplication.CuraApplication.getInstance().getMaterialManagementModel().addFavorite(new_base_id)
return new_base_id
#
# Creates a duplicate of a material, which has the same GUID and base_file metadata.
# Returns the root material ID of the duplicated material if successful.
#
@pyqtSlot("QVariant", result = str)
def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None,
new_metadata: Optional[Dict[str, Any]] = None) -> str:
if material_node.container is None:
Logger.log("e", "Material node {0} doesn't have container.".format(material_node.container_id))
return "ERROR" return "ERROR"
root_material_id = cast(str, material_node.container.getMetaDataEntry("base_file", "")) return result
new_material_id = self.duplicateMaterialByRootId(root_material_id, new_base_id, new_metadata)
return new_material_id if new_material_id is not None else "ERROR"
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID. ## Creates a duplicate of a material with the same GUID and base_file
# Returns the ID of the newly created material. # metadata.
# \param material_node The node representing 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.
@pyqtSlot("QVariant", result = str)
def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Optional[Dict[str, Any]] = None) -> str:
result = cura.CuraApplication.CuraApplication.getInstance().getMaterialManagementModel().duplicateMaterial(material_node, new_base_id, new_metadata)
if result is None:
return "ERROR"
return result
## Create a new material by cloning the preferred material for the current
# material diameter and generate a new GUID.
#
# The material type is explicitly left to be the one from the preferred
# material, since this allows the user to still have SOME profiles to work
# with.
# \return The ID of the newly created material.
@pyqtSlot(result = str) @pyqtSlot(result = str)
def createMaterial(self) -> str: def createMaterial(self) -> str:
from UM.i18n import i18nCatalog return cura.CuraApplication.CuraApplication.getMaterialManagementModel().createMaterial()
catalog = i18nCatalog("cura")
# Ensure all settings are saved.
application = cura.CuraApplication.CuraApplication.getInstance()
application.saveSettings()
machine_manager = application.getMachineManager()
extruder_stack = machine_manager.activeStack
global_stack = application.getGlobalContainerStack()
if global_stack is None:
Logger.log("e", "Global stack not present!")
return "ERROR"
machine_definition = global_stack.definition
root_material_id = machine_definition.getMetaDataEntry("preferred_material", default = "generic_pla")
approximate_diameter = str(extruder_stack.approximateMaterialDiameter)
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
# Create a new ID & container to hold the data.
new_id = CuraContainerRegistry.getInstance().uniqueName("custom_material")
new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
"brand": catalog.i18nc("@label", "Custom"),
"GUID": str(uuid.uuid4()),
}
self.duplicateMaterialByRootId(root_material_id, new_base_id = new_id, new_metadata = new_metadata)
return new_id
@pyqtSlot(str) @pyqtSlot(str)
def addFavorite(self, root_material_id: str) -> None: def addFavorite(self, root_material_id: str) -> None:

View File

@ -127,8 +127,8 @@ class BaseMaterialsModel(ListModel):
return return
nozzle_name = extruder_stack.variant.getName() nozzle_name = extruder_stack.variant.getName()
materials = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[nozzle_name].materials materials = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[nozzle_name].materials
compatible_material_diameter = str(round(extruder_stack.getCompatibleMaterialDiameter())) approximate_material_diameter = extruder_stack.getApproximateMaterialDiameter()
self._available_materials = {key: material for key, material in materials.items() if material.container.getMetaDataEntry("approximate_diameter") == compatible_material_diameter} self._available_materials = {key: material for key, material in materials.items() if float(material.container.getMetaDataEntry("approximate_diameter")) == approximate_material_diameter}
## This method is used by all material models in the beginning of the ## This method is used by all material models in the beginning of the
# _update() method in order to prevent errors. It's the same in all models # _update() method in order to prevent errors. It's the same in all models

View File

@ -10,7 +10,7 @@ from cura.Settings.IntentManager import IntentManager
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes. from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
from PyQt5.QtCore import pyqtProperty, pyqtSignal from PyQt5.QtCore import pyqtProperty, pyqtSignal
import cura.CuraApplication
if TYPE_CHECKING: if TYPE_CHECKING:
from UM.Settings.ContainerRegistry import ContainerInterface from UM.Settings.ContainerRegistry import ContainerInterface
@ -48,7 +48,7 @@ class IntentCategoryModel(ListModel):
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange) ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange)
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange) ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange)
IntentManager.getInstance().configurationChanged.connect(self.update) cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStackChanged.connect(self.update)
self.update() self.update()

View File

@ -66,12 +66,26 @@ class IntentModel(ListModel):
container_tree = ContainerTree.getInstance() container_tree = ContainerTree.getInstance()
machine_node = container_tree.machines[global_stack.definition.getId()] machine_node = container_tree.machines[global_stack.definition.getId()]
active_extruder = ExtruderManager.getInstance().getActiveExtruderStack()
# We can't just look at the active extruder, since it is possible for only one extruder to have an intent
# and the other extruders have no intent (eg, default). It is not possible for one extruder to have intent A and
# the other to have B.
# So we will use the first extruder that we find that has an intent that is not default as the "active" extruder
active_extruder = None
for extruder in global_stack.extruderList:
if extruder.intent.getMetaDataEntry("intent_category", "default") == "default":
if active_extruder is None:
active_extruder = extruder # If there is no extruder found and the intent is default, use that.
else: # We found an intent, use that extruder as "active"
active_extruder = extruder
if not active_extruder: if not active_extruder:
return return
active_variant_name = active_extruder.variant.getMetaDataEntry("name") active_variant_name = active_extruder.variant.getMetaDataEntry("name")
active_variant_node = machine_node.variants[active_variant_name] active_variant_node = machine_node.variants[active_variant_name]
active_material_node = active_variant_node.materials[active_extruder.material.getMetaDataEntry("base_file")] active_material_node = active_variant_node.materials[active_extruder.material.getMetaDataEntry("base_file")]
layer_heights_added = [] layer_heights_added = []
for quality_id, quality_node in active_material_node.qualities.items(): for quality_id, quality_node in active_material_node.qualities.items():
if quality_node.quality_type not in quality_groups: # Don't add the empty quality type (or anything else that would crash, defensively). if quality_node.quality_type not in quality_groups: # Don't add the empty quality type (or anything else that would crash, defensively).

View File

@ -73,20 +73,19 @@ class MaterialManagementModel(QObject):
## Creates a duplicate of a material with the same GUID and base_file ## Creates a duplicate of a material with the same GUID and base_file
# metadata. # metadata.
# \param material_node The node representing the material to duplicate. # \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 # \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 # the submaterials will be based off this one. If not provided, a material
# ID will be generated automatically. # ID will be generated automatically.
# \param new_metadata Metadata for the new material. If not provided, this # \param new_metadata Metadata for the new material. If not provided, this
# will be duplicated from the original material. # will be duplicated from the original material.
# \return The root material ID of the duplicate material. # \return The root material ID of the duplicate material.
@pyqtSlot("QVariant", result = str) def duplicateMaterialByBaseFile(self, base_file: str, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
container_registry = CuraContainerRegistry.getInstance() container_registry = CuraContainerRegistry.getInstance()
root_materials = container_registry.findContainers(id = material_node.base_file) root_materials = container_registry.findContainers(id = base_file)
if not root_materials: 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 = material_node.base_file)) 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 return None
root_material = root_materials[0] root_material = root_materials[0]
@ -105,8 +104,8 @@ class MaterialManagementModel(QObject):
new_containers = [new_root_material] new_containers = [new_root_material]
# Clone all submaterials. # Clone all submaterials.
for container_to_copy in container_registry.findInstanceContainers(base_file = material_node.base_file): for container_to_copy in container_registry.findInstanceContainers(base_file = base_file):
if container_to_copy.getId() == material_node.base_file: if container_to_copy.getId() == base_file:
continue # We already have that one. Skip it. continue # We already have that one. Skip it.
new_id = new_base_id new_id = new_base_id
definition = container_to_copy.getMetaDataEntry("definition") definition = container_to_copy.getMetaDataEntry("definition")
@ -129,12 +128,25 @@ class MaterialManagementModel(QObject):
# If the duplicated material was favorite then the new material should also be added to the favorites. # 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(";")) favorites_set = set(application.getPreferences().getValue("cura/favorite_materials").split(";"))
if material_node.base_file in favorites_set: if base_file in favorites_set:
favorites_set.add(new_base_id) favorites_set.add(new_base_id)
application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set)) application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set))
return new_base_id return new_base_id
## Creates a duplicate of a material with the same GUID and base_file
# metadata.
# \param material_node The node representing 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.
@pyqtSlot("QVariant", result = str)
def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
return self.duplicateMaterialByBaseFile(material_node.base_file, new_base_id, new_metadata)
## Create a new material by cloning the preferred material for the current ## Create a new material by cloning the preferred material for the current
# material diameter and generate a new GUID. # material diameter and generate a new GUID.
# #

View File

@ -78,9 +78,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups() quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups()
item_list = [] item_list = []
for key in quality_group_dict: for quality_group in quality_group_dict.values():
quality_group = quality_group_dict[key]
layer_height = self._fetchLayerHeight(quality_group) layer_height = self._fetchLayerHeight(quality_group)
item = {"name": quality_group.name, item = {"name": quality_group.name,

View File

@ -5,7 +5,6 @@ from typing import Any, Dict, List, Optional, TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
from UM.Logger import Logger
from UM.Util import parseBool from UM.Util import parseBool
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Decorators import deprecated from UM.Decorators import deprecated

View File

@ -4,6 +4,7 @@
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
import cura.CuraApplication import cura.CuraApplication
from UM.Logger import Logger
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.cura_empty_instance_containers import empty_intent_container from cura.Settings.cura_empty_instance_containers import empty_intent_container
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
@ -11,20 +12,12 @@ from UM.Settings.InstanceContainer import InstanceContainer
if TYPE_CHECKING: if TYPE_CHECKING:
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
## Front-end for querying which intents are available for a certain ## Front-end for querying which intents are available for a certain
# configuration. # configuration.
#
# CURRENTLY THIS CLASS CONTAINS ONLY SOME PSEUDOCODE OF WHAT WE ARE SUPPOSED
# TO IMPLEMENT.
class IntentManager(QObject): class IntentManager(QObject):
__instance = None __instance = None
def __init__(self) -> None:
super().__init__()
cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStackChanged.connect(self.configurationChanged)
self.configurationChanged.connect(self.selectDefaultIntent)
pass
## This class is a singleton. ## This class is a singleton.
@classmethod @classmethod
def getInstance(cls): def getInstance(cls):
@ -32,7 +25,6 @@ class IntentManager(QObject):
cls.__instance = IntentManager() cls.__instance = IntentManager()
return cls.__instance return cls.__instance
configurationChanged = pyqtSignal() #Triggered when something changed in the rest of the stack.
intentCategoryChanged = pyqtSignal() #Triggered when we switch categories. intentCategoryChanged = pyqtSignal() #Triggered when we switch categories.
## Gets the metadata dictionaries of all intent profiles for a given ## Gets the metadata dictionaries of all intent profiles for a given
@ -40,12 +32,16 @@ class IntentManager(QObject):
# #
# \param definition_id ID of the printer. # \param definition_id ID of the printer.
# \param nozzle_name Name of the nozzle. # \param nozzle_name Name of the nozzle.
# \param material_id ID of the material. # \param material_base_file The base_file of the material.
# \return A list of metadata dictionaries matching the search criteria, or # \return A list of metadata dictionaries matching the search criteria, or
# an empty list if nothing was found. # an empty list if nothing was found.
def intentMetadatas(self, definition_id: str, nozzle_name: str, material_id: str) -> List[Dict[str, Any]]: def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]:
registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() material_node = ContainerTree.getInstance().machines[definition_id].variants[nozzle_name].materials[material_base_file]
return registry.findContainersMetadata(type = "intent", definition = definition_id, variant = nozzle_name, material = material_id) intent_metadatas = []
for quality_node in material_node.qualities.values():
for intent_node in quality_node.intents.values():
intent_metadatas.append(intent_node.getMetadata())
return intent_metadatas
## Collects and returns all intent categories available for the given ## Collects and returns all intent categories available for the given
# parameters. Note that the 'default' category is always available. # parameters. Note that the 'default' category is always available.
@ -77,7 +73,6 @@ class IntentManager(QObject):
# even though there should always be defaults. The problem then is what to do with the quality_types. # even though there should always be defaults. The problem then is what to do with the quality_types.
# Currently _also_ inconsistent with 'currentAvailableIntentCategories', which _does_ return default. # Currently _also_ inconsistent with 'currentAvailableIntentCategories', which _does_ return default.
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups() quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
# TODO: These quality nodes in that tree already contain the intent nodes. We can optimise this.
available_quality_types = {quality_group.quality_type for quality_group in quality_groups.values() if quality_group.node_for_global is not None} available_quality_types = {quality_group.quality_type for quality_group in quality_groups.values() if quality_group.node_for_global is not None}
final_intent_ids = set() # type: Set[str] final_intent_ids = set() # type: Set[str]
@ -131,6 +126,7 @@ class IntentManager(QObject):
## Apply intent on the stacks. ## Apply intent on the stacks.
@pyqtSlot(str, str) @pyqtSlot(str, str)
def selectIntent(self, intent_category: str, quality_type: str) -> None: def selectIntent(self, intent_category: str, quality_type: str) -> None:
Logger.log("i", "Attempting to set intent_category to [%s] and quality type to [%s]", intent_category, quality_type)
old_intent_category = self.currentIntentCategory old_intent_category = self.currentIntentCategory
application = cura.CuraApplication.CuraApplication.getInstance() application = cura.CuraApplication.CuraApplication.getInstance()
global_stack = application.getGlobalContainerStack() global_stack = application.getGlobalContainerStack()
@ -148,12 +144,3 @@ class IntentManager(QObject):
application.getMachineManager().setQualityGroupByQualityType(quality_type) application.getMachineManager().setQualityGroupByQualityType(quality_type)
if old_intent_category != intent_category: if old_intent_category != intent_category:
self.intentCategoryChanged.emit() self.intentCategoryChanged.emit()
## Selects the default intents on every extruder.
def selectDefaultIntent(self) -> None:
application = cura.CuraApplication.CuraApplication.getInstance()
global_stack = application.getGlobalContainerStack()
if global_stack is None:
return
for extruder_stack in global_stack.extruderList:
extruder_stack.intent = self.getDefaultIntent()

View File

@ -26,7 +26,6 @@ import cura.CuraApplication # Imported like this to prevent circular references
from cura.Machines.ContainerNode import ContainerNode from cura.Machines.ContainerNode import ContainerNode
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.MaterialManager import MaterialManager
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionType from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionType
from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
@ -616,10 +615,15 @@ class MachineManager(QObject):
@pyqtProperty(str, notify=activeIntentChanged) @pyqtProperty(str, notify=activeIntentChanged)
def activeIntentCategory(self): def activeIntentCategory(self):
global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if not self._active_container_stack: if not global_container_stack:
return "" return ""
intent_category = self._active_container_stack.intent.getMetaDataEntry("intent_category") intent_category = "default"
for extruder in global_container_stack.extruderList:
category = extruder.intent.getMetaDataEntry("intent_category", "default")
if category != "default" and category != intent_category:
intent_category = category
return intent_category return intent_category
## Returns whether there is anything unsupported in the current set-up. ## Returns whether there is anything unsupported in the current set-up.
@ -1301,8 +1305,7 @@ class MachineManager(QObject):
self._setMaterial(position_item, new_material) self._setMaterial(position_item, new_material)
else: else:
# The current material is not available, find the preferred one. # The current material is not available, find the preferred one.
material_diameter = self._global_container_stack.extruders[position].getCompatibleMaterialDiameter() approximate_material_diameter = int(self._global_container_stack.extruderList[int(position)].getApproximateMaterialDiameter())
approximate_material_diameter = round(material_diameter)
material_node = nozzle_node.preferredMaterial(approximate_material_diameter) material_node = nozzle_node.preferredMaterial(approximate_material_diameter)
self._setMaterial(position_item, material_node) self._setMaterial(position_item, material_node)
@ -1343,6 +1346,7 @@ class MachineManager(QObject):
if self._global_container_stack is None: if self._global_container_stack is None:
return return
self.blurSettings.emit() self.blurSettings.emit()
container_registry = CuraContainerRegistry.getInstance()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self.switchPrinterType(configuration.printerType) self.switchPrinterType(configuration.printerType)
@ -1379,20 +1383,18 @@ class MachineManager(QObject):
else: else:
machine_node = ContainerTree.getInstance().machines.get(self._global_container_stack.definition.getId()) machine_node = ContainerTree.getInstance().machines.get(self._global_container_stack.definition.getId())
variant_node = machine_node.variants.get(extruder_configuration.hotendID) variant_node = machine_node.variants.get(extruder_configuration.hotendID)
if variant_node: self._setVariantNode(position, variant_node)
self._setVariantNode(position, variant_node)
else:
self._global_container_stack.extruders[position].variant = empty_variant_container
material_container_node = MaterialManager.getInstance().getMaterialNodeByType(self._global_container_stack, # Find the material profile that the printer has stored.
position, # This might find one of the duplicates if the user duplicated the material to sync with. But that's okay; both have this GUID so both are correct.
extruder_configuration.hotendID, approximate_diameter = int(self._global_container_stack.extruderList[int(position)].getApproximateMaterialDiameter())
configuration.buildplateConfiguration, materials_with_guid = container_registry.findInstanceContainersMetadata(guid = extruder_configuration.material.guid, approximate_diameter = approximate_diameter)
extruder_configuration.material.guid) material_container_node = variant_node.preferredMaterial(approximate_diameter)
if material_container_node: if materials_with_guid: # We also have the material profile that the printer wants to share.
self._setMaterial(position, material_container_node) base_file = materials_with_guid[0]["base_file"]
else: material_container_node = variant_node.materials.get(base_file, default = material_container_node) # If Cura thinks that the selected material is not available for this printer, revert to the preferred material.
self._global_container_stack.extruders[position].material = empty_material_container
self._setMaterial(position, material_container_node)
self._global_container_stack.extruders[position].setEnabled(True) self._global_container_stack.extruders[position].setEnabled(True)
self.updateMaterialWithVariant(position) self.updateMaterialWithVariant(position)
@ -1431,17 +1433,12 @@ class MachineManager(QObject):
def setMaterialById(self, position: str, root_material_id: str) -> None: def setMaterialById(self, position: str, root_material_id: str) -> None:
if self._global_container_stack is None: if self._global_container_stack is None:
return return
buildplate_name = None
if self._global_container_stack.variant.getId() != "empty_variant":
buildplate_name = self._global_container_stack.variant.getName()
machine_definition_id = self._global_container_stack.definition.id machine_definition_id = self._global_container_stack.definition.id
position = str(position) position = str(position)
extruder_stack = self._global_container_stack.extruders[position] extruder_stack = self._global_container_stack.extruders[position]
nozzle_name = extruder_stack.variant.getName() nozzle_name = extruder_stack.variant.getName()
material_diameter = extruder_stack.getApproximateMaterialDiameter() material_node = ContainerTree.getInstance().machines[machine_definition_id].variants[nozzle_name].materials[root_material_id]
material_node = MaterialManager.getInstance().getMaterialNode(machine_definition_id, nozzle_name, buildplate_name,
material_diameter, root_material_id)
self.setMaterial(position, material_node) self.setMaterial(position, material_node)
## Global_stack: if you want to provide your own global_stack instead of the current active one ## Global_stack: if you want to provide your own global_stack instead of the current active one
@ -1624,7 +1621,3 @@ class MachineManager(QObject):
abbr_machine += stripped_word abbr_machine += stripped_word
return abbr_machine return abbr_machine
# Gets all machines that belong to the given group_id.
def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]:
return self._container_registry.findContainerStacks(type = "machine", group_id = group_id)

View File

@ -11,7 +11,9 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Util import parseBool from UM.Util import parseBool
import cura.CuraApplication # Imported like this to prevent circular dependencies.
from cura.MachineAction import MachineAction from cura.MachineAction import MachineAction
from cura.Machines.ContainerTree import ContainerTree # To re-build the machine node when hasMaterials changes.
from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.cura_empty_instance_containers import isEmptyContainer from cura.Settings.cura_empty_instance_containers import isEmptyContainer
@ -41,6 +43,9 @@ class MachineSettingsAction(MachineAction):
self._backend = self._application.getBackend() self._backend = self._application.getBackend()
self.onFinished.connect(self._onFinished) self.onFinished.connect(self._onFinished)
# If the g-code flavour changes between UltiGCode and another flavour, we need to update the container tree.
self._application.globalContainerStackChanged.connect(self._updateHasMaterialsInContainerTree)
# Which container index in a stack to store machine setting changes. # Which container index in a stack to store machine setting changes.
@pyqtProperty(int, constant = True) @pyqtProperty(int, constant = True)
def storeContainerIndex(self) -> int: def storeContainerIndex(self) -> int:
@ -51,6 +56,16 @@ class MachineSettingsAction(MachineAction):
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
## Triggered when the global container stack changes or when the g-code
# flavour setting is changed.
def _updateHasMaterialsInContainerTree(self) -> None:
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
if machine_node.has_materials != parseBool(global_stack.getMetaDataEntry("has_materials")): # May have changed due to the g-code flavour.
machine_node.has_materials = parseBool(global_stack.getMetaDataEntry("has_materials"))
machine_node._loadAll()
def _reset(self): def _reset(self):
global_stack = self._application.getMachineManager().activeMachine global_stack = self._application.getMachineManager().activeMachine
if not global_stack: if not global_stack:
@ -98,11 +113,8 @@ class MachineSettingsAction(MachineAction):
return return
machine_manager = self._application.getMachineManager() machine_manager = self._application.getMachineManager()
material_manager = self._application.getMaterialManager()
extruder_positions = list(global_stack.extruders.keys())
has_materials = global_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" has_materials = global_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
material_node = None
if has_materials: if has_materials:
global_stack.setMetaDataEntry("has_materials", True) global_stack.setMetaDataEntry("has_materials", True)
else: else:
@ -111,11 +123,15 @@ class MachineSettingsAction(MachineAction):
if "has_materials" in global_stack.getMetaData(): if "has_materials" in global_stack.getMetaData():
global_stack.removeMetaDataEntry("has_materials") global_stack.removeMetaDataEntry("has_materials")
self._updateHasMaterialsInContainerTree()
# set materials # set materials
for position in extruder_positions: machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
if has_materials: for position, extruder in enumerate(global_stack.extruderList):
material_node = material_manager.getDefaultMaterial(global_stack, position, None) #Find out what material we need to default to.
machine_manager.setMaterial(position, material_node) approximate_diameter = round(extruder.getProperty("material_diameter", "value"))
material_node = machine_node.variants[extruder.variant.getName()].preferredMaterial(approximate_diameter)
machine_manager.setMaterial(str(position), material_node)
self._application.globalContainerStackChanged.emit() self._application.globalContainerStackChanged.emit()

View File

@ -69,9 +69,9 @@ class SendMaterialJob(Job):
def _sendMaterials(self, materials_to_send: Set[str]) -> None: def _sendMaterials(self, materials_to_send: Set[str]) -> None:
container_registry = CuraApplication.getInstance().getContainerRegistry() container_registry = CuraApplication.getInstance().getContainerRegistry()
all_materials = container_registry.findInstanceContainersMetadata(type = "material") all_materials = container_registry.findInstanceContainersMetadata(type = "material")
all_root_materials = {material["base_file"] for material in all_materials if "base_file" in material} # Filters out uniques by making it a set. Don't include files without base file (i.e. empty material). all_base_files = {material["base_file"] for material in all_materials if "base_file" in material} # Filters out uniques by making it a set. Don't include files without base file (i.e. empty material).
for root_material_id in all_root_materials: for root_material_id in all_base_files:
if root_material_id not in materials_to_send: if root_material_id not in materials_to_send:
# If the material does not have to be sent we skip it. # If the material does not have to be sent we skip it.
continue continue
@ -129,10 +129,10 @@ class SendMaterialJob(Job):
def _getLocalMaterials() -> Dict[str, LocalMaterial]: def _getLocalMaterials() -> Dict[str, LocalMaterial]:
result = {} # type: Dict[str, LocalMaterial] result = {} # type: Dict[str, LocalMaterial]
all_materials = CuraApplication.getInstance().getContainerRegistry().findInstanceContainersMetadata(type = "material") all_materials = CuraApplication.getInstance().getContainerRegistry().findInstanceContainersMetadata(type = "material")
all_root_materials = [material for material in all_materials if material["id"] == material.get("base_file")] # Don't send materials without base_file: The empty material doesn't need to be sent. all_base_files = [material for material in all_materials if material["id"] == material.get("base_file")] # Don't send materials without base_file: The empty material doesn't need to be sent.
# Find the latest version of all material containers in the registry. # Find the latest version of all material containers in the registry.
for material_metadata in all_root_materials: for material_metadata in all_base_files:
try: try:
# material version must be an int # material version must be an int
material_metadata["version"] = int(material_metadata["version"]) material_metadata["version"] = int(material_metadata["version"])

View File

@ -22,7 +22,7 @@ Button
background: Rectangle background: Rectangle
{ {
id: backgroundRectangle id: backgroundRectangle
border.width: 1 border.width: UM.Theme.getSize("default_lining").width
border.color: button.checked ? UM.Theme.getColor("setting_control_border_highlight") : "transparent" border.color: button.checked ? UM.Theme.getColor("setting_control_border_highlight") : "transparent"
color: button.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent" color: button.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent"
radius: UM.Theme.getSize("action_button_radius").width radius: UM.Theme.getSize("action_button_radius").width

View File

@ -182,11 +182,12 @@ Popup
Rectangle Rectangle
{ {
height: 1 height: UM.Theme.getSize("default_lining").height
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
color: borderColor color: borderColor
} }
MenuButton MenuButton
{ {
labelText: Cura.Actions.addProfile.text labelText: Cura.Actions.addProfile.text

View File

@ -14,7 +14,7 @@ Item
property color activeColor: UM.Theme.getColor("primary") property color activeColor: UM.Theme.getColor("primary")
property color inactiveColor: UM.Theme.getColor("slider_groove") property color inactiveColor: UM.Theme.getColor("slider_groove")
property color defaultItemColor: UM.Theme.getColor("small_button_active") property color defaultItemColor: UM.Theme.getColor("small_button_active")
property int checkboxSize: UM.Theme.getSize("radio_button").height * 0.75 property int checkboxSize: Math.round(UM.Theme.getSize("radio_button").height * 0.75)
property int inactiveMarkerSize: 2 * barSize property int inactiveMarkerSize: 2 * barSize
property int barSize: UM.Theme.getSize("slider_groove_radius").height property int barSize: UM.Theme.getSize("slider_groove_radius").height
property var isCheckedFunction // Function that accepts the modelItem and returns if the item should be active. property var isCheckedFunction // Function that accepts the modelItem and returns if the item should be active.
@ -36,8 +36,8 @@ Item
{ {
left: buttonBar.left left: buttonBar.left
right: buttonBar.right right: buttonBar.right
leftMargin: (checkboxSize - inactiveMarkerSize) / 2 leftMargin: Math.round((checkboxSize - inactiveMarkerSize) / 2)
rightMargin: (checkboxSize - inactiveMarkerSize) / 2 rightMargin: Math.round((checkboxSize - inactiveMarkerSize) / 2)
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
} }
@ -72,7 +72,7 @@ Item
property Item previousItem: repeater.itemAt(index - 1) property Item previousItem: repeater.itemAt(index - 1)
height: barSize height: barSize
width: buttonBar.width / (repeater.count - 1) - activeComponent.width - 2 width: Math.round(buttonBar.width / (repeater.count - 1) - activeComponent.width - 2)
color: defaultItemColor color: defaultItemColor
anchors anchors
@ -110,7 +110,7 @@ Item
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
height: inactiveMarkerSize height: inactiveMarkerSize
width: inactiveMarkerSize width: inactiveMarkerSize
radius: width / 2 radius: Math.round(width / 2)
color: inactiveColor color: inactiveColor
} }
} }
@ -132,7 +132,7 @@ Item
{ {
height: checkboxSize height: checkboxSize
width: checkboxSize width: checkboxSize
radius: width / 2 radius: Math.round(width / 2)
border.color: defaultItemColor border.color: defaultItemColor
@ -143,7 +143,7 @@ Item
margins: 3 margins: 3
fill: parent fill: parent
} }
radius: width / 2 radius: Math.round(width / 2)
color: activeColor color: activeColor
visible: checkbox.checked visible: checkbox.checked
} }