Merge pull request #3377 from Ultimaker/refactoring_machine_manager

CURA-4606 Refactoring MachineManager
This commit is contained in:
Lipu Fei 2018-03-05 13:27:21 +01:00 committed by GitHub
commit 91e3e6b50c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
543 changed files with 5634 additions and 6057 deletions

View file

@ -937,8 +937,8 @@ class BuildVolume(SceneNode):
# stack. # stack.
# #
# \return A sequence of setting values, one for each extruder. # \return A sequence of setting values, one for each extruder.
def _getSettingFromAllExtruders(self, setting_key, property = "value"): def _getSettingFromAllExtruders(self, setting_key):
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, property) all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type") all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
for i in range(len(all_values)): for i in range(len(all_values)):
if not all_values[i] and (all_types[i] == "int" or all_types[i] == "float"): if not all_values[i] and (all_types[i] == "int" or all_types[i] == "float"):
@ -951,7 +951,7 @@ class BuildVolume(SceneNode):
# not part of the collision radius, such as bed adhesion (skirt/brim/raft) # not part of the collision radius, such as bed adhesion (skirt/brim/raft)
# and travel avoid distance. # and travel avoid distance.
def _getEdgeDisallowedSize(self): def _getEdgeDisallowedSize(self):
if not self._global_container_stack: if not self._global_container_stack or not self._global_container_stack.extruders:
return 0 return 0
container_stack = self._global_container_stack container_stack = self._global_container_stack

View file

@ -2,6 +2,8 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
#Type hinting. #Type hinting.
from typing import Dict from typing import Dict
from PyQt5.QtCore import QObject
from PyQt5.QtNetwork import QLocalServer from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket from PyQt5.QtNetwork import QLocalSocket
@ -52,13 +54,23 @@ from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyT
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingFunction import SettingFunction
from cura.Settings.MachineNameValidator import MachineNameValidator from cura.Settings.MachineNameValidator import MachineNameValidator
from cura.Settings.ProfilesModel import ProfilesModel
from cura.Settings.MaterialsModel import MaterialsModel from cura.Machines.Models.BuildPlateModel import BuildPlateModel
from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel from cura.Machines.Models.NozzleModel import NozzleModel
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.UserProfilesModel import UserProfilesModel
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.Machines.VariantManager import VariantManager
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
from . import PlatformPhysics from . import PlatformPhysics
from . import BuildVolume from . import BuildVolume
@ -71,17 +83,14 @@ from . import CameraImageProvider
from . import MachineActionManager from . import MachineActionManager
from cura.Settings.MachineManager import MachineManager from cura.Settings.MachineManager import MachineManager
from cura.Settings.MaterialManager import MaterialManager
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.UserChangesModel import UserChangesModel from cura.Settings.UserChangesModel import UserChangesModel
from cura.Settings.ExtrudersModel import ExtrudersModel from cura.Settings.ExtrudersModel import ExtrudersModel
from cura.Settings.ContainerSettingsModel import ContainerSettingsModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Settings.QualitySettingsModel import QualitySettingsModel from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
from cura.Settings.ContainerManager import ContainerManager from cura.Settings.ContainerManager import ContainerManager
from cura.ObjectsModel import ObjectsModel from cura.ObjectsModel import ObjectsModel
from cura.BuildPlateModel import BuildPlateModel
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
@ -216,6 +225,7 @@ class CuraApplication(QtApplication):
self._material_manager = None self._material_manager = None
self._object_manager = None self._object_manager = None
self._build_plate_model = None self._build_plate_model = None
self._multi_build_plate_model = None
self._setting_inheritance_manager = None self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None self._simple_mode_settings_manager = None
self._cura_scene_controller = None self._cura_scene_controller = None
@ -233,6 +243,8 @@ class CuraApplication(QtApplication):
if kwargs["parsed_command_line"].get("trigger_early_crash", False): if kwargs["parsed_command_line"].get("trigger_early_crash", False):
assert not "This crash is triggered by the trigger_early_crash command line argument." assert not "This crash is triggered by the trigger_early_crash command line argument."
self._variant_manager = None
self.default_theme = "cura-light" self.default_theme = "cura-light"
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png"))) self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
@ -287,21 +299,25 @@ class CuraApplication(QtApplication):
# Since they are empty, they should never be serialized and instead just programmatically created. # Since they are empty, they should never be serialized and instead just programmatically created.
# We need them to simplify the switching between materials. # We need them to simplify the switching between materials.
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self.empty_container = empty_container
empty_definition_changes_container = copy.deepcopy(empty_container) empty_definition_changes_container = copy.deepcopy(empty_container)
empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes") empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
empty_definition_changes_container.addMetaDataEntry("type", "definition_changes") empty_definition_changes_container.addMetaDataEntry("type", "definition_changes")
ContainerRegistry.getInstance().addContainer(empty_definition_changes_container) ContainerRegistry.getInstance().addContainer(empty_definition_changes_container)
self.empty_definition_changes_container = empty_definition_changes_container
empty_variant_container = copy.deepcopy(empty_container) empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant") empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant") empty_variant_container.addMetaDataEntry("type", "variant")
ContainerRegistry.getInstance().addContainer(empty_variant_container) ContainerRegistry.getInstance().addContainer(empty_variant_container)
self.empty_variant_container = empty_variant_container
empty_material_container = copy.deepcopy(empty_container) empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material") empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material") empty_material_container.addMetaDataEntry("type", "material")
ContainerRegistry.getInstance().addContainer(empty_material_container) ContainerRegistry.getInstance().addContainer(empty_material_container)
self.empty_material_container = empty_material_container
empty_quality_container = copy.deepcopy(empty_container) empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality") empty_quality_container.setMetaDataEntry("id", "empty_quality")
@ -310,12 +326,14 @@ class CuraApplication(QtApplication):
empty_quality_container.addMetaDataEntry("type", "quality") empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False) empty_quality_container.addMetaDataEntry("supported", False)
ContainerRegistry.getInstance().addContainer(empty_quality_container) ContainerRegistry.getInstance().addContainer(empty_quality_container)
self.empty_quality_container = empty_quality_container
empty_quality_changes_container = copy.deepcopy(empty_container) empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes") empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported") empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported")
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container) ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
self.empty_quality_changes_container = empty_quality_changes_container
with ContainerRegistry.getInstance().lockFile(): with ContainerRegistry.getInstance().lockFile():
ContainerRegistry.getInstance().loadAllMetadata() ContainerRegistry.getInstance().loadAllMetadata()
@ -381,6 +399,9 @@ class CuraApplication(QtApplication):
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
self._quality_profile_drop_down_menu_model = None
self._custom_quality_profile_drop_down_menu_model = None
CuraApplication.Created = True CuraApplication.Created = True
@pyqtSlot(str, result = str) @pyqtSlot(str, result = str)
@ -523,8 +544,6 @@ class CuraApplication(QtApplication):
has_user_interaction = True has_user_interaction = True
return has_user_interaction return has_user_interaction
onDiscardOrKeepProfileChangesClosed = pyqtSignal() # Used to notify other managers that the dialog was closed
@pyqtSlot(str) @pyqtSlot(str)
def discardOrKeepProfileChangesClosed(self, option): def discardOrKeepProfileChangesClosed(self, option):
if option == "discard": if option == "discard":
@ -547,7 +566,6 @@ class CuraApplication(QtApplication):
user_global_container.update() user_global_container.update()
# notify listeners that quality has changed (after user selected discard or keep) # notify listeners that quality has changed (after user selected discard or keep)
self.onDiscardOrKeepProfileChangesClosed.emit()
self.getMachineManager().activeQualityChanged.emit() self.getMachineManager().activeQualityChanged.emit()
@pyqtSlot(int) @pyqtSlot(int)
@ -723,6 +741,20 @@ class CuraApplication(QtApplication):
def run(self): def run(self):
self.preRun() self.preRun()
container_registry = ContainerRegistry.getInstance()
self._variant_manager = VariantManager(container_registry)
self._variant_manager.initialize()
from cura.Machines.MaterialManager import MaterialManager
self._material_manager = MaterialManager(container_registry, parent = self)
self._material_manager.initialize()
from cura.Machines.QualityManager import QualityManager
self._quality_manager = QualityManager(container_registry, parent = self)
self._quality_manager.initialize()
self._machine_manager = MachineManager(self)
# Check if we should run as single instance or not # Check if we should run as single instance or not
self._setUpSingleInstanceServer() self._setUpSingleInstanceServer()
@ -816,7 +848,7 @@ class CuraApplication(QtApplication):
def getMachineManager(self, *args) -> MachineManager: def getMachineManager(self, *args) -> MachineManager:
if self._machine_manager is None: if self._machine_manager is None:
self._machine_manager = MachineManager.createMachineManager() self._machine_manager = MachineManager(self)
return self._machine_manager return self._machine_manager
def getExtruderManager(self, *args): def getExtruderManager(self, *args):
@ -824,20 +856,32 @@ class CuraApplication(QtApplication):
self._extruder_manager = ExtruderManager.createExtruderManager() self._extruder_manager = ExtruderManager.createExtruderManager()
return self._extruder_manager return self._extruder_manager
def getVariantManager(self, *args):
return self._variant_manager
@pyqtSlot(result = QObject)
def getMaterialManager(self, *args): def getMaterialManager(self, *args):
if self._material_manager is None:
self._material_manager = MaterialManager.createMaterialManager()
return self._material_manager return self._material_manager
@pyqtSlot(result = QObject)
def getQualityManager(self, *args):
return self._quality_manager
def getObjectsModel(self, *args): def getObjectsModel(self, *args):
if self._object_manager is None: if self._object_manager is None:
self._object_manager = ObjectsModel.createObjectsModel() self._object_manager = ObjectsModel.createObjectsModel()
return self._object_manager return self._object_manager
@pyqtSlot(result = QObject)
def getMultiBuildPlateModel(self, *args):
if self._multi_build_plate_model is None:
self._multi_build_plate_model = MultiBuildPlateModel(self)
return self._multi_build_plate_model
@pyqtSlot(result = QObject)
def getBuildPlateModel(self, *args): def getBuildPlateModel(self, *args):
if self._build_plate_model is None: if self._build_plate_model is None:
self._build_plate_model = BuildPlateModel.createBuildPlateModel() self._build_plate_model = BuildPlateModel(self)
return self._build_plate_model return self._build_plate_model
def getCuraSceneController(self, *args): def getCuraSceneController(self, *args):
@ -875,6 +919,16 @@ class CuraApplication(QtApplication):
def getPrintInformation(self): def getPrintInformation(self):
return self._print_information return self._print_information
def getQualityProfilesDropDownMenuModel(self, *args, **kwargs):
if self._quality_profile_drop_down_menu_model is None:
self._quality_profile_drop_down_menu_model = QualityProfilesDropDownMenuModel(self)
return self._quality_profile_drop_down_menu_model
def getCustomQualityProfilesDropDownMenuModel(self, *args, **kwargs):
if self._custom_quality_profile_drop_down_menu_model is None:
self._custom_quality_profile_drop_down_menu_model = CustomQualityProfilesDropDownMenuModel(self)
return self._custom_quality_profile_drop_down_menu_model
## Registers objects for the QML engine to use. ## Registers objects for the QML engine to use.
# #
# \param engine The QML engine. # \param engine The QML engine.
@ -889,27 +943,34 @@ class CuraApplication(QtApplication):
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 2, "SceneController", self.getCuraSceneController) qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, "SceneController", self.getCuraSceneController)
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager) qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager) qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager) qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 2, "ObjectsModel", self.getObjectsModel) qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel)
qmlRegisterSingletonType(BuildPlateModel, "Cura", 1, 2, "BuildPlateModel", self.getBuildPlateModel) qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer") qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel) qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel") qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
qmlRegisterType(QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel") qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
qmlRegisterType(UserProfilesModel, "Cura", 1, 0, "UserProfilesModel") qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
"CustomQualityProfilesDropDownMenuModel", self.getCustomQualityProfilesDropDownMenuModel)
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel") qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
@ -991,7 +1052,7 @@ class CuraApplication(QtApplication):
count = 0 count = 0
scene_bounding_box = None scene_bounding_box = None
is_block_slicing_node = False is_block_slicing_node = False
active_build_plate = self.getBuildPlateModel().activeBuildPlate active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if ( if (
not issubclass(type(node), CuraSceneNode) or not issubclass(type(node), CuraSceneNode) or
@ -1240,7 +1301,7 @@ class CuraApplication(QtApplication):
@pyqtSlot() @pyqtSlot()
def arrangeAll(self): def arrangeAll(self):
nodes = [] nodes = []
active_build_plate = self.getBuildPlateModel().activeBuildPlate active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if not isinstance(node, SceneNode): if not isinstance(node, SceneNode):
continue continue
@ -1389,7 +1450,7 @@ class CuraApplication(QtApplication):
group_decorator = GroupDecorator() group_decorator = GroupDecorator()
group_node.addDecorator(group_decorator) group_node.addDecorator(group_decorator)
group_node.addDecorator(ConvexHullDecorator()) group_node.addDecorator(ConvexHullDecorator())
group_node.addDecorator(BuildPlateDecorator(self.getBuildPlateModel().activeBuildPlate)) group_node.addDecorator(BuildPlateDecorator(self.getMultiBuildPlateModel().activeBuildPlate))
group_node.setParent(self.getController().getScene().getRoot()) group_node.setParent(self.getController().getScene().getRoot())
group_node.setSelectable(True) group_node.setSelectable(True)
center = Selection.getSelectionCenter() center = Selection.getSelectionCenter()
@ -1534,7 +1595,7 @@ class CuraApplication(QtApplication):
arrange_objects_on_load = ( arrange_objects_on_load = (
not Preferences.getInstance().getValue("cura/use_multi_build_plate") or not Preferences.getInstance().getValue("cura/use_multi_build_plate") or
not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load")) not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load"))
target_build_plate = self.getBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1 target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
root = self.getController().getScene().getRoot() root = self.getController().getScene().getRoot()
fixed_nodes = [] fixed_nodes = []

View file

@ -0,0 +1,49 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from collections import OrderedDict
from UM.Logger import Logger
from UM.Settings.InstanceContainer import InstanceContainer
##
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
#
# ContainerNode is a multi-purpose class. It has two main purposes:
# 1. It encapsulates an InstanceContainer. It contains that InstanceContainer's
# - metadata (Always)
# - container (lazy-loaded when needed)
# 2. It also serves as a node in a hierarchical InstanceContainer lookup table/tree.
# This is used in Variant, Material, and Quality Managers.
#
class ContainerNode:
__slots__ = ("metadata", "container", "children_map")
def __init__(self, metadata: Optional[dict] = None):
self.metadata = metadata
self.container = None
self.children_map = OrderedDict()
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
return self.children_map.get(child_key)
def getContainer(self) -> "InstanceContainer":
if self.metadata is None:
raise RuntimeError("Cannot get container for a ContainerNode without metadata")
if self.container is None:
container_id = self.metadata["id"]
Logger.log("i", "Lazy-loading container [%s]", container_id)
from UM.Settings.ContainerRegistry import ContainerRegistry
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
if not container_list:
raise RuntimeError("Failed to lazy-load container [%s], cannot find it" % container_id)
self.container = container_list[0]
return self.container
def __str__(self) -> str:
return "%s[%s]" % (self.__class__.__name__, self.metadata.get("id"))

View file

@ -0,0 +1,26 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
#
# A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile.
# The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For
# example: "generic_abs" is the root material (ID) of "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4",
# and "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4" are derived materials of "generic_abs".
#
# Using "generic_abs" as an example, the MaterialGroup for "generic_abs" will contain the following information:
# - name: "generic_abs", root_material_id
# - root_material_node: MaterialNode of "generic_abs"
# - derived_material_node_list: A list of MaterialNodes that are derived from "generic_abs",
# so "generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc.
#
class MaterialGroup:
__slots__ = ("name", "root_material_node", "derived_material_node_list")
def __init__(self, name: str):
self.name = name
self.root_material_node = None
self.derived_material_node_list = []
def __str__(self) -> str:
return "%s[%s]" % (self.__class__.__name__, self.name)

View file

@ -0,0 +1,479 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from collections import defaultdict, OrderedDict
import copy
import uuid
from typing import Optional, TYPE_CHECKING
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
from UM.Util import parseBool
from .MaterialNode import MaterialNode
from .MaterialGroup import MaterialGroup
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
#
# MaterialManager maintains a number of maps and trees for material lookup.
# The models GUI and QML use are now only dependent on the MaterialManager. That means as long as the data in
# MaterialManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI.
#
# For now, updating the lookup maps and trees here is very simple: we discard the old data completely and recreate them
# again. This means the update is exactly the same as initialization. There are performance concerns about this approach
# but so far the creation of the tables and maps is very fast and there is no noticeable slowness, we keep it like this
# because it's simple.
#
class MaterialManager(QObject):
materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated.
def __init__(self, container_registry, parent = None):
super().__init__(parent)
self._application = Application.getInstance()
self._container_registry = container_registry # type: ContainerRegistry
self._fallback_materials_map = dict() # material_type -> generic material metadata
self._material_group_map = dict() # root_material_id -> MaterialGroup
self._diameter_machine_variant_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
# We're using these two maps to convert between the specific diameter material id and the generic material id
# because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
# i.e. generic_pla -> generic_pla_175
self._material_diameter_map = defaultdict(dict) # root_material_id -> approximate diameter str -> root_material_id for that diameter
self._diameter_material_map = dict() # material id including diameter (generic_pla_175) -> material root id (generic_pla)
# This is used in Legacy UM3 send material function and the material management page.
self._guid_material_groups_map = defaultdict(list) # GUID -> a list of material_groups
# The machine definition ID for the non-machine-specific materials.
# This is used as the last fallback option if the given machine-specific material(s) cannot be found.
self._default_machine_definition_id = "fdmprinter"
self._default_approximate_diameter_for_quality_search = "3"
# When a material gets added/imported, there can be more than one InstanceContainers. In those cases, we don't
# want to react on every container/metadata changed signal. The timer here is to buffer it a bit so we don't
# react too many time.
self._update_timer = QTimer(self)
self._update_timer.setInterval(300)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._updateMaps)
self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
def initialize(self):
# Find all materials and put them in a matrix for quick search.
material_metadata_list = self._container_registry.findContainersMetadata(type = "material")
self._material_group_map = dict()
# Map #1
# root_material_id -> MaterialGroup
for material_metadata in material_metadata_list:
material_id = material_metadata["id"]
# We don't store empty material in the lookup tables
if material_id == "empty_material":
continue
root_material_id = material_metadata.get("base_file")
if root_material_id not in self._material_group_map:
self._material_group_map[root_material_id] = MaterialGroup(root_material_id)
group = self._material_group_map[root_material_id]
# We only add root materials here
if material_id == root_material_id:
group.root_material_node = MaterialNode(material_metadata)
else:
new_node = MaterialNode(material_metadata)
group.derived_material_node_list.append(new_node)
# Order this map alphabetically so it's easier to navigate in a debugger
self._material_group_map = OrderedDict(sorted(self._material_group_map.items(), key = lambda x: x[0]))
# Map #1.5
# GUID -> material group list
self._guid_material_groups_map = defaultdict(list)
for root_material_id, material_group in self._material_group_map.items():
guid = material_group.root_material_node.metadata["GUID"]
self._guid_material_groups_map[guid].append(material_group)
# Map #2
# Lookup table for material type -> fallback material metadata, only for read-only materials
grouped_by_type_dict = dict()
for root_material_id, material_node in self._material_group_map.items():
if not self._container_registry.isReadOnly(root_material_id):
continue
material_type = material_node.root_material_node.metadata["material"]
if material_type not in grouped_by_type_dict:
grouped_by_type_dict[material_type] = {"generic": None,
"others": []}
brand = material_node.root_material_node.metadata["brand"]
if brand.lower() == "generic":
to_add = True
if material_type in grouped_by_type_dict:
diameter = material_node.root_material_node.metadata.get("approximate_diameter")
if diameter != self._default_approximate_diameter_for_quality_search:
to_add = False # don't add if it's not the default diameter
if to_add:
grouped_by_type_dict[material_type] = material_node.root_material_node.metadata
self._fallback_materials_map = grouped_by_type_dict
# Map #3
# There can be multiple material profiles for the same material with different diameters, such as "generic_pla"
# and "generic_pla_175". This is inconvenient when we do material-specific quality lookup because a quality can
# be for either "generic_pla" or "generic_pla_175", but not both. This map helps to get the correct material ID
# for quality search.
self._material_diameter_map = defaultdict(dict)
self._diameter_material_map = dict()
# Group the material IDs by the same name, material, brand, and color but with different diameters.
material_group_dict = dict()
keys_to_fetch = ("name", "material", "brand", "color")
for root_material_id, machine_node in self._material_group_map.items():
if not self._container_registry.isReadOnly(root_material_id):
continue
root_material_metadata = machine_node.root_material_node.metadata
key_data = []
for key in keys_to_fetch:
key_data.append(root_material_metadata.get(key))
key_data = tuple(key_data)
if key_data not in material_group_dict:
material_group_dict[key_data] = dict()
approximate_diameter = root_material_metadata.get("approximate_diameter")
material_group_dict[key_data][approximate_diameter] = root_material_metadata["id"]
# Map [root_material_id][diameter] -> root_material_id for this diameter
for data_dict in material_group_dict.values():
for root_material_id1 in data_dict.values():
if root_material_id1 in self._material_diameter_map:
continue
diameter_map = data_dict
for root_material_id2 in data_dict.values():
self._material_diameter_map[root_material_id2] = diameter_map
default_root_material_id = data_dict.get(self._default_approximate_diameter_for_quality_search)
if default_root_material_id is None:
default_root_material_id = list(data_dict.values())[0] # no default diameter present, just take "the" only one
for root_material_id in data_dict.values():
self._diameter_material_map[root_material_id] = default_root_material_id
# Map #4
# "machine" -> "variant_name" -> "root material ID" -> specific material InstanceContainer
# Construct the "machine" -> "variant" -> "root material ID" -> specific material InstanceContainer
self._diameter_machine_variant_material_map = dict()
for material_metadata in material_metadata_list:
# We don't store empty material in the lookup tables
if material_metadata["id"] == "empty_material":
continue
root_material_id = material_metadata["base_file"]
definition = material_metadata["definition"]
approximate_diameter = material_metadata["approximate_diameter"]
if approximate_diameter not in self._diameter_machine_variant_material_map:
self._diameter_machine_variant_material_map[approximate_diameter] = {}
machine_variant_material_map = self._diameter_machine_variant_material_map[approximate_diameter]
if definition not in machine_variant_material_map:
machine_variant_material_map[definition] = MaterialNode()
machine_node = machine_variant_material_map[definition]
variant_name = material_metadata.get("variant_name")
if not variant_name:
# if there is no variant, this material is for the machine, so put its metadata in the machine node.
machine_node.material_map[root_material_id] = MaterialNode(material_metadata)
else:
# this material is variant-specific, so we save it in a variant-specific node under the
# machine-specific node
if variant_name not in machine_node.children_map:
machine_node.children_map[variant_name] = MaterialNode()
variant_node = machine_node.children_map[variant_name]
if root_material_id not in variant_node.material_map:
variant_node.material_map[root_material_id] = MaterialNode(material_metadata)
else:
# Sanity check: make sure we don't have duplicated variant-specific materials for the same machine
raise RuntimeError("Found duplicate variant name [%s] for machine [%s] in material [%s]" %
(variant_name, definition, material_metadata["id"]))
self.materialsUpdated.emit()
def _updateMaps(self):
self.initialize()
def _onContainerMetadataChanged(self, container):
self._onContainerChanged(container)
def _onContainerChanged(self, container):
container_type = container.getMetaDataEntry("type")
if container_type != "material":
return
# update the maps
self._update_timer.start()
def getMaterialGroup(self, root_material_id: str) -> Optional[MaterialGroup]:
return self._material_group_map.get(root_material_id)
def getRootMaterialIDForDiameter(self, root_material_id: str, approximate_diameter: str) -> str:
return self._material_diameter_map.get(root_material_id).get(approximate_diameter, root_material_id)
def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
return self._diameter_material_map.get(root_material_id)
def getMaterialGroupListByGUID(self, guid: str) -> Optional[list]:
return self._guid_material_groups_map.get(guid)
#
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
#
def getAvailableMaterials(self, machine_definition_id: str, extruder_variant_name: Optional[str],
diameter: float) -> dict:
# round the diameter to get the approximate diameter
rounded_diameter = str(round(diameter))
if rounded_diameter not in self._diameter_machine_variant_material_map:
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
return dict()
# If there are variant materials, get the variant material
machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
machine_node = machine_variant_material_map.get(machine_definition_id)
default_machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
variant_node = None
if extruder_variant_name is not None and machine_node is not None:
variant_node = machine_node.getChildNode(extruder_variant_name)
nodes_to_check = [variant_node, machine_node, default_machine_node]
# Fallback mechanism of finding materials:
# 1. variant-specific material
# 2. machine-specific material
# 3. generic material (for fdmprinter)
material_id_metadata_dict = dict()
for node in nodes_to_check:
if node is not None:
for material_id, node in node.material_map.items():
if material_id not in material_id_metadata_dict:
material_id_metadata_dict[material_id] = node
return material_id_metadata_dict
#
# A convenience function to get available materials for the given machine with the extruder position.
#
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
extruder_stack: "ExtruderStack") -> Optional[dict]:
machine_definition_id = machine.definition.getId()
variant_name = None
if extruder_stack.variant.getId() != "empty_variant":
variant_name = extruder_stack.variant.getName()
diameter = extruder_stack.approximateMaterialDiameter
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
return self.getAvailableMaterials(machine_definition_id, variant_name, diameter)
#
# Gets MaterialNode for the given extruder and machine with the given material name.
# Returns None if:
# 1. the given machine doesn't have materials;
# 2. cannot find any material InstanceContainers with the given settings.
#
def getMaterialNode(self, machine_definition_id: str, extruder_variant_name: Optional[str],
diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
# round the diameter to get the approximate diameter
rounded_diameter = str(round(diameter))
if rounded_diameter not in self._diameter_machine_variant_material_map:
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]",
diameter, rounded_diameter, root_material_id)
return None
# If there are variant materials, get the variant material
machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
machine_node = machine_variant_material_map.get(machine_definition_id)
variant_node = None
# Fallback for "fdmprinter" if the machine-specific materials cannot be found
if machine_node is None:
machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
if machine_node is not None and extruder_variant_name is not None:
variant_node = machine_node.getChildNode(extruder_variant_name)
# Fallback mechanism of finding materials:
# 1. variant-specific material
# 2. machine-specific material
# 3. generic material (for fdmprinter)
nodes_to_check = [variant_node, machine_node,
machine_variant_material_map.get(self._default_machine_definition_id)]
material_node = None
for node in nodes_to_check:
if node is not None:
material_node = node.material_map.get(root_material_id)
if material_node:
break
return material_node
#
# Used by QualityManager. Built-in quality profiles may be based on generic material IDs such as "generic_pla".
# For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use
# the generic material IDs to search for qualities.
#
# An example would be, suppose we have machine with preferred material set to "filo3d_pla" (1.75mm), but its
# extruders only use 2.85mm materials, then we won't be able to find the preferred material for this machine.
# A fallback would be to fetch a generic material of the same type "PLA" as "filo3d_pla", and in this case it will
# be "generic_pla". This function is intended to get a generic fallback material for the given material type.
#
# This function returns the generic root material ID for the given material type, where material types are "PLA",
# "ABS", etc.
#
def getFallbackMaterialIdByMaterialType(self, material_type: str) -> Optional[str]:
# For safety
if material_type not in self._fallback_materials_map:
Logger.log("w", "The material type [%s] does not have a fallback material" % material_type)
return None
fallback_material = self._fallback_materials_map[material_type]
if fallback_material:
return self.getRootMaterialIDWithoutDiameter(fallback_material["id"])
else:
return None
def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: str) -> Optional["MaterialNode"]:
node = None
machine_definition = global_stack.definition
if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
material_diameter = machine_definition.getProperty("material_diameter", "value")
if isinstance(material_diameter, SettingFunction):
material_diameter = material_diameter(global_stack)
approximate_material_diameter = str(round(material_diameter))
root_material_id = machine_definition.getMetaDataEntry("preferred_material")
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter)
node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
material_diameter, root_material_id)
return node
#
# Methods for GUI
#
#
# Sets the new name for the given material.
#
@pyqtSlot("QVariant", str)
def setMaterialName(self, material_node: "MaterialNode", name: str):
root_material_id = material_node.metadata["base_file"]
if self._container_registry.isReadOnly(root_material_id):
Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
return
material_group = self.getMaterialGroup(root_material_id)
material_group.root_material_node.getContainer().setName(name)
#
# Removes the given material.
#
@pyqtSlot("QVariant")
def removeMaterial(self, material_node: "MaterialNode"):
root_material_id = material_node.metadata["base_file"]
material_group = self.getMaterialGroup(root_material_id)
if not material_group:
Logger.log("d", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id)
return
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
for node in nodes_to_remove:
self._container_registry.removeContainer(node.metadata["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, new_base_id = None, new_metadata = None) -> Optional[str]:
root_material_id = material_node.metadata["base_file"]
material_group = self.getMaterialGroup(root_material_id)
if not material_group:
Logger.log("i", "Unable to duplicate the material with id %s, because it doesn't exist.", root_material_id)
return None
base_container = material_group.root_material_node.getContainer()
# Ensure all settings are saved.
self._application.saveSettings()
# Create a new ID & container to hold the data.
new_containers = []
if new_base_id is None:
new_base_id = self._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 node in material_group.derived_material_node_list:
container_to_copy = node.getContainer()
# Create unique IDs for every clone.
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"):
variant_name = container_to_copy.getMetaDataEntry("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:
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)
self._container_registry.addContainer(container_to_add)
return new_base_id
#
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
#
@pyqtSlot(result = str)
def createMaterial(self) -> str:
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
# Ensure all settings are saved.
self._application.saveSettings()
global_stack = self._application.getGlobalContainerStack()
approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
root_material_id = "generic_pla"
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
material_group = self.getMaterialGroup(root_material_id)
# Create a new ID & container to hold the data.
new_id = self._container_registry.uniqueName("custom_material")
new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
"brand": catalog.i18nc("@label", "Custom"),
"GUID": str(uuid.uuid4()),
}
self.duplicateMaterial(material_group.root_material_node,
new_base_id = new_id,
new_metadata = new_metadata)
return new_id

View file

@ -0,0 +1,21 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from .ContainerNode import ContainerNode
#
# A MaterialNode is a node in the material lookup tree/map/table. It contains 2 (extra) fields:
# - material_map: a one-to-one map of "material_root_id" to material_node.
# - children_map: the key-value map for child nodes of this node. This is used in a lookup tree.
#
#
class MaterialNode(ContainerNode):
__slots__ = ("material_map", "children_map")
def __init__(self, metadata: Optional[dict] = None):
super().__init__(metadata = metadata)
self.material_map = {} # material_root_id -> material_node
self.children_map = {} # mapping for the child nodes

View file

@ -0,0 +1,46 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
#
# This is the base model class for GenericMaterialsModel and BrandMaterialsModel
# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
#
class BaseMaterialsModel(ListModel):
RootMaterialIdRole = Qt.UserRole + 1
IdRole = Qt.UserRole + 2
NameRole = Qt.UserRole + 3
BrandRole = Qt.UserRole + 4
MaterialRole = Qt.UserRole + 5
ColorRole = Qt.UserRole + 6
ContainerNodeRole = Qt.UserRole + 7
extruderPositionChanged = pyqtSignal()
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.RootMaterialIdRole, "root_material_id")
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.BrandRole, "brand")
self.addRoleName(self.MaterialRole, "material")
self.addRoleName(self.ColorRole, "color_name")
self.addRoleName(self.ContainerNodeRole, "container_node")
self._extruder_position = 0
def setExtruderPosition(self, position: int):
if self._extruder_position != position:
self._extruder_position = position
self.extruderPositionChanged.emit()
@pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
def extruderPosition(self) -> int:
return self._extruder_positoin

View file

@ -0,0 +1,131 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
from .BaseMaterialsModel import BaseMaterialsModel
#
# This is an intermediate model to group materials with different colours for a same brand and type.
#
class MaterialsModelGroupedByType(ListModel):
NameRole = Qt.UserRole + 1
ColorsRole = Qt.UserRole + 2
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.ColorsRole, "colors")
#
# This model is used to show branded materials in the material drop down menu.
# The structure of the menu looks like this:
# Brand -> Material Type -> list of materials
#
# To illustrate, a branded material menu may look like this:
# Ultimaker -> PLA -> Yellow PLA
# -> Black PLA
# -> ...
# -> ABS -> White ABS
# ...
#
class BrandMaterialsModel(ListModel):
NameRole = Qt.UserRole + 1
MaterialsRole = Qt.UserRole + 2
extruderPositionChanged = pyqtSignal()
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.MaterialsRole, "materials")
self._extruder_position = 0
from cura.CuraApplication import CuraApplication
self._machine_manager = CuraApplication.getInstance().getMachineManager()
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._material_manager = CuraApplication.getInstance().getMaterialManager()
self._machine_manager.globalContainerChanged.connect(self._update)
self._extruder_manager.activeExtruderChanged.connect(self._update)
self._material_manager.materialsUpdated.connect(self._update)
self._update()
def setExtruderPosition(self, position: int):
if self._extruder_position != position:
self._extruder_position = position
self.extruderPositionChanged.emit()
@pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
def extruderPosition(self) -> int:
return self._extruder_position
def _update(self):
global_stack = self._machine_manager.activeMachine
if global_stack is None:
self.setItems([])
return
extruder_position = str(self._extruder_position)
if extruder_position not in global_stack.extruders:
self.setItems([])
return
extruder_stack = global_stack.extruders[str(self._extruder_position)]
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
extruder_stack)
if available_material_dict is None:
self.setItems([])
return
brand_item_list = []
brand_group_dict = {}
for root_material_id, container_node in available_material_dict.items():
metadata = container_node.metadata
brand = metadata["brand"]
# Only add results for generic materials
if brand.lower() == "generic":
continue
if brand not in brand_group_dict:
brand_group_dict[brand] = {}
material_type = metadata["material"]
if material_type not in brand_group_dict[brand]:
brand_group_dict[brand][material_type] = []
item = {"root_material_id": root_material_id,
"id": metadata["id"],
"name": metadata["name"],
"brand": metadata["brand"],
"material": metadata["material"],
"color_name": metadata["color_name"],
"container_node": container_node
}
brand_group_dict[brand][material_type].append(item)
for brand, material_dict in brand_group_dict.items():
brand_item = {"name": brand,
"materials": MaterialsModelGroupedByType(self)}
material_type_item_list = []
for material_type, material_list in material_dict.items():
material_type_item = {"name": material_type,
"colors": BaseMaterialsModel(self)}
material_type_item["colors"].clear()
material_type_item["colors"].setItems(material_list)
material_type_item_list.append(material_type_item)
brand_item["materials"].setItems(material_type_item_list)
brand_item_list.append(brand_item)
self.setItems(brand_item_list)

View file

@ -0,0 +1,49 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt
from UM.Application import Application
from UM.Qt.ListModel import ListModel
from UM.Util import parseBool
from cura.Machines.VariantManager import VariantType
class BuildPlateModel(ListModel):
NameRole = Qt.UserRole + 1
ContainerNodeRole = Qt.UserRole + 2
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.ContainerNodeRole, "container_node")
self._application = Application.getInstance()
self._variant_manager = self._application._variant_manager
self._machine_manager = self._application.getMachineManager()
self._machine_manager.globalContainerChanged.connect(self._update)
self._update()
def _update(self):
global_stack = self._machine_manager._global_container_stack
if not global_stack:
self.setItems([])
return
has_variants = parseBool(global_stack.getMetaDataEntry("has_variant_buildplates", False))
if not has_variants:
self.setItems([])
return
variant_dict = self._variant_manager.getVariantNodes(global_stack, variant_type = VariantType.BUILD_PLATE)
item_list = []
for name, variant_node in variant_dict.items():
item = {"name": name,
"container_node": variant_node}
item_list.append(item)
self.setItems(item_list)

View file

@ -0,0 +1,37 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
#
# This model is used for the custom profile items in the profile drop down menu.
#
class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
def _update(self):
Logger.log("d", "Updating %s ...", self.__class__.__name__)
active_global_stack = self._machine_manager.activeMachine
if active_global_stack is None:
self.setItems([])
Logger.log("d", "No active GlobalStack, set %s as empty.", self.__class__.__name__)
return
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(active_global_stack)
item_list = []
for key in sorted(quality_changes_group_dict):
quality_changes_group = quality_changes_group_dict[key]
item = {"name": quality_changes_group.name,
"layer_height": "",
"layer_height_without_unit": "",
"available": quality_changes_group.is_available,
"quality_changes_group": quality_changes_group}
item_list.append(item)
self.setItems(item_list)

View file

@ -0,0 +1,60 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .BaseMaterialsModel import BaseMaterialsModel
class GenericMaterialsModel(BaseMaterialsModel):
def __init__(self, parent = None):
super().__init__(parent)
from cura.CuraApplication import CuraApplication
self._machine_manager = CuraApplication.getInstance().getMachineManager()
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._material_manager = CuraApplication.getInstance().getMaterialManager()
self._machine_manager.globalContainerChanged.connect(self._update)
self._extruder_manager.activeExtruderChanged.connect(self._update)
self._material_manager.materialsUpdated.connect(self._update)
self._update()
def _update(self):
global_stack = self._machine_manager.activeMachine
if global_stack is None:
self.setItems([])
return
extruder_position = str(self._extruder_position)
if extruder_position not in global_stack.extruders:
self.setItems([])
return
extruder_stack = global_stack.extruders[extruder_position]
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
extruder_stack)
if available_material_dict is None:
self.setItems([])
return
item_list = []
for root_material_id, container_node in available_material_dict.items():
metadata = container_node.metadata
# Only add results for generic materials
if metadata["brand"].lower() != "generic":
continue
item = {"root_material_id": root_material_id,
"id": metadata["id"],
"name": metadata["name"],
"brand": metadata["brand"],
"material": metadata["material"],
"color_name": metadata["color_name"],
"container_node": container_node
}
item_list.append(item)
# Sort the item list by material name alphabetically
item_list = sorted(item_list, key = lambda d: d["name"])
self.setItems(item_list)

View file

@ -0,0 +1,101 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtProperty
from UM.Qt.ListModel import ListModel
#
# This model is for the Material management page.
#
class MaterialManagementModel(ListModel):
RootMaterialIdRole = Qt.UserRole + 1
DisplayNameRole = Qt.UserRole + 2
BrandRole = Qt.UserRole + 3
MaterialTypeRole = Qt.UserRole + 4
ColorNameRole = Qt.UserRole + 5
ColorCodeRole = Qt.UserRole + 6
ContainerNodeRole = Qt.UserRole + 7
ContainerIdRole = Qt.UserRole + 8
DescriptionRole = Qt.UserRole + 9
AdhesionInfoRole = Qt.UserRole + 10
ApproximateDiameterRole = Qt.UserRole + 11
GuidRole = Qt.UserRole + 12
DensityRole = Qt.UserRole + 13
DiameterRole = Qt.UserRole + 14
IsReadOnlyRole = Qt.UserRole + 15
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.RootMaterialIdRole, "root_material_id")
self.addRoleName(self.DisplayNameRole, "name")
self.addRoleName(self.BrandRole, "brand")
self.addRoleName(self.MaterialTypeRole, "material")
self.addRoleName(self.ColorNameRole, "color_name")
self.addRoleName(self.ColorCodeRole, "color_code")
self.addRoleName(self.ContainerNodeRole, "container_node")
self.addRoleName(self.ContainerIdRole, "container_id")
self.addRoleName(self.DescriptionRole, "description")
self.addRoleName(self.AdhesionInfoRole, "adhesion_info")
self.addRoleName(self.ApproximateDiameterRole, "approximate_diameter")
self.addRoleName(self.GuidRole, "guid")
self.addRoleName(self.DensityRole, "density")
self.addRoleName(self.DiameterRole, "diameter")
self.addRoleName(self.IsReadOnlyRole, "is_read_only")
from cura.CuraApplication import CuraApplication
self._container_registry = CuraApplication.getInstance().getContainerRegistry()
self._machine_manager = CuraApplication.getInstance().getMachineManager()
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._material_manager = CuraApplication.getInstance().getMaterialManager()
self._machine_manager.globalContainerChanged.connect(self._update)
self._extruder_manager.activeExtruderChanged.connect(self._update)
self._material_manager.materialsUpdated.connect(self._update)
self._update()
def _update(self):
global_stack = self._machine_manager.activeMachine
if global_stack is None:
self.setItems([])
return
active_extruder_stack = self._machine_manager.activeStack
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
active_extruder_stack)
if available_material_dict is None:
self.setItems([])
return
material_list = []
for root_material_id, container_node in available_material_dict.items():
keys_to_fetch = ("name",
"brand",
"material",
"color_name",
"color_code",
"description",
"adhesion_info",
"approximate_diameter",)
item = {"root_material_id": container_node.metadata["base_file"],
"container_node": container_node,
"guid": container_node.metadata["GUID"],
"container_id": container_node.metadata["id"],
"density": container_node.metadata.get("properties", {}).get("density", ""),
"diameter": container_node.metadata.get("properties", {}).get("diameter", ""),
"is_read_only": self._container_registry.isReadOnly(container_node.metadata["id"]),
}
for key in keys_to_fetch:
item[key] = container_node.metadata.get(key, "")
material_list.append(item)
material_list = sorted(material_list, key = lambda k: (k["brand"].lower(), k["name"]))
self.setItems(material_list)

View file

@ -1,24 +1,32 @@
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
from UM.Scene.Selection import Selection
from UM.Logger import Logger
from UM.Application import Application from UM.Application import Application
from UM.Scene.Selection import Selection
from UM.Qt.ListModel import ListModel
class BuildPlateModel(ListModel): #
# This is the model for multi build plate feature.
# This has nothing to do with the build plate types you can choose on the sidebar for a machine.
#
class MultiBuildPlateModel(ListModel):
maxBuildPlateChanged = pyqtSignal() maxBuildPlateChanged = pyqtSignal()
activeBuildPlateChanged = pyqtSignal() activeBuildPlateChanged = pyqtSignal()
selectionChanged = pyqtSignal() selectionChanged = pyqtSignal()
def __init__(self): def __init__(self, parent = None):
super().__init__() super().__init__(parent)
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
self._application = Application.getInstance()
self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers) Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
self._max_build_plate = 1 # default self._max_build_plate = 1 # default
self._active_build_plate = -1 self._active_build_plate = -1
self._selection_build_plates = []
def setMaxBuildPlate(self, max_build_plate): def setMaxBuildPlate(self, max_build_plate):
self._max_build_plate = max_build_plate self._max_build_plate = max_build_plate
@ -37,10 +45,6 @@ class BuildPlateModel(ListModel):
def activeBuildPlate(self): def activeBuildPlate(self):
return self._active_build_plate return self._active_build_plate
@staticmethod
def createBuildPlateModel():
return BuildPlateModel()
def _updateSelectedObjectBuildPlateNumbers(self, *args): def _updateSelectedObjectBuildPlateNumbers(self, *args):
result = set() result = set()
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():

View file

@ -0,0 +1,56 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt
from UM.Application import Application
from UM.Qt.ListModel import ListModel
from UM.Util import parseBool
class NozzleModel(ListModel):
IdRole = Qt.UserRole + 1
HotendNameRole = Qt.UserRole + 2
ContainerNodeRole = Qt.UserRole + 3
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.HotendNameRole, "hotend_name")
self.addRoleName(self.ContainerNodeRole, "container_node")
Application.getInstance().globalContainerStackChanged.connect(self._update)
Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
def _update(self):
self.items.clear()
variant_manager = Application.getInstance()._variant_manager
active_global_stack = Application.getInstance().getMachineManager()._global_container_stack
if active_global_stack is None:
self.setItems([])
return
has_variants = parseBool(active_global_stack.getMetaDataEntry("has_variants", False))
if not has_variants:
self.setItems([])
return
variant_node_dict = variant_manager.getVariantNodes(active_global_stack)
if not variant_node_dict:
self.setItems([])
return
item_list = []
for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0]):
item = {"id": hotend_name,
"hotend_name": hotend_name,
"container_node": container_node
}
item_list.append(item)
self.setItems(item_list)

View file

@ -0,0 +1,122 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSlot
from UM.Qt.ListModel import ListModel
#
# This the QML model for the quality management page.
#
class QualityManagementModel(ListModel):
NameRole = Qt.UserRole + 1
IsReadOnlyRole = Qt.UserRole + 2
QualityGroupRole = Qt.UserRole + 3
QualityChangesGroupRole = Qt.UserRole + 4
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.IsReadOnlyRole, "is_read_only")
self.addRoleName(self.QualityGroupRole, "quality_group")
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
from cura.CuraApplication import CuraApplication
self._container_registry = CuraApplication.getInstance().getContainerRegistry()
self._machine_manager = CuraApplication.getInstance().getMachineManager()
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._quality_manager = CuraApplication.getInstance().getQualityManager()
self._machine_manager.globalContainerChanged.connect(self._update)
self._quality_manager.qualitiesUpdated.connect(self._update)
self._update()
def _update(self):
global_stack = self._machine_manager.activeMachine
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack)
available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items()
if quality_group.is_available)
if not available_quality_types and not quality_changes_group_dict:
# Nothing to show
self.setItems([])
return
item_list = []
# Create quality group items
for quality_group in quality_group_dict.values():
if not quality_group.is_available:
continue
item = {"name": quality_group.name,
"is_read_only": True,
"quality_group": quality_group,
"quality_changes_group": None}
item_list.append(item)
# Sort by quality names
item_list = sorted(item_list, key = lambda x: x["name"])
# Create quality_changes group items
quality_changes_item_list = []
for quality_changes_group in quality_changes_group_dict.values():
if quality_changes_group.quality_type not in available_quality_types:
continue
quality_group = quality_group_dict[quality_changes_group.quality_type]
item = {"name": quality_changes_group.name,
"is_read_only": False,
"quality_group": quality_group,
"quality_changes_group": quality_changes_group}
quality_changes_item_list.append(item)
# Sort quality_changes items by names and append to the item list
quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"])
item_list += quality_changes_item_list
self.setItems(item_list)
# TODO: Duplicated code here from InstanceContainersModel. Refactor and remove this later.
#
## Gets a list of the possible file filters that the plugins have
# registered they can read or write. The convenience meta-filters
# "All Supported Types" and "All Files" are added when listing
# readers, but not when listing writers.
#
# \param io_type \type{str} name of the needed IO type
# \return A list of strings indicating file name filters for a file
# dialog.
@pyqtSlot(str, result = "QVariantList")
def getFileNameFilters(self, io_type):
from UM.i18n import i18nCatalog
catalog = i18nCatalog("uranium")
#TODO: This function should be in UM.Resources!
filters = []
all_types = []
for plugin_id, meta_data in self._getIOPlugins(io_type):
for io_plugin in meta_data[io_type]:
filters.append(io_plugin["description"] + " (*." + io_plugin["extension"] + ")")
all_types.append("*.{0}".format(io_plugin["extension"]))
if "_reader" in io_type:
# if we're listing readers, add the option to show all supported files as the default option
filters.insert(0, catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types)))
filters.append(catalog.i18nc("@item:inlistbox", "All Files (*)")) # Also allow arbitrary files, if the user so prefers.
return filters
## Gets a list of profile reader or writer plugins
# \return List of tuples of (plugin_id, meta_data).
def _getIOPlugins(self, io_type):
from UM.PluginRegistry import PluginRegistry
pr = PluginRegistry.getInstance()
active_plugin_ids = pr.getActivePlugins()
result = []
for plugin_id in active_plugin_ids:
meta_data = pr.getMetaData(plugin_id)
if io_type in meta_data:
result.append( (plugin_id, meta_data) )
return result

View file

@ -0,0 +1,106 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from cura.Machines.QualityManager import QualityGroup
#
# QML Model for all built-in quality profiles. This model is used for the drop-down quality menu.
#
class QualityProfilesDropDownMenuModel(ListModel):
NameRole = Qt.UserRole + 1
QualityTypeRole = Qt.UserRole + 2
LayerHeightRole = Qt.UserRole + 3
LayerHeightUnitRole = Qt.UserRole + 4
AvailableRole = Qt.UserRole + 5
QualityGroupRole = Qt.UserRole + 6
QualityChangesGroupRole = Qt.UserRole + 7
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.QualityTypeRole, "quality_type")
self.addRoleName(self.LayerHeightRole, "layer_height")
self.addRoleName(self.LayerHeightUnitRole, "layer_height_unit")
self.addRoleName(self.AvailableRole, "available")
self.addRoleName(self.QualityGroupRole, "quality_group")
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
self._application = Application.getInstance()
self._machine_manager = self._application.getMachineManager()
self._quality_manager = Application.getInstance().getQualityManager()
self._application.globalContainerStackChanged.connect(self._update)
self._machine_manager.activeQualityGroupChanged.connect(self._update)
self._quality_manager.qualitiesUpdated.connect(self._update)
self._layer_height_unit = "" # This is cached
self._update()
def _update(self):
Logger.log("d", "Updating quality profile model ...")
global_stack = self._machine_manager.activeMachine
if global_stack is None:
self.setItems([])
Logger.log("d", "No active GlobalStack, set quality profile model as empty.")
return
# Check for material compatibility
if not self._machine_manager.activeMaterialsCompatible():
Logger.log("d", "No active material compatibility, set quality profile model as empty.")
self.setItems([])
return
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
item_list = []
for key in sorted(quality_group_dict):
quality_group = quality_group_dict[key]
layer_height = self._fetchLayerHeight(quality_group)
item = {"name": quality_group.name,
"quality_type": quality_group.quality_type,
"layer_height": layer_height,
"layer_height_unit": self._layer_height_unit,
"available": quality_group.is_available,
"quality_group": quality_group}
item_list.append(item)
# Sort items based on layer_height
item_list = sorted(item_list, key = lambda x: x["layer_height"])
self.setItems(item_list)
def _fetchLayerHeight(self, quality_group: "QualityGroup"):
global_stack = self._machine_manager.activeMachine
if not self._layer_height_unit:
unit = global_stack.definition.getProperty("layer_height", "unit")
if not unit:
unit = ""
self._layer_height_unit = unit
default_layer_height = global_stack.definition.getProperty("layer_height", "value")
# Get layer_height from the quality profile for the GlobalStack
container = quality_group.node_for_global.getContainer()
layer_height = default_layer_height
if container.hasProperty("layer_height", "value"):
layer_height = container.getProperty("layer_height", "value")
else:
# Look for layer_height in the GlobalStack from material -> definition
container = global_stack.definition
if container.hasProperty("layer_height", "value"):
layer_height = container.getProperty("layer_height", "value")
return float(layer_height)

View file

@ -0,0 +1,159 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry
#
# This model is used to show details settings of the selected quality in the quality management page.
#
class QualitySettingsModel(ListModel):
KeyRole = Qt.UserRole + 1
LabelRole = Qt.UserRole + 2
UnitRole = Qt.UserRole + 3
ProfileValueRole = Qt.UserRole + 4
ProfileValueSourceRole = Qt.UserRole + 5
UserValueRole = Qt.UserRole + 6
CategoryRole = Qt.UserRole + 7
def __init__(self, parent = None):
super().__init__(parent = parent)
self.addRoleName(self.KeyRole, "key")
self.addRoleName(self.LabelRole, "label")
self.addRoleName(self.UnitRole, "unit")
self.addRoleName(self.ProfileValueRole, "profile_value")
self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
self.addRoleName(self.UserValueRole, "user_value")
self.addRoleName(self.CategoryRole, "category")
self._container_registry = ContainerRegistry.getInstance()
self._application = Application.getInstance()
self._quality_manager = self._application.getQualityManager()
self._selected_position = "" # empty string means GlobalStack
# strings such as "0", "1", etc. mean extruder positions
self._selected_quality_item = None # The selected quality in the quality management page
self._i18n_catalog = None
self._quality_manager.qualitiesUpdated.connect(self._update)
self._update()
selectedPositionChanged = pyqtSignal()
selectedQualityItemChanged = pyqtSignal()
def setSelectedPosition(self, selected_position):
if selected_position != self._selected_position:
self._selected_position = selected_position
self.selectedPositionChanged.emit()
self._update()
@pyqtProperty(str, fset = setSelectedPosition, notify = selectedPositionChanged)
def selectedPosition(self):
return self._selected_position
def setSelectedQualityItem(self, selected_quality_item):
if selected_quality_item != self._selected_quality_item:
self._selected_quality_item = selected_quality_item
self.selectedQualityItemChanged.emit()
self._update()
@pyqtProperty("QVariantMap", fset = setSelectedQualityItem, notify = selectedQualityItemChanged)
def selectedQualityItem(self):
return self._selected_quality_item
def _update(self):
if self._selected_quality_item is None:
self.setItems([])
return
items = []
global_container_stack = self._application.getGlobalContainerStack()
definition_container = global_container_stack.definition
quality_group = self._selected_quality_item["quality_group"]
quality_changes_group = self._selected_quality_item["quality_changes_group"]
if self._selected_position == "":
quality_node = quality_group.node_for_global
else:
quality_node = quality_group.nodes_for_extruders.get(self._selected_position)
settings_keys = quality_group.getAllKeys()
quality_containers = [quality_node.getContainer()]
# Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch
# the settings in that quality_changes_group.
if quality_changes_group is not None:
if self._selected_position == "":
quality_changes_node = quality_changes_group.node_for_global
else:
quality_changes_node = quality_changes_group.nodes_for_extruders.get(self._selected_position)
if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime
try:
quality_containers.insert(0, quality_changes_node.getContainer())
except:
# FIXME: This is to prevent incomplete update of QualityManager
Logger.logException("d", "Failed to get container for quality changes node %s", quality_changes_node)
return
settings_keys.update(quality_changes_group.getAllKeys())
# We iterate over all definitions instead of settings in a quality/qualtiy_changes group is because in the GUI,
# the settings are grouped together by categories, and we had to go over all the definitions to figure out
# which setting belongs in which category.
current_category = ""
for definition in definition_container.findDefinitions():
if definition.type == "category":
current_category = definition.label
if self._i18n_catalog:
current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
continue
profile_value = None
profile_value_source = ""
for quality_container in quality_containers:
new_value = quality_container.getProperty(definition.key, "value")
if new_value is not None:
profile_value_source = quality_container.getMetaDataEntry("type")
profile_value = new_value
# Global tab should use resolve (if there is one)
if self._selected_position == "":
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
if resolve_value is not None and definition.key in settings_keys:
profile_value = resolve_value
if profile_value is not None:
break
if not self._selected_position:
user_value = global_container_stack.userChanges.getProperty(definition.key, "value")
else:
extruder_stack = global_container_stack.extruders[self._selected_position]
user_value = extruder_stack.userChanges.getProperty(definition.key, "value")
if profile_value is None and user_value is None:
continue
label = definition.label
if self._i18n_catalog:
label = self._i18n_catalog.i18nc(definition.key + " label", label)
items.append({
"key": definition.key,
"label": label,
"unit": definition.unit,
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
"profile_value_source": profile_value_source,
"user_value": "" if user_value is None else str(user_value),
"category": current_category
})
self.setItems(items)

View file

View file

@ -0,0 +1,53 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from .QualityGroup import QualityGroup
class QualityChangesGroup(QualityGroup):
def __init__(self, name: str, quality_type: str, parent = None):
super().__init__(name, quality_type, parent)
self._container_registry = Application.getInstance().getContainerRegistry()
def addNode(self, node: "QualityNode"):
# TODO: in 3.2 and earlier, a quality_changes container may have a field called "extruder" which contains the
# extruder definition ID it belongs to. But, in fact, we only need to know the following things:
# 1. which machine a custom profile is suitable for,
# 2. if this profile is for the GlobalStack,
# 3. if this profile is for an ExtruderStack and which one (the position).
#
# So, it is preferred to have a field like this:
# extruder_position = 1
# instead of this:
# extruder = custom_extruder_1
#
# An upgrade needs to be done if we want to do it this way. Before that, we use the extruder's definition
# to figure out its position.
#
extruder_definition_id = node.metadata.get("extruder")
if extruder_definition_id:
metadata_list = self._container_registry.findDefinitionContainersMetadata(id = extruder_definition_id)
if not metadata_list:
raise RuntimeError("%s cannot get metadata for extruder definition [%s]" %
(self, extruder_definition_id))
extruder_definition_metadata = metadata_list[0]
extruder_position = str(extruder_definition_metadata["position"])
if extruder_position in self.nodes_for_extruders:
raise RuntimeError("%s tries to overwrite the existing nodes_for_extruders position [%s] %s with %s" %
(self, extruder_position, self.node_for_global, node))
self.nodes_for_extruders[extruder_position] = node
else:
# This is a quality_changes for the GlobalStack
if self.node_for_global is not None:
raise RuntimeError("%s tries to overwrite the existing node_for_global %s with %s" %
(self, self.node_for_global, node))
self.node_for_global = node
def __str__(self) -> str:
return "%s[<%s>, available = %s]" % (self.__class__.__name__, self.name, self.is_available)

View file

@ -0,0 +1,50 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, List
from PyQt5.QtCore import QObject, pyqtSlot
#
# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used.
# Some concrete examples are Quality and QualityChanges: when we select quality type "normal", this quality type
# must be applied to all stacks in a machine, although each stack can have different containers. Use an Ultimaker 3
# as an example, suppose we choose quality type "normal", the actual InstanceContainers on each stack may look
# as below:
# GlobalStack ExtruderStack 1 ExtruderStack 2
# quality container: um3_global_normal um3_aa04_pla_normal um3_aa04_abs_normal
#
# This QualityGroup is mainly used in quality and quality_changes to group the containers that can be applied to
# a machine, so when a quality/custom quality is selected, the container can be directly applied to each stack instead
# of looking them up again.
#
class QualityGroup(QObject):
def __init__(self, name: str, quality_type: str, parent = None):
super().__init__(parent)
self.name = name
self.node_for_global = None # type: Optional["QualityGroup"]
self.nodes_for_extruders = dict() # position str -> QualityGroup
self.quality_type = quality_type
self.is_available = False
@pyqtSlot(result = str)
def getName(self) -> str:
return self.name
def getAllKeys(self) -> set:
result = set()
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
if node is None:
continue
result.update(node.getContainer().getAllKeys())
return result
def getAllNodes(self) -> List["QualityGroup"]:
result = []
if self.node_for_global is not None:
result.append(self.node_for_global)
for extruder_node in self.nodes_for_extruders.values():
result.append(extruder_node)
return result

View file

@ -0,0 +1,445 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING, Optional
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
from UM.Util import parseBool
from UM.Settings.InstanceContainer import InstanceContainer
from .QualityGroup import QualityGroup
from .QualityNode import QualityNode
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
from .QualityChangesGroup import QualityChangesGroup
#
# Similar to MaterialManager, QualityManager maintains a number of maps and trees for quality profile lookup.
# The models GUI and QML use are now only dependent on the QualityManager. That means as long as the data in
# QualityManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI.
#
# For now, updating the lookup maps and trees here is very simple: we discard the old data completely and recreate them
# again. This means the update is exactly the same as initialization. There are performance concerns about this approach
# but so far the creation of the tables and maps is very fast and there is no noticeable slowness, we keep it like this
# because it's simple.
#
class QualityManager(QObject):
qualitiesUpdated = pyqtSignal()
def __init__(self, container_registry, parent = None):
super().__init__(parent)
self._application = Application.getInstance()
self._material_manager = self._application.getMaterialManager()
self._container_registry = container_registry
self._empty_quality_container = self._application.empty_quality_container
self._empty_quality_changes_container = self._application.empty_quality_changes_container
self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
self._default_machine_definition_id = "fdmprinter"
self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
# When a custom quality gets added/imported, there can be more than one InstanceContainers. In those cases,
# we don't want to react on every container/metadata changed signal. The timer here is to buffer it a bit so
# we don't react too many time.
self._update_timer = QTimer(self)
self._update_timer.setInterval(300)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._updateMaps)
def initialize(self):
# Initialize the lookup tree for quality profiles with following structure:
# <machine> -> <variant> -> <material>
# -> <material>
self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
quality_metadata_list = self._container_registry.findContainersMetadata(type = "quality")
for metadata in quality_metadata_list:
if metadata["id"] == "empty_quality":
continue
definition_id = metadata["definition"]
quality_type = metadata["quality_type"]
root_material_id = metadata.get("material")
variant_name = metadata.get("variant")
is_global_quality = metadata.get("global_quality", False)
is_global_quality = is_global_quality or (root_material_id is None and variant_name is None)
# Sanity check: material+variant and is_global_quality cannot be present at the same time
if is_global_quality and (root_material_id or variant_name):
raise RuntimeError("Quality profile [%s] contains invalid data: it is a global quality but contains 'material' and 'nozzle' info." % metadata["id"])
if definition_id not in self._machine_variant_material_quality_type_to_quality_dict:
self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode()
machine_node = self._machine_variant_material_quality_type_to_quality_dict[definition_id]
if is_global_quality:
# For global qualities, save data in the machine node
machine_node.addQualityMetadata(quality_type, metadata)
continue
if variant_name is not None:
# If variant_name is specified in the quality/quality_changes profile, check if material is specified,
# too.
if variant_name not in machine_node.children_map:
machine_node.children_map[variant_name] = QualityNode()
variant_node = machine_node.children_map[variant_name]
if root_material_id is None:
# If only variant_name is specified but material is not, add the quality/quality_changes metadata
# into the current variant node.
variant_node.addQualityMetadata(quality_type, metadata)
else:
# If only variant_name and material are both specified, go one level deeper: create a material node
# under the current variant node, and then add the quality/quality_changes metadata into the
# material node.
if root_material_id not in variant_node.children_map:
variant_node.children_map[root_material_id] = QualityNode()
material_node = variant_node.children_map[root_material_id]
material_node.addQualityMetadata(quality_type, metadata)
else:
# If variant_name is not specified, check if material is specified.
if root_material_id is not None:
if root_material_id not in machine_node.children_map:
machine_node.children_map[root_material_id] = QualityNode()
material_node = machine_node.children_map[root_material_id]
material_node.addQualityMetadata(quality_type, metadata)
# Initialize the lookup tree for quality_changes profiles with following structure:
# <machine> -> <quality_type> -> <name>
quality_changes_metadata_list = self._container_registry.findContainersMetadata(type = "quality_changes")
for metadata in quality_changes_metadata_list:
if metadata["id"] == "empty_quality_changes":
continue
machine_definition_id = metadata["definition"]
quality_type = metadata["quality_type"]
if machine_definition_id not in self._machine_quality_type_to_quality_changes_dict:
self._machine_quality_type_to_quality_changes_dict[machine_definition_id] = QualityNode()
machine_node = self._machine_quality_type_to_quality_changes_dict[machine_definition_id]
machine_node.addQualityChangesMetadata(quality_type, metadata)
Logger.log("d", "Lookup tables updated.")
self.qualitiesUpdated.emit()
def _updateMaps(self):
self.initialize()
def _onContainerMetadataChanged(self, container):
self._onContainerChanged(container)
def _onContainerChanged(self, container):
container_type = container.getMetaDataEntry("type")
if container_type not in ("quality", "quality_changes"):
return
# update the cache table
self._update_timer.start()
# Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list):
used_extruders = set()
# TODO: This will change after the Machine refactoring
for i in range(machine.getProperty("machine_extruder_count", "value")):
used_extruders.add(str(i))
# Update the "is_available" flag for each quality group.
for quality_group in quality_group_list:
is_available = True
if quality_group.node_for_global is None:
is_available = False
if is_available:
for position in used_extruders:
if position not in quality_group.nodes_for_extruders:
is_available = False
break
quality_group.is_available = is_available
# Returns a dict of "custom profile name" -> QualityChangesGroup
def getQualityChangesGroups(self, machine: "GlobalStack") -> dict:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id)
if not machine_node:
Logger.log("i", "Cannot find node for machine def [%s] in QualityChanges lookup table", machine_definition_id)
return dict()
# Update availability for each QualityChangesGroup:
# A custom profile is always available as long as the quality_type it's based on is available
quality_group_dict = self.getQualityGroups(machine)
available_quality_type_list = [qt for qt, qg in quality_group_dict.items() if qg.is_available]
# Iterate over all quality_types in the machine node
quality_changes_group_dict = dict()
for quality_type, quality_changes_node in machine_node.quality_type_map.items():
for quality_changes_name, quality_changes_group in quality_changes_node.children_map.items():
quality_changes_group_dict[quality_changes_name] = quality_changes_group
quality_changes_group.is_available = quality_type in available_quality_type_list
return quality_changes_group_dict
#
# Gets all quality groups for the given machine. Both available and none available ones will be included.
# It returns a dictionary with "quality_type"s as keys and "QualityGroup"s as values.
# Whether a QualityGroup is available can be unknown via the field QualityGroup.is_available.
# For more details, see QualityGroup.
#
def getQualityGroups(self, machine: "GlobalStack") -> dict:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
# To find the quality container for the GlobalStack, check in the following fall-back manner:
# (1) the machine-specific node
# (2) the generic node
machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
nodes_to_check = [machine_node, default_machine_node]
# Iterate over all quality_types in the machine node
quality_group_dict = {}
for node in nodes_to_check:
if node and node.quality_type_map:
# Only include global qualities
if has_variant_materials:
quality_node = list(node.quality_type_map.values())[0]
is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
if not is_global_quality:
continue
for quality_type, quality_node in node.quality_type_map.items():
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
quality_group.node_for_global = quality_node
quality_group_dict[quality_type] = quality_group
break
# Iterate over all extruders to find quality containers for each extruder
for position, extruder in machine.extruders.items():
variant_name = None
if extruder.variant.getId() != "empty_variant":
variant_name = extruder.variant.getName()
# This is a list of root material IDs to use for searching for suitable quality profiles.
# The root material IDs in this list are in prioritized order.
root_material_id_list = []
has_material = False # flag indicating whether this extruder has a material assigned
if extruder.material.getId() != "empty_material":
has_material = True
root_material_id = extruder.material.getMetaDataEntry("base_file")
# Convert possible generic_pla_175 -> generic_pla
root_material_id = self._material_manager.getRootMaterialIDWithoutDiameter(root_material_id)
root_material_id_list.append(root_material_id)
# Also try to get the fallback material
material_type = extruder.material.getMetaDataEntry("material")
fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType(material_type)
if fallback_root_material_id:
root_material_id_list.append(fallback_root_material_id)
# Here we construct a list of nodes we want to look for qualities with the highest priority first.
# The use case is that, when we look for qualities for a machine, we first want to search in the following
# order:
# 1. machine-variant-and-material-specific qualities if exist
# 2. machine-variant-specific qualities if exist
# 3. machine-material-specific qualities if exist
# 4. machine-specific qualities if exist
# 5. generic qualities if exist
# Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
# the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
# qualities from there.
nodes_to_check = []
if variant_name:
# In this case, we have both a specific variant and a specific material
variant_node = machine_node.getChildNode(variant_name)
if variant_node and has_material:
for root_material_id in root_material_id_list:
material_node = variant_node.getChildNode(root_material_id)
if material_node:
nodes_to_check.append(material_node)
break
nodes_to_check.append(variant_node)
# In this case, we only have a specific material but NOT a variant
if has_material:
for root_material_id in root_material_id_list:
material_node = machine_node.getChildNode(root_material_id)
if material_node:
nodes_to_check.append(material_node)
break
nodes_to_check += [machine_node, default_machine_node]
for node in nodes_to_check:
if node and node.quality_type_map:
if has_variant_materials:
# Only include variant qualities; skip non global qualities
quality_node = list(node.quality_type_map.values())[0]
is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
if is_global_quality:
continue
for quality_type, quality_node in node.quality_type_map.items():
if quality_type not in quality_group_dict:
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
quality_group_dict[quality_type] = quality_group
quality_group = quality_group_dict[quality_type]
quality_group.nodes_for_extruders[position] = quality_node
break
# Update availabilities for each quality group
self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
return quality_group_dict
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
# To find the quality container for the GlobalStack, check in the following fall-back manner:
# (1) the machine-specific node
# (2) the generic node
machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(
self._default_machine_definition_id)
nodes_to_check = [machine_node, default_machine_node]
# Iterate over all quality_types in the machine node
quality_group_dict = dict()
for node in nodes_to_check:
if node and node.quality_type_map:
for quality_type, quality_node in node.quality_type_map.items():
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
quality_group.node_for_global = quality_node
quality_group_dict[quality_type] = quality_group
break
return quality_group_dict
#
# Methods for GUI
#
#
# Remove the given quality changes group.
#
@pyqtSlot(QObject)
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
for node in quality_changes_group.getAllNodes():
self._container_registry.removeContainer(node.metadata["id"])
#
# Rename a set of quality changes containers. Returns the new name.
#
@pyqtSlot(QObject, str, result = str)
def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str:
Logger.log("i", "Renaming QualityChangesGroup[%s] to [%s]", quality_changes_group.name, new_name)
if new_name == quality_changes_group.name:
Logger.log("i", "QualityChangesGroup name [%s] unchanged.", quality_changes_group.name)
return new_name
new_name = self._container_registry.uniqueName(new_name)
for node in quality_changes_group.getAllNodes():
node.getContainer().setName(new_name)
quality_changes_group.name = new_name
self._application.getMachineManager().activeQualityChanged.emit()
self._application.getMachineManager().activeQualityGroupChanged.emit()
return new_name
#
# Duplicates the given quality.
#
@pyqtSlot(str, "QVariantMap")
def duplicateQualityChanges(self, quality_changes_name, quality_model_item):
global_stack = self._application.getGlobalContainerStack()
if not global_stack:
Logger.log("i", "No active global stack, cannot duplicate quality changes.")
return
quality_group = quality_model_item["quality_group"]
quality_changes_group = quality_model_item["quality_changes_group"]
if quality_changes_group is None:
# create global quality changes only
new_quality_changes = self._createQualityChanges(quality_group.quality_type, quality_changes_name,
global_stack, extruder_id = None)
self._container_registry.addContainer(new_quality_changes)
else:
new_name = self._container_registry.uniqueName(quality_changes_name)
for node in quality_changes_group.getAllNodes():
container = node.getContainer()
new_id = self._container_registry.uniqueName(container.getId())
self._container_registry.addContainer(container.duplicate(new_id, new_name))
#
# Create a quality changes container with the given setup.
#
def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack",
extruder_id: Optional[str]) -> "InstanceContainer":
base_id = machine.definition.getId() if extruder_id is None else extruder_id
new_id = base_id + "_" + new_name
new_id = new_id.lower().replace(" ", "_")
new_id = self._container_registry.uniqueName(new_id)
# Create a new quality_changes container for the quality.
quality_changes = InstanceContainer(new_id)
quality_changes.setName(new_name)
quality_changes.addMetaDataEntry("type", "quality_changes")
quality_changes.addMetaDataEntry("quality_type", quality_type)
# If we are creating a container for an extruder, ensure we add that to the container
if extruder_id is not None:
quality_changes.addMetaDataEntry("extruder", extruder_id)
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
quality_changes.setDefinition(machine_definition_id)
quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion)
return quality_changes
#
# Gets the machine definition ID that can be used to search for Quality containers that are suitable for the given
# machine. The rule is as follows:
# 1. By default, the machine definition ID for quality container search will be "fdmprinter", which is the generic
# machine.
# 2. If a machine has its own machine quality (with "has_machine_quality = True"), we should use the given machine's
# own machine definition ID for quality search.
# Example: for an Ultimaker 3, the definition ID should be "ultimaker3".
# 3. When condition (2) is met, AND the machine has "quality_definition" defined in its definition file, then the
# definition ID specified in "quality_definition" should be used.
# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended
# shares the same set of qualities profiles as Ultimaker 3.
#
def getMachineDefinitionIDForQualitySearch(machine: "GlobalStack", default_definition_id: str = "fdmprinter") -> str:
machine_definition_id = default_definition_id
if parseBool(machine.getMetaDataEntry("has_machine_quality", False)):
# Only use the machine's own quality definition ID if this machine has machine quality.
machine_definition_id = machine.getMetaDataEntry("quality_definition")
if machine_definition_id is None:
machine_definition_id = machine.definition.getId()
return machine_definition_id

View file

@ -0,0 +1,35 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from .ContainerNode import ContainerNode
from .QualityChangesGroup import QualityChangesGroup
#
# QualityNode is used for BOTH quality and quality_changes containers.
#
class QualityNode(ContainerNode):
def __init__(self, metadata: Optional[dict] = None):
super().__init__(metadata = metadata)
self.quality_type_map = {} # quality_type -> QualityNode for InstanceContainer
def addQualityMetadata(self, quality_type: str, metadata: dict):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode(metadata)
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
return self.quality_type_map.get(quality_type)
def addQualityChangesMetadata(self, quality_type: str, metadata: dict):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode()
quality_type_node = self.quality_type_map[quality_type]
name = metadata["name"]
if name not in quality_type_node.children_map:
quality_type_node.children_map[name] = QualityChangesGroup(name, quality_type)
quality_changes_group = quality_type_node.children_map[name]
quality_changes_group.addNode(QualityNode(metadata))

View file

@ -0,0 +1,111 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from enum import Enum
from collections import OrderedDict
from typing import Optional, TYPE_CHECKING
from UM.Logger import Logger
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Util import parseBool
from cura.Machines.ContainerNode import ContainerNode
from cura.Settings.GlobalStack import GlobalStack
if TYPE_CHECKING:
from UM.Settings.DefinitionContainer import DefinitionContainer
class VariantType(Enum):
BUILD_PLATE = "buildplate"
NOZZLE = "nozzle"
ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
#
# VariantManager is THE place to look for a specific variant. It maintains a variant lookup table with the following
# structure:
#
# [machine_definition_id] -> [variant_type] -> [variant_name] -> ContainerNode(metadata / container)
# Example: "ultimaker3" -> "buildplate" -> "Glass" (if present) -> ContainerNode
# -> ...
# -> "nozzle" -> "AA 0.4"
# -> "BB 0.8"
# -> ...
#
# Note that the "container" field is not loaded in the beginning because it would defeat the purpose of lazy-loading.
# A container is loaded when getVariant() is called to load a variant InstanceContainer.
#
class VariantManager:
def __init__(self, container_registry):
self._container_registry = container_registry # type: ContainerRegistry
self._machine_to_variant_dict_map = dict() # <machine_type> -> <variant_dict>
self._exclude_variant_id_list = ["empty_variant"]
#
# Initializes the VariantManager including:
# - initializing the variant lookup table based on the metadata in ContainerRegistry.
#
def initialize(self):
self._machine_to_variant_dict_map = OrderedDict()
# Cache all variants from the container registry to a variant map for better searching and organization.
variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant")
for variant_metadata in variant_metadata_list:
if variant_metadata["id"] in self._exclude_variant_id_list:
Logger.log("d", "Exclude variant [%s]", variant_metadata["id"])
continue
variant_name = variant_metadata["name"]
variant_definition = variant_metadata["definition"]
if variant_definition not in self._machine_to_variant_dict_map:
self._machine_to_variant_dict_map[variant_definition] = OrderedDict()
for variant_type in ALL_VARIANT_TYPES:
self._machine_to_variant_dict_map[variant_definition][variant_type] = dict()
variant_type = variant_metadata["hardware_type"]
variant_type = VariantType(variant_type)
variant_dict = self._machine_to_variant_dict_map[variant_definition][variant_type]
if variant_name in variant_dict:
# ERROR: duplicated variant name.
raise RuntimeError("Found duplicated variant name [%s], type [%s] for machine [%s]" %
(variant_name, variant_type, variant_definition))
variant_dict[variant_name] = ContainerNode(metadata = variant_metadata)
#
# Gets the variant InstanceContainer with the given information.
# Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
#
def getVariantNode(self, machine_definition_id: str, variant_name: str,
variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> Optional["ContainerNode"]:
return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
def getVariantNodes(self, machine: "GlobalStack",
variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> dict:
machine_definition_id = machine.definition.getId()
return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
#
# Gets the default variant for the given machine definition.
#
def getDefaultVariantNode(self, machine_definition: "DefinitionContainer",
variant_type: VariantType) -> Optional["ContainerNode"]:
machine_definition_id = machine_definition.getId()
preferred_variant_name = None
if variant_type == VariantType.BUILD_PLATE:
if parseBool(machine_definition.getMetaDataEntry("has_variant_buildplates", False)):
preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_buildplate_name")
else:
if parseBool(machine_definition.getMetaDataEntry("has_variants", False)):
preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_name")
node = None
if preferred_variant_name:
node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
return node

View file

View file

@ -1,27 +1,22 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.Duration import Duration
from UM.Preferences import Preferences
from UM.Scene.SceneNode import SceneNode
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Settings.ExtruderManager import ExtruderManager
from typing import Dict from typing import Dict
import math import math
import os.path import os.path
import unicodedata import unicodedata
import json import json
import re # To create abbreviations for printer names. import re # To create abbreviations for printer names.
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.Duration import Duration
from UM.Preferences import Preferences
from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
## A class for processing and calculating minimum, current and maximum print time as well as managing the job name ## A class for processing and calculating minimum, current and maximum print time as well as managing the job name
@ -76,16 +71,19 @@ class PrintInformation(QObject):
self._active_build_plate = 0 self._active_build_plate = 0
self._initVariablesWithBuildPlate(self._active_build_plate) self._initVariablesWithBuildPlate(self._active_build_plate)
Application.getInstance().globalContainerStackChanged.connect(self._updateJobName) self._application = Application.getInstance()
Application.getInstance().fileLoaded.connect(self.setBaseName) self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
Application.getInstance().getBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
Application.getInstance().workspaceLoaded.connect(self.setProjectName) self._application.globalContainerStackChanged.connect(self._updateJobName)
self._application.globalContainerStackChanged.connect(self.setToZeroPrintInformation)
self._application.fileLoaded.connect(self.setBaseName)
self._application.workspaceLoaded.connect(self.setProjectName)
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
self._active_material_container = None self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged)
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._onActiveMaterialChanged) self._onActiveMaterialsChanged()
self._onActiveMaterialChanged()
self._material_amounts = [] self._material_amounts = []
@ -200,7 +198,8 @@ class PrintInformation(QObject):
self._current_print_time[build_plate_number].setDuration(total_estimated_time) self._current_print_time[build_plate_number].setDuration(total_estimated_time)
def _calculateInformation(self, build_plate_number): def _calculateInformation(self, build_plate_number):
if Application.getInstance().getGlobalContainerStack() is None: global_stack = Application.getInstance().getGlobalContainerStack()
if global_stack is None:
return return
self._material_lengths[build_plate_number] = [] self._material_lengths[build_plate_number] = []
@ -210,19 +209,17 @@ class PrintInformation(QObject):
material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings")) material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))
extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(Application.getInstance().getGlobalContainerStack().getId())) extruder_stacks = global_stack.extruders
for index, amount in enumerate(self._material_amounts): for position, extruder_stack in extruder_stacks.items():
index = int(position)
if index >= len(self._material_amounts):
continue
amount = self._material_amounts[index]
## Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some ## Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some
# list comprehension filtering to solve this for us. # list comprehension filtering to solve this for us.
if extruder_stacks: # Multi extrusion machine
extruder_stack = [extruder for extruder in extruder_stacks if extruder.getMetaDataEntry("position") == str(index)][0]
density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0) density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
material = extruder_stack.findContainer({"type": "material"}) material = extruder_stack.findContainer({"type": "material"})
radius = extruder_stack.getProperty("material_diameter", "value") / 2 radius = extruder_stack.getProperty("material_diameter", "value") / 2
else: # Machine with no extruder stacks
density = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("properties", {}).get("density", 0)
material = Application.getInstance().getGlobalContainerStack().findContainer({"type": "material"})
radius = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2
weight = float(amount) * float(density) / 1000 weight = float(amount) * float(density) / 1000
cost = 0 cost = 0
@ -260,25 +257,11 @@ class PrintInformation(QObject):
if preference != "cura/material_settings": if preference != "cura/material_settings":
return return
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1): for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
self._calculateInformation(build_plate_number) self._calculateInformation(build_plate_number)
def _onActiveMaterialChanged(self):
if self._active_material_container:
try:
self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged)
except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that is already disconnected.
pass
active_material_id = Application.getInstance().getMachineManager().activeMaterialId
active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id = active_material_id)
if active_material_containers:
self._active_material_container = active_material_containers[0]
self._active_material_container.metaDataChanged.connect(self._onMaterialMetaDataChanged)
def _onActiveBuildPlateChanged(self): def _onActiveBuildPlateChanged(self):
new_active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate new_active_build_plate = self._multi_build_plate_model.activeBuildPlate
if new_active_build_plate != self._active_build_plate: if new_active_build_plate != self._active_build_plate:
self._active_build_plate = new_active_build_plate self._active_build_plate = new_active_build_plate
@ -290,8 +273,8 @@ class PrintInformation(QObject):
self.materialNamesChanged.emit() self.materialNamesChanged.emit()
self.currentPrintTimeChanged.emit() self.currentPrintTimeChanged.emit()
def _onMaterialMetaDataChanged(self, *args, **kwargs): def _onActiveMaterialsChanged(self, *args, **kwargs):
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1): for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
self._calculateInformation(build_plate_number) self._calculateInformation(build_plate_number)
@pyqtSlot(str) @pyqtSlot(str)
@ -396,7 +379,9 @@ class PrintInformation(QObject):
return result return result
# Simulate message with zero time duration # Simulate message with zero time duration
def setToZeroPrintInformation(self, build_plate): def setToZeroPrintInformation(self, build_plate = None):
if build_plate is None:
build_plate = self._active_build_plate
# Construct the 0-time message # Construct the 0-time message
temp_message = {} temp_message = {}

View file

@ -1,332 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
# This collects a lot of quality and quality changes related code which was split between ContainerManager
# and the MachineManager and really needs to usable from both.
from typing import Any, Dict, List, Optional, TYPE_CHECKING
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.InstanceContainer import InstanceContainer
from cura.Settings.ExtruderManager import ExtruderManager
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.ExtruderStack import ExtruderStack
from UM.Settings.DefinitionContainer import DefinitionContainerInterface
class QualityManager:
## Get the singleton instance for this class.
@classmethod
def getInstance(cls) -> "QualityManager":
# Note: Explicit use of class name to prevent issues with inheritance.
if not QualityManager.__instance:
QualityManager.__instance = cls()
return QualityManager.__instance
__instance = None # type: "QualityManager"
## Find a quality by name for a specific machine definition and materials.
#
# \param quality_name
# \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is
# specified then the currently selected machine definition is used.
# \param material_containers_metadata If nothing is specified then the
# current set of selected materials is used.
# \return the matching quality container \type{InstanceContainer}
def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None) -> Optional[InstanceContainer]:
criteria = {"type": "quality", "name": quality_name}
result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
# Fall back to using generic materials and qualities if nothing could be found.
if not result and material_containers_metadata and len(material_containers_metadata) == 1:
basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
return result[0] if result else None
## Find a quality changes container by name.
#
# \param quality_changes_name \type{str} the name of the quality changes container.
# \param machine_definition (Optional) \type{DefinitionContainer} If nothing is
# specified then the currently selected machine definition is used..
# \return the matching quality changes containers \type{List[InstanceContainer]}
def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None):
if not machine_definition:
global_stack = Application.getGlobalContainerStack()
if not global_stack:
return [] #No stack, so no current definition could be found, so there are no quality changes either.
machine_definition = global_stack.definition
result = self.findAllQualityChangesForMachine(machine_definition)
result = [quality_change for quality_change in result if quality_change.getName() == quality_changes_name]
return result
## Fetch the list of available quality types for this combination of machine definition and materials.
#
# \param machine_definition \type{DefinitionContainer}
# \param material_containers \type{List[InstanceContainer]}
# \return \type{List[str]}
def findAllQualityTypesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[str]:
# Determine the common set of quality types which can be
# applied to all of the materials for this machine.
quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
common_quality_types = set(quality_type_dict.keys())
for material_container in material_containers[1:]:
next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
common_quality_types.intersection_update(set(next_quality_type_dict.keys()))
return list(common_quality_types)
def findAllQualitiesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[InstanceContainer]:
# Determine the common set of quality types which can be
# applied to all of the materials for this machine.
quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
qualities = set(quality_type_dict.values())
for material_container in material_containers[1:]:
next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
qualities.intersection_update(set(next_quality_type_dict.values()))
return list(qualities)
## Fetches a dict of quality types names to quality profiles for a combination of machine and material.
#
# \param machine_definition \type{DefinitionContainer} the machine definition.
# \param material \type{InstanceContainer} the material.
# \return \type{Dict[str, InstanceContainer]} the dict of suitable quality type names mapping to qualities.
def __fetchQualityTypeDictForMaterial(self, machine_definition: "DefinitionContainerInterface", material: InstanceContainer) -> Dict[str, InstanceContainer]:
qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
quality_type_dict = {}
for quality in qualities:
quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
return quality_type_dict
## Find a quality container by quality type.
#
# \param quality_type \type{str} the name of the quality type to search for.
# \param machine_definition (Optional) \type{InstanceContainer} If nothing is
# specified then the currently selected machine definition is used.
# \param material_containers_metadata If nothing is specified then the
# current set of selected materials is used.
# \return the matching quality container \type{InstanceContainer}
def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs) -> InstanceContainer:
criteria = kwargs
criteria["type"] = "quality"
if quality_type:
criteria["quality_type"] = quality_type
result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
# Fall back to using generic materials and qualities if nothing could be found.
if not result and material_containers_metadata and len(material_containers_metadata) == 1:
basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
if basic_materials:
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
return result[0] if result else None
## Find all suitable qualities for a combination of machine and material.
#
# \param machine_definition \type{DefinitionContainer} the machine definition.
# \param material_container \type{InstanceContainer} the material.
# \return \type{List[InstanceContainer]} the list of suitable qualities.
def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]:
criteria = {"type": "quality"}
result = self._getFilteredContainersForStack(machine_definition, [material_container.getMetaData()], **criteria)
if not result:
basic_materials = self._getBasicMaterialMetadatas(material_container.getMetaData())
if basic_materials:
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
return result
## Find all quality changes for a machine.
#
# \param machine_definition \type{DefinitionContainer} the machine definition.
# \return \type{List[InstanceContainer]} the list of quality changes
def findAllQualityChangesForMachine(self, machine_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
if machine_definition.getMetaDataEntry("has_machine_quality"):
definition_id = machine_definition.getId()
else:
definition_id = "fdmprinter"
filter_dict = { "type": "quality_changes", "definition": definition_id }
quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
return quality_changes_list
def findAllExtruderDefinitionsForMachine(self, machine_definition: "DefinitionContainerInterface") -> List["DefinitionContainerInterface"]:
filter_dict = { "machine": machine_definition.getId() }
return ContainerRegistry.getInstance().findDefinitionContainers(**filter_dict)
## Find all quality changes for a given extruder.
#
# \param extruder_definition The extruder to find the quality changes for.
# \return The list of quality changes for the given extruder.
def findAllQualityChangesForExtruder(self, extruder_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
filter_dict = {"type": "quality_changes", "extruder": extruder_definition.getId()}
return ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
## Find all usable qualities for a machine and extruders.
#
# Finds all of the qualities for this combination of machine and extruders.
# Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
# then only one of then is returned (at random).
#
# \param global_container_stack \type{GlobalStack} the global machine definition
# \param extruder_stacks \type{List[ExtruderStack]} the list of extruder stacks
# \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
# return come from the first extruder in the given list of extruders.
def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]:
global_machine_definition = global_container_stack.getBottom()
machine_manager = Application.getInstance().getMachineManager()
active_stack_id = machine_manager.activeStackId
materials = []
for stack in extruder_stacks:
if stack.getId() == active_stack_id and machine_manager.newMaterial:
materials.append(machine_manager.newMaterial)
else:
materials.append(stack.material)
quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
# Map the list of quality_types to InstanceContainers
qualities = self.findAllQualitiesForMachineMaterial(global_machine_definition, materials[0])
quality_type_dict = {}
for quality in qualities:
quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
return [quality_type_dict[quality_type] for quality_type in quality_types]
## Fetch more basic versions of a material.
#
# This tries to find a generic or basic version of the given material.
# \param material_container \type{Dict[str, Any]} The metadata of a
# material to find the basic versions of.
# \return \type{List[Dict[str, Any]]} A list of the metadata of basic
# materials, or an empty list if none could be found.
def _getBasicMaterialMetadatas(self, material_container: Dict[str, Any]) -> List[Dict[str, Any]]:
if "definition" not in material_container:
definition_id = "fdmprinter"
else:
material_container_definition = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = material_container["definition"])
if not material_container_definition:
definition_id = "fdmprinter"
else:
material_container_definition = material_container_definition[0]
if "has_machine_quality" not in material_container_definition:
definition_id = "fdmprinter"
else:
definition_id = material_container_definition.get("quality_definition", material_container_definition["id"])
base_material = material_container.get("material")
if base_material:
# There is a basic material specified
criteria = {
"type": "material",
"name": base_material,
"definition": definition_id,
"variant": material_container.get("variant")
}
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
return containers
return []
def _getFilteredContainers(self, **kwargs):
return self._getFilteredContainersForStack(None, None, **kwargs)
def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs):
# Fill in any default values.
if machine_definition is None:
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
quality_definition_id = machine_definition.getMetaDataEntry("quality_definition")
if quality_definition_id is not None:
machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition_id)[0]
if not material_metadata:
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
if active_stacks:
material_metadata = [stack.material.getMetaData() for stack in active_stacks]
criteria = kwargs
filter_by_material = False
machine_definition = self.getParentMachineDefinition(machine_definition)
criteria["definition"] = machine_definition.getId()
found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
whole_machine_definition = self.getWholeMachineDefinition(machine_definition)
if whole_machine_definition.getMetaDataEntry("has_machine_quality"):
definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId())
criteria["definition"] = definition_id
filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials")
# only fall back to "fdmprinter" when there is no container for this machine
elif not found_containers_with_machine_definition:
criteria["definition"] = "fdmprinter"
# Stick the material IDs in a set
material_ids = set()
for material_instance in material_metadata:
if material_instance is not None:
# Add the parent material too.
for basic_material in self._getBasicMaterialMetadatas(material_instance):
material_ids.add(basic_material["id"])
material_ids.add(material_instance["id"])
containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
result = []
for container in containers:
# If the machine specifies we should filter by material, exclude containers that do not match any active material.
if filter_by_material and container.getMetaDataEntry("material") not in material_ids and "global_quality" not in kwargs:
continue
result.append(container)
return result
## Get the parent machine definition of a machine definition.
#
# \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
# an extruder definition.
# \return \type{DefinitionContainer} the parent machine definition. If the given machine
# definition doesn't have a parent then it is simply returned.
def getParentMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
container_registry = ContainerRegistry.getInstance()
machine_entry = machine_definition.getMetaDataEntry("machine")
if machine_entry is None:
# We have a normal (whole) machine defintion
quality_definition = machine_definition.getMetaDataEntry("quality_definition")
if quality_definition is not None:
parent_machine_definition = container_registry.findDefinitionContainers(id = quality_definition)[0]
return self.getParentMachineDefinition(parent_machine_definition)
else:
return machine_definition
else:
# This looks like an extruder. Find the rest of the machine.
whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
parent_machine = self.getParentMachineDefinition(whole_machine)
if whole_machine is parent_machine:
# This extruder already belongs to a 'parent' machine def.
return machine_definition
else:
# Look up the corresponding extruder definition in the parent machine definition.
extruder_position = machine_definition.getMetaDataEntry("position")
parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
return container_registry.findDefinitionContainers(id = parent_extruder_id)[0]
## Get the whole/global machine definition from an extruder definition.
#
# \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
# an extruder definition.
# \return \type{DefinitionContainerInterface}
def getWholeMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
machine_entry = machine_definition.getMetaDataEntry("machine")
if machine_entry is None:
# This already is a 'global' machine definition.
return machine_definition
else:
container_registry = ContainerRegistry.getInstance()
whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
return whole_machine

View file

@ -68,7 +68,7 @@ class ConvexHullNode(SceneNode):
ConvexHullNode.shader.setUniformValue("u_opacity", 0.6) ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
if self.getParent(): if self.getParent():
if self.getMeshData() and isinstance(self._node, SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate: if self.getMeshData() and isinstance(self._node, SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate:
renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8) renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8)
if self._convex_hull_head_mesh: if self._convex_hull_head_mesh:
renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8) renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8)

View file

@ -4,7 +4,7 @@ from PyQt5.QtCore import Qt, pyqtSlot, QObject
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from cura.ObjectsModel import ObjectsModel from cura.ObjectsModel import ObjectsModel
from cura.BuildPlateModel import BuildPlateModel from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
from UM.Application import Application from UM.Application import Application
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -16,11 +16,11 @@ from UM.Signal import Signal
class CuraSceneController(QObject): class CuraSceneController(QObject):
activeBuildPlateChanged = Signal() activeBuildPlateChanged = Signal()
def __init__(self, objects_model: ObjectsModel, build_plate_model: BuildPlateModel): def __init__(self, objects_model: ObjectsModel, multi_build_plate_model: MultiBuildPlateModel):
super().__init__() super().__init__()
self._objects_model = objects_model self._objects_model = objects_model
self._build_plate_model = build_plate_model self._multi_build_plate_model = multi_build_plate_model
self._active_build_plate = -1 self._active_build_plate = -1
self._last_selected_index = 0 self._last_selected_index = 0
@ -41,9 +41,9 @@ class CuraSceneController(QObject):
self._max_build_plate = max_build_plate self._max_build_plate = max_build_plate
changed = True changed = True
if changed: if changed:
self._build_plate_model.setMaxBuildPlate(self._max_build_plate) self._multi_build_plate_model.setMaxBuildPlate(self._max_build_plate)
build_plates = [{"name": "Build Plate %d" % (i + 1), "buildPlateNumber": i} for i in range(self._max_build_plate + 1)] build_plates = [{"name": "Build Plate %d" % (i + 1), "buildPlateNumber": i} for i in range(self._max_build_plate + 1)]
self._build_plate_model.setItems(build_plates) self._multi_build_plate_model.setItems(build_plates)
if self._active_build_plate > self._max_build_plate: if self._active_build_plate > self._max_build_plate:
build_plate_number = 0 build_plate_number = 0
if self._last_selected_index >= 0: # go to the buildplate of the item you last selected if self._last_selected_index >= 0: # go to the buildplate of the item you last selected
@ -104,12 +104,12 @@ class CuraSceneController(QObject):
self._active_build_plate = nr self._active_build_plate = nr
Selection.clear() Selection.clear()
self._build_plate_model.setActiveBuildPlate(nr) self._multi_build_plate_model.setActiveBuildPlate(nr)
self._objects_model.setActiveBuildPlate(nr) self._objects_model.setActiveBuildPlate(nr)
self.activeBuildPlateChanged.emit() self.activeBuildPlateChanged.emit()
@staticmethod @staticmethod
def createCuraSceneController(): def createCuraSceneController():
objects_model = Application.getInstance().getObjectsModel() objects_model = Application.getInstance().getObjectsModel()
build_plate_model = Application.getInstance().getBuildPlateModel() multi_build_plate_model = Application.getInstance().getMultiBuildPlateModel()
return CuraSceneController(objects_model = objects_model, build_plate_model = build_plate_model) return CuraSceneController(objects_model = objects_model, multi_build_plate_model = multi_build_plate_model)

View file

@ -20,10 +20,10 @@ class CuraSceneNode(SceneNode):
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0 return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
def isVisible(self): def isVisible(self):
return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
def isSelectable(self) -> bool: def isSelectable(self) -> bool:
return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned ## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
# TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded # TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded

View file

@ -1,16 +1,14 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import copy
import os.path import os.path
import urllib.parse import urllib.parse
import uuid import uuid
from typing import Any, Dict, List, Union from typing import Dict, Union
from PyQt5.QtCore import QObject, QUrl, QVariant from PyQt5.QtCore import QObject, QUrl, QVariant
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from UM.Util import parseBool
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
from UM.SaveFile import SaveFile from UM.SaveFile import SaveFile
@ -22,7 +20,6 @@ from UM.Application import Application
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from cura.QualityManager import QualityManager
from UM.MimeTypeDatabase import MimeTypeNotFoundError from UM.MimeTypeDatabase import MimeTypeNotFoundError
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
@ -30,9 +27,11 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
## Manager class that contains common actions to deal with containers in Cura. ## Manager class that contains common actions to deal with containers in Cura.
# #
# This is primarily intended as a class to be able to perform certain actions # This is primarily intended as a class to be able to perform certain actions
@ -42,154 +41,12 @@ class ContainerManager(QObject):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._application = Application.getInstance()
self._container_registry = ContainerRegistry.getInstance() self._container_registry = ContainerRegistry.getInstance()
self._machine_manager = Application.getInstance().getMachineManager() self._machine_manager = self._application.getMachineManager()
self._material_manager = self._application.getMaterialManager()
self._container_name_filters = {} self._container_name_filters = {}
## Create a duplicate of the specified container
#
# This will create and add a duplicate of the container corresponding
# to the container ID.
#
# \param container_id \type{str} The ID of the container to duplicate.
#
# \return The ID of the new container, or an empty string if duplication failed.
@pyqtSlot(str, result = str)
def duplicateContainer(self, container_id):
#TODO: It should be able to duplicate a container of which only the metadata is known.
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could duplicate container %s because it was not found.", container_id)
return ""
container = containers[0]
new_container = self.duplicateContainerInstance(container)
return new_container.getId()
## Create a duplicate of the given container instance
#
# This will create and add a duplicate of the container that was passed.
#
# \param container \type{ContainerInterface} The container to duplicate.
#
# \return The duplicated container, or None if duplication failed.
def duplicateContainerInstance(self, container):
new_container = None
new_name = self._container_registry.uniqueName(container.getName())
# Only InstanceContainer has a duplicate method at the moment.
# So fall back to serialize/deserialize when no duplicate method exists.
if hasattr(container, "duplicate"):
new_container = container.duplicate(new_name)
else:
new_container = container.__class__(new_name)
new_container.deserialize(container.serialize())
new_container.setName(new_name)
# TODO: we probably don't want to add it to the registry here!
if new_container:
self._container_registry.addContainer(new_container)
return new_container
## Change the name of a specified container to a new name.
#
# \param container_id \type{str} The ID of the container to change the name of.
# \param new_id \type{str} The new ID of the container.
# \param new_name \type{str} The new name of the specified container.
#
# \return True if successful, False if not.
@pyqtSlot(str, str, str, result = bool)
def renameContainer(self, container_id, new_id, new_name):
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could rename container %s because it was not found.", container_id)
return False
container = containers[0]
# First, remove the container from the registry. This will clean up any files related to the container.
self._container_registry.removeContainer(container_id)
# Ensure we have a unique name for the container
new_name = self._container_registry.uniqueName(new_name)
# Then, update the name and ID of the container
container.setName(new_name)
container._id = new_id # TODO: Find a nicer way to set a new, unique ID
# Finally, re-add the container so it will be properly serialized again.
self._container_registry.addContainer(container)
return True
## Remove the specified container.
#
# \param container_id \type{str} The ID of the container to remove.
#
# \return True if the container was successfully removed, False if not.
@pyqtSlot(str, result = bool)
def removeContainer(self, container_id):
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could not remove container %s because it was not found.", container_id)
return False
self._container_registry.removeContainer(containers[0].getId())
return True
## Merge a container with another.
#
# This will try to merge one container into the other, by going through the container
# and setting the right properties on the other container.
#
# \param merge_into_id \type{str} The ID of the container to merge into.
# \param merge_id \type{str} The ID of the container to merge.
#
# \return True if successfully merged, False if not.
@pyqtSlot(str, result = bool)
def mergeContainers(self, merge_into_id, merge_id):
containers = self._container_registry.findContainers(id = merge_into_id)
if not containers:
Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id)
return False
merge_into = containers[0]
containers = self._container_registry.findContainers(id = merge_id)
if not containers:
Logger.log("w", "Could not merge container %s because it was not found", merge_id)
return False
merge = containers[0]
if not isinstance(merge, type(merge_into)):
Logger.log("w", "Cannot merge two containers of different types")
return False
self._performMerge(merge_into, merge)
return True
## Clear the contents of a container.
#
# \param container_id \type{str} The ID of the container to clear.
#
# \return True if successful, False if not.
@pyqtSlot(str, result = bool)
def clearContainer(self, container_id):
if self._container_registry.isReadOnly(container_id):
Logger.log("w", "Cannot clear read-only container %s", container_id)
return False
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could clear container %s because it was not found.", container_id)
return False
containers[0].clear()
return True
@pyqtSlot(str, str, result=str) @pyqtSlot(str, str, result=str)
def getContainerMetaDataEntry(self, container_id, entry_name): def getContainerMetaDataEntry(self, container_id, entry_name):
metadatas = self._container_registry.findContainersMetadata(id = container_id) metadatas = self._container_registry.findContainersMetadata(id = container_id)
@ -211,18 +68,15 @@ class ContainerManager(QObject):
# \param entry_value The new value of the entry. # \param entry_value The new value of the entry.
# #
# \return True if successful, False if not. # \return True if successful, False if not.
@pyqtSlot(str, str, str, result = bool) # TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
def setContainerMetaDataEntry(self, container_id, entry_name, entry_value): @pyqtSlot("QVariant", str, str)
if self._container_registry.isReadOnly(container_id): def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
Logger.log("w", "Cannot set metadata of read-only container %s.", container_id) root_material_id = container_node.metadata["base_file"]
if self._container_registry.isReadOnly(root_material_id):
Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
return False return False
containers = self._container_registry.findContainers(id = container_id) #We need the complete container, since we need to know whether the container is read-only or not. material_group = self._material_manager.getMaterialGroup(root_material_id)
if not containers:
Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id)
return False
container = containers[0]
entries = entry_name.split("/") entries = entry_name.split("/")
entry_name = entries.pop() entry_name = entries.pop()
@ -230,7 +84,7 @@ class ContainerManager(QObject):
sub_item_changed = False sub_item_changed = False
if entries: if entries:
root_name = entries.pop(0) root_name = entries.pop(0)
root = container.getMetaDataEntry(root_name) root = material_group.root_material_node.metadata.get(root_name)
item = root item = root
for _ in range(len(entries)): for _ in range(len(entries)):
@ -243,12 +97,11 @@ class ContainerManager(QObject):
entry_name = root_name entry_name = root_name
entry_value = root entry_value = root
container = material_group.root_material_node.getContainer()
container.setMetaDataEntry(entry_name, entry_value) container.setMetaDataEntry(entry_name, entry_value)
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed. if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
container.metaDataChanged.emit(container) container.metaDataChanged.emit(container)
return True
## Set a setting property of the specified container. ## Set a setting property of the specified container.
# #
# This will set the specified property of the specified setting of the container # This will set the specified property of the specified setting of the container
@ -306,60 +159,6 @@ class ContainerManager(QObject):
return container.getProperty(setting_key, property_name) return container.getProperty(setting_key, property_name)
## Set the name of the specified container.
@pyqtSlot(str, str, result = bool)
def setContainerName(self, container_id, new_name):
if self._container_registry.isReadOnly(container_id):
Logger.log("w", "Cannot set name of read-only container %s.", container_id)
return False
containers = self._container_registry.findContainers(id = container_id) #We need to get the full container, not just metadata, since we need to know whether it's read-only.
if not containers:
Logger.log("w", "Could not set name of container %s because it was not found.", container_id)
return False
containers[0].setName(new_name)
return True
## Find instance containers matching certain criteria.
#
# This effectively forwards to
# ContainerRegistry::findInstanceContainersMetadata.
#
# \param criteria A dict of key - value pairs to search for.
#
# \return A list of container IDs that match the given criteria.
@pyqtSlot("QVariantMap", result = "QVariantList")
def findInstanceContainers(self, criteria):
return [entry["id"] for entry in self._container_registry.findInstanceContainersMetadata(**criteria)]
@pyqtSlot(str, result = bool)
def isContainerUsed(self, container_id):
Logger.log("d", "Checking if container %s is currently used", container_id)
# check if this is a material container. If so, check if any material with the same base is being used by any
# stacks.
container_ids_to_check = [container_id]
container_results = self._container_registry.findInstanceContainersMetadata(id = container_id, type = "material")
if container_results:
this_container = container_results[0]
material_base_file = this_container["id"]
if "base_file" in this_container:
material_base_file = this_container["base_file"]
# check all material container IDs with the same base
material_containers = self._container_registry.findInstanceContainersMetadata(base_file = material_base_file,
type = "material")
if material_containers:
container_ids_to_check = [container["id"] for container in material_containers]
all_stacks = self._container_registry.findContainerStacks()
for stack in all_stacks:
for used_container_id in container_ids_to_check:
if used_container_id in [child.getId() for child in stack.getContainers()]:
Logger.log("d", "The container is in use by %s", stack.getId())
return True
return False
@pyqtSlot(str, result = str) @pyqtSlot(str, result = str)
def makeUniqueName(self, original_name): def makeUniqueName(self, original_name):
return self._container_registry.uniqueName(original_name) return self._container_registry.uniqueName(original_name)
@ -410,7 +209,7 @@ class ContainerManager(QObject):
return {"status": "error", "message": "Invalid path"} return {"status": "error", "message": "Invalid path"}
mime_type = None mime_type = None
if not file_type in self._container_name_filters: if file_type not in self._container_name_filters:
try: try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url) mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
except MimeTypeNotFoundError: except MimeTypeNotFoundError:
@ -450,7 +249,7 @@ class ContainerManager(QObject):
with SaveFile(file_url, "w") as f: with SaveFile(file_url, "w") as f:
f.write(contents) f.write(contents)
return { "status": "success", "message": "Succesfully exported container", "path": file_url} return {"status": "success", "message": "Successfully exported container", "path": file_url}
## Imports a profile from a file ## Imports a profile from a file
# #
@ -522,7 +321,7 @@ class ContainerManager(QObject):
self._performMerge(quality_changes, stack.getTop()) self._performMerge(quality_changes, stack.getTop())
self._machine_manager.activeQualityChanged.emit() self._machine_manager.activeQualityChangesGroupChanged.emit()
return True return True
@ -547,18 +346,16 @@ class ContainerManager(QObject):
# This will go through the global and extruder stacks and create quality_changes containers from # This will go through the global and extruder stacks and create quality_changes containers from
# the user containers in each stack. These then replace the quality_changes containers in the # the user containers in each stack. These then replace the quality_changes containers in the
# stack and clear the user settings. # stack and clear the user settings.
# @pyqtSlot(str)
# \return \type{bool} True if the operation was successfully, False if not.
@pyqtSlot(str, result = bool)
def createQualityChanges(self, base_name): def createQualityChanges(self, base_name):
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack: if not global_stack:
return False return
active_quality_name = self._machine_manager.activeQualityName active_quality_name = self._machine_manager.activeQualityOrQualityChangesName
if active_quality_name == "": if active_quality_name == "":
Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId()) Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
return False return
self._machine_manager.blurSettings.emit() self._machine_manager.blurSettings.emit()
if base_name is None or base_name == "": if base_name is None or base_name == "":
@ -567,382 +364,54 @@ class ContainerManager(QObject):
# Go through the active stacks and create quality_changes containers from the user containers. # Go through the active stacks and create quality_changes containers from the user containers.
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
user_container = stack.getTop() user_container = stack.userChanges
quality_container = stack.quality quality_container = stack.quality
quality_changes_container = stack.qualityChanges quality_changes_container = stack.qualityChanges
if not quality_container or not quality_changes_container: if not quality_container or not quality_changes_container:
Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId()) Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
continue continue
extruder_id = None if stack is global_stack else QualityManager.getInstance().getParentMachineDefinition(stack.getBottom()).getId() extruder_definition_id = None
new_changes = self._createQualityChanges(quality_container, unique_name, if isinstance(stack, ExtruderStack):
Application.getInstance().getGlobalContainerStack().getBottom(), extruder_definition_id = stack.definition.getId()
extruder_id) quality_type = quality_container.getMetaDataEntry("quality_type")
new_changes = self._createQualityChanges(quality_type, unique_name, global_stack, extruder_definition_id)
self._performMerge(new_changes, quality_changes_container, clear_settings = False) self._performMerge(new_changes, quality_changes_container, clear_settings = False)
self._performMerge(new_changes, user_container) self._performMerge(new_changes, user_container)
self._container_registry.addContainer(new_changes) self._container_registry.addContainer(new_changes)
stack.replaceContainer(stack.getContainerIndex(quality_changes_container), new_changes)
self._machine_manager.activeQualityChanged.emit()
return True
## Remove all quality changes containers matching a specified name.
#
# This will search for quality_changes containers matching the supplied name and remove them.
# Note that if the machine specifies that qualities should be filtered by machine and/or material
# only the containers related to the active machine/material are removed.
#
# \param quality_name The name of the quality changes to remove.
#
# \return \type{bool} True if successful, False if not.
@pyqtSlot(str, result = bool)
def removeQualityChanges(self, quality_name):
Logger.log("d", "Attempting to remove the quality change containers with name %s", quality_name)
containers_found = False
if not quality_name:
return containers_found # Without a name we will never find a container to remove.
# If the container that is being removed is the currently active quality, set another quality as the active quality
activate_quality = quality_name == self._machine_manager.activeQualityName
activate_quality_type = None
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack or not quality_name:
return ""
machine_definition = QualityManager.getInstance().getParentMachineDefinition(global_stack.getBottom())
for container in QualityManager.getInstance().findQualityChangesByName(quality_name, machine_definition):
containers_found = True
if activate_quality and not activate_quality_type:
activate_quality_type = container.getMetaDataEntry("quality")
self._container_registry.removeContainer(container.getId())
if not containers_found:
Logger.log("d", "Unable to remove quality containers, as we did not find any by the name of %s", quality_name)
elif activate_quality:
definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId
containers = self._container_registry.findInstanceContainersMetadata(type = "quality", definition = definition_id, quality_type = activate_quality_type)
if containers:
self._machine_manager.setActiveQuality(containers[0]["id"])
self._machine_manager.activeQualityChanged.emit()
return containers_found
## Rename a set of quality changes containers.
#
# This will search for quality_changes containers matching the supplied name and rename them.
# Note that if the machine specifies that qualities should be filtered by machine and/or material
# only the containers related to the active machine/material are renamed.
#
# \param quality_name The name of the quality changes containers to rename.
# \param new_name The new name of the quality changes.
#
# \return True if successful, False if not.
@pyqtSlot(str, str, result = bool)
def renameQualityChanges(self, quality_name, new_name):
Logger.log("d", "User requested QualityChanges container rename of %s to %s", quality_name, new_name)
if not quality_name or not new_name:
return False
if quality_name == new_name:
Logger.log("w", "Unable to rename %s to %s, because they are the same.", quality_name, new_name)
return True
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return False
self._machine_manager.blurSettings.emit()
new_name = self._container_registry.uniqueName(new_name)
container_registry = self._container_registry
containers_to_rename = self._container_registry.findInstanceContainersMetadata(type = "quality_changes", name = quality_name)
for container in containers_to_rename:
stack_id = global_stack.getId()
if "extruder" in container:
stack_id = container["extruder"]
container_registry.renameContainer(container["id"], new_name, self._createUniqueId(stack_id, new_name))
if not containers_to_rename:
Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name)
self._machine_manager.activeQualityChanged.emit()
return True
## Duplicate a specified set of quality or quality_changes containers.
#
# This will search for containers matching the specified name. If the container is a "quality" type container, a new
# quality_changes container will be created with the specified quality as base. If the container is a "quality_changes"
# container, it is simply duplicated and renamed.
#
# \param quality_name The name of the quality to duplicate.
#
# \return A string containing the name of the duplicated containers, or an empty string if it failed.
@pyqtSlot(str, str, result = str)
def duplicateQualityOrQualityChanges(self, quality_name, base_name):
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack or not quality_name:
return ""
machine_definition = global_stack.definition
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
if active_stacks is None:
return ""
material_metadatas = [stack.material.getMetaData() for stack in active_stacks]
result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
QualityManager.getInstance().getParentMachineDefinition(machine_definition),
material_metadatas)
return result[0].getName() if result else ""
## Duplicate a quality or quality changes profile specific to a machine type
#
# \param quality_name The name of the quality or quality changes container to duplicate.
# \param base_name The desired name for the new container.
# \param machine_definition The machine with the specific machine type.
# \param material_metadatas Metadata of materials
# \return List of duplicated quality profiles.
def _duplicateQualityOrQualityChangesForMachineType(self, quality_name: str, base_name: str, machine_definition: DefinitionContainer, material_metadatas: List[Dict[str, Any]]) -> List[InstanceContainer]:
Logger.log("d", "Attempting to duplicate the quality %s", quality_name)
if base_name is None:
base_name = quality_name
# Try to find a Quality with the name.
container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_metadatas)
if container:
Logger.log("d", "We found a quality to duplicate.")
return self._duplicateQualityForMachineType(container, base_name, machine_definition)
Logger.log("d", "We found a quality_changes to duplicate.")
# Assume it is a quality changes.
return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition)
# Duplicate a quality profile
def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition) -> List[InstanceContainer]:
if base_name is None:
base_name = quality_container.getName()
new_name = self._container_registry.uniqueName(base_name)
new_change_instances = []
# Handle the global stack first.
global_changes = self._createQualityChanges(quality_container, new_name, machine_definition, None)
new_change_instances.append(global_changes)
self._container_registry.addContainer(global_changes)
# Handle the extruders if present.
extruders = machine_definition.getMetaDataEntry("machine_extruder_trains")
if extruders:
for extruder_id in extruders:
extruder = extruders[extruder_id]
new_changes = self._createQualityChanges(quality_container, new_name, machine_definition, extruder)
new_change_instances.append(new_changes)
self._container_registry.addContainer(new_changes)
return new_change_instances
# Duplicate a quality changes container
def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition) -> List[InstanceContainer]:
new_change_instances = []
for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_name,
machine_definition):
base_id = container.getMetaDataEntry("extruder")
if not base_id:
base_id = container.getDefinition().getId()
new_unique_id = self._createUniqueId(base_id, base_name)
new_container = container.duplicate(new_unique_id, base_name)
new_change_instances.append(new_container)
self._container_registry.addContainer(new_container)
return new_change_instances
## Create a duplicate of a material, which has the same GUID and base_file metadata
#
# \return \type{str} the id of the newly created container.
@pyqtSlot(str, result = str)
def duplicateMaterial(self, material_id: str) -> str:
original = self._container_registry.findContainersMetadata(id = material_id)
if not original:
Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id)
return ""
original = original[0]
base_container_id = original.get("base_file")
base_container = self._container_registry.findContainers(id = base_container_id)
if not base_container:
Logger.log("d", "Unable to duplicate the material with id {material_id}, because base_file {base_container_id} doesn't exist.".format(material_id = material_id, base_container_id = base_container_id))
return ""
base_container = base_container[0]
#We'll copy all containers with the same base.
#This way the correct variant and machine still gets assigned when loading the copy of the material.
containers_to_copy = self._container_registry.findInstanceContainers(base_file = base_container_id)
# Ensure all settings are saved.
Application.getInstance().saveSettings()
# Create a new ID & container to hold the data.
new_containers = []
new_base_id = self._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
new_containers.append(new_base_container)
#Clone all of them.
clone_of_original = None #Keeping track of which one is the clone of the original material, since we need to return that.
for container_to_copy in containers_to_copy:
#Create unique IDs for every clone.
current_id = container_to_copy.getId()
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"):
variant = self._container_registry.findContainers(id = container_to_copy.getMetaDataEntry("variant"))[0]
new_id += "_" + variant.getName().replace(" ", "_")
if current_id == material_id:
clone_of_original = new_id
new_container = copy.deepcopy(container_to_copy)
new_container.getMetaData()["id"] = new_id
new_container.getMetaData()["base_file"] = new_base_id
new_containers.append(new_container)
for container_to_add in new_containers:
container_to_add.setDirty(True)
ContainerRegistry.getInstance().addContainer(container_to_add)
return self._getMaterialContainerIdForActiveMachine(clone_of_original)
## Create a duplicate of a material or it's original entry
#
# \return \type{str} the id of the newly created container.
@pyqtSlot(str, result = str)
def duplicateOriginalMaterial(self, material_id):
# check if the given material has a base file (i.e. was shipped by default)
base_file = self.getContainerMetaDataEntry(material_id, "base_file")
if base_file == "":
# there is no base file, so duplicate by ID
return self.duplicateMaterial(material_id)
else:
# there is a base file, so duplicate the original material
return self.duplicateMaterial(base_file)
## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
#
# \return \type{str} the id of the newly created container.
@pyqtSlot(result = str)
def createMaterial(self) -> str:
# Ensure all settings are saved.
Application.getInstance().saveSettings()
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return ""
approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
containers = self._container_registry.findInstanceContainersMetadata(id = "generic_pla*", approximate_diameter = approximate_diameter)
if not containers:
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.")
return ""
base_file = containers[0].get("base_file")
containers = self._container_registry.findInstanceContainers(id = base_file)
if not containers:
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.")
return ""
# Create a new ID & container to hold the data.
new_id = self._container_registry.uniqueName("custom_material")
container_type = type(containers[0]) # Always XMLMaterialProfile, since we specifically clone the base_file
duplicated_container = container_type(new_id)
# Instead of duplicating we load the data from the basefile again.
# This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
# are also correctly created.
with open(containers[0].getPath(), encoding="utf-8") as f:
duplicated_container.deserialize(f.read())
duplicated_container.setMetaDataEntry("GUID", str(uuid.uuid4()))
duplicated_container.setMetaDataEntry("brand", catalog.i18nc("@label", "Custom"))
# We're defaulting to PLA, as machines with material profiles don't like material types they don't know.
# TODO: This is a hack, the only reason this is in now is to bandaid the problem as we're close to a release!
duplicated_container.setMetaDataEntry("material", "PLA")
duplicated_container.setName(catalog.i18nc("@label", "Custom Material"))
self._container_registry.addContainer(duplicated_container)
return self._getMaterialContainerIdForActiveMachine(new_id)
## Find the id of a material container based on the new material
# Utilty function that is shared between duplicateMaterial and createMaterial
#
# \param base_file \type{str} the id of the created container.
def _getMaterialContainerIdForActiveMachine(self, base_file):
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return base_file
has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False))
has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False))
has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
if has_machine_materials or has_variant_materials:
if has_variants:
materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
else:
materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
if materials:
return materials[0]["id"]
Logger.log("w", "Unable to find a suitable container based on %s for the current machine.", base_file)
return "" # do not activate a new material if a container can not be found
return base_file
## Get a list of materials that have the same GUID as the reference material ## Get a list of materials that have the same GUID as the reference material
# #
# \param material_id \type{str} the id of the material for which to get the linked materials. # \param material_id \type{str} the id of the material for which to get the linked materials.
# \return \type{list} a list of names of materials with the same GUID # \return \type{list} a list of names of materials with the same GUID
@pyqtSlot(str, result = "QStringList") @pyqtSlot("QVariant", result = "QStringList")
def getLinkedMaterials(self, material_id: str): def getLinkedMaterials(self, material_node):
containers = self._container_registry.findInstanceContainersMetadata(id = material_id) guid = material_node.metadata["GUID"]
if not containers:
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
return []
material_container = containers[0] material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
material_base_file = material_container.get("base_file", "")
material_guid = material_container.get("GUID", "")
if not material_guid:
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
return []
containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_guid)
linked_material_names = [] linked_material_names = []
for container in containers: if material_group_list:
if container["id"] in [material_id, material_base_file] or container.get("base_file") != container["id"]: for material_group in material_group_list:
continue linked_material_names.append(material_group.root_material_node.metadata["name"])
linked_material_names.append(container["name"])
return linked_material_names return linked_material_names
## Unlink a material from all other materials by creating a new GUID ## Unlink a material from all other materials by creating a new GUID
# \param material_id \type{str} the id of the material to create a new GUID for. # \param material_id \type{str} the id of the material to create a new GUID for.
@pyqtSlot(str) @pyqtSlot("QVariant")
def unlinkMaterial(self, material_id: str): def unlinkMaterial(self, material_node):
containers = self._container_registry.findInstanceContainers(id=material_id) # Get the material group
if not containers: material_group = self._material_manager.getMaterialGroup(material_node.metadata["base_file"])
Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id)
return ""
containers[0].setMetaDataEntry("GUID", str(uuid.uuid4())) # Generate a new GUID
new_guid = str(uuid.uuid4())
# Update the GUID
# NOTE: We only need to set the root material container because XmlMaterialProfile.setMetaDataEntry() will
# take care of the derived containers too
container = material_group.root_material_node.getContainer()
container.setMetaDataEntry("GUID", new_guid)
## Get the singleton instance for this class. ## Get the singleton instance for this class.
@classmethod @classmethod
@ -1015,81 +484,6 @@ class ContainerManager(QObject):
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list) name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
self._container_name_filters[name_filter] = entry self._container_name_filters[name_filter] = entry
## Creates a unique ID for a container by prefixing the name with the stack ID.
#
# This method creates a unique ID for a container by prefixing it with a specified stack ID.
# This is done to ensure we have an easily identified ID for quality changes, which have the
# same name across several stacks.
#
# \param stack_id The ID of the stack to prepend.
# \param container_name The name of the container that we are creating a unique ID for.
#
# \return Container name prefixed with stack ID, in lower case with spaces replaced by underscores.
def _createUniqueId(self, stack_id, container_name):
result = stack_id + "_" + container_name
result = result.lower()
result.replace(" ", "_")
return result
## Create a quality changes container for a specified quality container.
#
# \param quality_container The quality container to create a changes container for.
# \param new_name The name of the new quality_changes container.
# \param machine_definition The machine definition this quality changes container is specific to.
# \param extruder_id
#
# \return A new quality_changes container with the specified container as base.
def _createQualityChanges(self, quality_container, new_name, machine_definition, extruder_id):
base_id = machine_definition.getId() if extruder_id is None else extruder_id
# Create a new quality_changes container for the quality.
quality_changes = InstanceContainer(self._createUniqueId(base_id, new_name))
quality_changes.setName(new_name)
quality_changes.addMetaDataEntry("type", "quality_changes")
quality_changes.addMetaDataEntry("quality_type", quality_container.getMetaDataEntry("quality_type"))
# If we are creating a container for an extruder, ensure we add that to the container
if extruder_id is not None:
quality_changes.addMetaDataEntry("extruder", extruder_id)
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
if not machine_definition.getMetaDataEntry("has_machine_quality"):
quality_changes.setDefinition("fdmprinter")
else:
quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition).getId())
from cura.CuraApplication import CuraApplication
quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
return quality_changes
## Import profiles from a list of file_urls.
# Each QUrl item must end with .curaprofile, or it will not be imported.
#
# \param QVariant<QUrl>, essentially a list with QUrl objects.
# \return Dict with keys status, text
@pyqtSlot("QVariantList", result="QVariantMap")
def importProfiles(self, file_urls):
status = "ok"
results = {"ok": [], "error": []}
for file_url in file_urls:
if not file_url.isValid():
continue
path = file_url.toLocalFile()
if not path:
continue
if not path.endswith(".curaprofile"):
continue
single_result = self._container_registry.importProfile(path)
if single_result["status"] == "error":
status = "error"
results[single_result["status"]].append(single_result["message"])
return {
"status": status,
"message": "\n".join(results["ok"] + results["error"])}
## Import single profile, file_url does not have to end with curaprofile ## Import single profile, file_url does not have to end with curaprofile
@pyqtSlot(QUrl, result="QVariantMap") @pyqtSlot(QUrl, result="QVariantMap")
def importProfile(self, file_url): def importProfile(self, file_url):
@ -1100,11 +494,13 @@ class ContainerManager(QObject):
return return
return self._container_registry.importProfile(path) return self._container_registry.importProfile(path)
@pyqtSlot("QVariantList", QUrl, str) @pyqtSlot(QObject, QUrl, str)
def exportProfile(self, instance_id: str, file_url: QUrl, file_type: str) -> None: def exportQualityChangesGroup(self, quality_changes_group, file_url: QUrl, file_type: str):
if not file_url.isValid(): if not file_url.isValid():
return return
path = file_url.toLocalFile() path = file_url.toLocalFile()
if not path: if not path:
return return
self._container_registry.exportProfile(instance_id, path, file_type)
container_list = [n.getContainer() for n in quality_changes_group.getAllNodes()]
self._container_registry.exportQualityProfile(container_list, path, file_type)

View file

@ -1,97 +0,0 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Qt.ListModel import ListModel
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot, QUrl
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction
class ContainerSettingsModel(ListModel):
LabelRole = Qt.UserRole + 1
CategoryRole = Qt.UserRole + 2
UnitRole = Qt.UserRole + 3
ValuesRole = Qt.UserRole + 4
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.LabelRole, "label")
self.addRoleName(self.CategoryRole, "category")
self.addRoleName(self.UnitRole, "unit")
self.addRoleName(self.ValuesRole, "values")
self._container_ids = []
self._containers = []
def _onPropertyChanged(self, key, property_name):
if property_name == "value":
self._update()
def _update(self):
items = []
if len(self._container_ids) == 0:
return
keys = []
for container in self._containers:
keys = keys + list(container.getAllKeys())
keys = list(set(keys)) # remove duplicate keys
for key in keys:
definition = None
category = None
values = []
for container in self._containers:
instance = container.getInstance(key)
if instance:
definition = instance.definition
# Traverse up to find the category
category = definition
while category.type != "category":
category = category.parent
value = container.getProperty(key, "value")
if type(value) == SettingFunction:
values.append("=\u0192")
else:
values.append(container.getProperty(key, "value"))
else:
values.append("")
items.append({
"key": key,
"values": values,
"label": definition.label,
"unit": definition.unit,
"category": category.label
})
items.sort(key = lambda k: (k["category"], k["key"]))
self.setItems(items)
## Set the ids of the containers which have the settings this model should list.
# Also makes sure the model updates when the containers have property changes
def setContainers(self, container_ids):
for container in self._containers:
container.propertyChanged.disconnect(self._onPropertyChanged)
self._container_ids = container_ids
self._containers = []
for container_id in self._container_ids:
containers = ContainerRegistry.getInstance().findContainers(id = container_id)
if containers:
containers[0].propertyChanged.connect(self._onPropertyChanged)
self._containers.append(containers[0])
self._update()
containersChanged = pyqtSignal()
@pyqtProperty("QVariantList", fset = setContainers, notify = containersChanged)
def containers(self):
return self.container_ids

View file

@ -25,14 +25,15 @@ from UM.Resources import Resources
from . import ExtruderStack from . import ExtruderStack
from . import GlobalStack from . import GlobalStack
from .ContainerManager import ContainerManager
from .ExtruderManager import ExtruderManager from .ExtruderManager import ExtruderManager
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
class CuraContainerRegistry(ContainerRegistry): class CuraContainerRegistry(ContainerRegistry):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -102,7 +103,7 @@ class CuraContainerRegistry(ContainerRegistry):
# \param instance_ids \type{list} the IDs of the profiles to export. # \param instance_ids \type{list} the IDs of the profiles to export.
# \param file_name \type{str} the full path and filename to export to. # \param file_name \type{str} the full path and filename to export to.
# \param file_type \type{str} the file type with the format "<description> (*.<extension>)" # \param file_type \type{str} the file type with the format "<description> (*.<extension>)"
def exportProfile(self, instance_ids, file_name, file_type): def exportQualityProfile(self, container_list, file_name, file_type):
# Parse the fileType to deduce what plugin can save the file format. # Parse the fileType to deduce what plugin can save the file format.
# fileType has the format "<description> (*.<extension>)" # fileType has the format "<description> (*.<extension>)"
split = file_type.rfind(" (*.") # Find where the description ends and the extension starts. split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
@ -121,31 +122,10 @@ class CuraContainerRegistry(ContainerRegistry):
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name)) catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
if result == QMessageBox.No: if result == QMessageBox.No:
return return
found_containers = []
extruder_positions = []
for instance_id in instance_ids:
containers = ContainerRegistry.getInstance().findInstanceContainers(id = instance_id)
if containers:
found_containers.append(containers[0])
# Determine the position of the extruder of this container
extruder_id = containers[0].getMetaDataEntry("extruder", "")
if extruder_id == "":
# Global stack
extruder_positions.append(-1)
else:
extruder_containers = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = extruder_id)
if extruder_containers:
extruder_positions.append(int(extruder_containers[0].get("position", 0)))
else:
extruder_positions.append(0)
# Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...)
found_containers = [containers for (positions, containers) in sorted(zip(extruder_positions, found_containers))]
profile_writer = self._findProfileWriter(extension, description) profile_writer = self._findProfileWriter(extension, description)
try: try:
success = profile_writer.write(file_name, found_containers) success = profile_writer.write(file_name, container_list)
except Exception as e: except Exception as e:
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e)) Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
m = Message(catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), m = Message(catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)),
@ -289,7 +269,7 @@ class CuraContainerRegistry(ContainerRegistry):
elif profile_index < len(machine_extruders) + 1: elif profile_index < len(machine_extruders) + 1:
# This is assumed to be an extruder profile # This is assumed to be an extruder profile
extruder_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_extruders[profile_index - 1].getBottom()) extruder_id = machine_extruders[profile_index - 1].definition.getId()
if not profile.getMetaDataEntry("extruder"): if not profile.getMetaDataEntry("extruder"):
profile.addMetaDataEntry("extruder", extruder_id) profile.addMetaDataEntry("extruder", extruder_id)
else: else:
@ -348,39 +328,16 @@ class CuraContainerRegistry(ContainerRegistry):
return catalog.i18nc("@info:status", "Profile is missing a quality type.") return catalog.i18nc("@info:status", "Profile is missing a quality type.")
quality_type_criteria = {"quality_type": quality_type} quality_type_criteria = {"quality_type": quality_type}
if self._machineHasOwnQualities(): global_stack = Application.getInstance().getGlobalContainerStack()
profile.setDefinition(self._activeQualityDefinition().getId()) definition_id = getMachineDefinitionIDForQualitySearch(global_stack)
if self._machineHasOwnMaterials(): profile.setDefinition(definition_id)
active_material_id = self._activeMaterialId()
if active_material_id and active_material_id != "empty": # only update if there is an active material
profile.addMetaDataEntry("material", active_material_id)
quality_type_criteria["material"] = active_material_id
quality_type_criteria["definition"] = profile.getDefinition().getId()
else:
profile.setDefinition("fdmprinter")
quality_type_criteria["definition"] = "fdmprinter"
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
del quality_type_criteria["definition"]
# materials = None
if "material" in quality_type_criteria:
# materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"])
del quality_type_criteria["material"]
# Do not filter quality containers here with materials because we are trying to import a profile, so it should
# NOT be restricted by the active materials on the current machine.
materials = None
# Check to make sure the imported profile actually makes sense in context of the current configuration. # Check to make sure the imported profile actually makes sense in context of the current configuration.
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
# successfully imported but then fail to show up. # successfully imported but then fail to show up.
from cura.QualityManager import QualityManager quality_manager = CuraApplication.getInstance()._quality_manager
qualities = QualityManager.getInstance()._getFilteredContainersForStack(machine_definition, materials, **quality_type_criteria) quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack)
if not qualities: if quality_type not in quality_group_dict:
return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type) return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)
ContainerRegistry.getInstance().addContainer(profile) ContainerRegistry.getInstance().addContainer(profile)
@ -400,18 +357,6 @@ class CuraContainerRegistry(ContainerRegistry):
result.append( (plugin_id, meta_data) ) result.append( (plugin_id, meta_data) )
return result return result
## Get the definition to use to select quality profiles for the active machine
# \return the active quality definition object or None if there is no quality definition
def _activeQualityDefinition(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(global_container_stack.getBottom())
definition = self.findDefinitionContainers(id = definition_id)[0]
if definition:
return definition
return None
## Returns true if the current machine requires its own materials ## Returns true if the current machine requires its own materials
# \return True if the current machine requires its own materials # \return True if the current machine requires its own materials
def _machineHasOwnMaterials(self): def _machineHasOwnMaterials(self):
@ -507,8 +452,6 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_stack.setDefinition(extruder_definition) extruder_stack.setDefinition(extruder_definition)
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
from cura.CuraApplication import CuraApplication
# create a new definition_changes container for the extruder stack # create a new definition_changes container for the extruder stack
definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings" definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings"
definition_changes_name = definition_changes_id definition_changes_name = definition_changes_id
@ -567,26 +510,28 @@ class CuraContainerRegistry(ContainerRegistry):
self.addContainer(user_container) self.addContainer(user_container)
extruder_stack.setUserChanges(user_container) extruder_stack.setUserChanges(user_container)
variant_id = "default" application = CuraApplication.getInstance()
empty_variant = application.empty_variant_container
empty_material = application.empty_material_container
empty_quality = application.empty_quality_container
if machine.variant.getId() not in ("empty", "empty_variant"): if machine.variant.getId() not in ("empty", "empty_variant"):
variant_id = machine.variant.getId() variant = machine.variant
else: else:
variant_id = "empty_variant" variant = empty_variant
extruder_stack.setVariantById(variant_id) extruder_stack.variant = variant
material_id = "default"
if machine.material.getId() not in ("empty", "empty_material"): if machine.material.getId() not in ("empty", "empty_material"):
material_id = machine.material.getId() material = machine.material
else: else:
material_id = "empty_material" material = empty_material
extruder_stack.setMaterialById(material_id) extruder_stack.material = material
quality_id = "default"
if machine.quality.getId() not in ("empty", "empty_quality"): if machine.quality.getId() not in ("empty", "empty_quality"):
quality_id = machine.quality.getId() quality = machine.quality
else: else:
quality_id = "empty_quality" quality = empty_quality
extruder_stack.setQualityById(quality_id) extruder_stack.quality = quality
machine_quality_changes = machine.qualityChanges machine_quality_changes = machine.qualityChanges
if new_global_quality_changes is not None: if new_global_quality_changes is not None:
@ -598,7 +543,7 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_quality_changes_container = extruder_quality_changes_container[0] extruder_quality_changes_container = extruder_quality_changes_container[0]
quality_changes_id = extruder_quality_changes_container.getId() quality_changes_id = extruder_quality_changes_container.getId()
extruder_stack.setQualityChangesById(quality_changes_id) extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
else: else:
# Some extruder quality_changes containers can be created at runtime as files in the qualities # Some extruder quality_changes containers can be created at runtime as files in the qualities
# folder. Those files won't be loaded in the registry immediately. So we also need to search # folder. Those files won't be loaded in the registry immediately. So we also need to search
@ -607,7 +552,7 @@ class CuraContainerRegistry(ContainerRegistry):
if extruder_quality_changes_container: if extruder_quality_changes_container:
quality_changes_id = extruder_quality_changes_container.getId() quality_changes_id = extruder_quality_changes_container.getId()
extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId()) extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId())
extruder_stack.setQualityChangesById(quality_changes_id) extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
else: else:
# if we still cannot find a quality changes container for the extruder, create a new one # if we still cannot find a quality changes container for the extruder, create a new one
container_name = machine_quality_changes.getName() container_name = machine_quality_changes.getName()
@ -642,7 +587,7 @@ class CuraContainerRegistry(ContainerRegistry):
machine_quality_changes.removeInstance(qc_setting_key, postpone_emit=True) machine_quality_changes.removeInstance(qc_setting_key, postpone_emit=True)
else: else:
extruder_stack.setQualityChangesById("empty_quality_changes") extruder_stack.qualityChanges = self.findInstanceContainers(id = "empty_quality_changes")[0]
self.addContainer(extruder_stack) self.addContainer(extruder_stack)

View file

@ -83,20 +83,6 @@ class CuraContainerStack(ContainerStack):
def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None: def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit) self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
## Set the quality changes container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
#
# \param new_quality_changes_id The ID of the new quality changes container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setQualityChangesById(self, new_quality_changes_id: str) -> None:
quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id)
if quality_changes:
self.setQualityChanges(quality_changes[0])
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id))
## Get the quality changes container. ## Get the quality changes container.
# #
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. # \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -110,31 +96,6 @@ class CuraContainerStack(ContainerStack):
def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None: def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit) self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
## Set the quality container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultQuality
# for details.
#
# \param new_quality_id The ID of the new quality container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setQualityById(self, new_quality_id: str) -> None:
quality = self._empty_quality
if new_quality_id == "default":
new_quality = self.findDefaultQuality()
if new_quality:
quality = new_quality
else:
qualities = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id)
if qualities:
quality = qualities[0]
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id))
self.setQuality(quality)
## Get the quality container. ## Get the quality container.
# #
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer. # \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -148,31 +109,6 @@ class CuraContainerStack(ContainerStack):
def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None: def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit) self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
## Set the material container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultMaterial
# for details.
#
# \param new_material_id The ID of the new material container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setMaterialById(self, new_material_id: str) -> None:
material = self._empty_material
if new_material_id == "default":
new_material = self.findDefaultMaterial()
if new_material:
material = new_material
else:
materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
if materials:
material = materials[0]
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id))
self.setMaterial(material)
## Get the material container. ## Get the material container.
# #
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer. # \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -186,31 +122,6 @@ class CuraContainerStack(ContainerStack):
def setVariant(self, new_variant: InstanceContainer) -> None: def setVariant(self, new_variant: InstanceContainer) -> None:
self.replaceContainer(_ContainerIndexes.Variant, new_variant) self.replaceContainer(_ContainerIndexes.Variant, new_variant)
## Set the variant container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultVariant
# for details.
#
# \param new_variant_id The ID of the new variant container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setVariantById(self, new_variant_id: str) -> None:
variant = self._empty_variant
if new_variant_id == "default":
new_variant = self.findDefaultVariantBuildplate() if self.getMetaDataEntry("type") == "machine" else self.findDefaultVariant()
if new_variant:
variant = new_variant
else:
variants = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id)
if variants:
variant = variants[0]
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id))
self.setVariant(variant)
## Get the variant container. ## Get the variant container.
# #
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer. # \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -224,18 +135,6 @@ class CuraContainerStack(ContainerStack):
def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None: def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes) self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)
## Set the definition changes container by an ID.
#
# \param new_definition_changes_id The ID of the new definition changes container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setDefinitionChangesById(self, new_definition_changes_id: str) -> None:
new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id)
if new_definition_changes:
self.setDefinitionChanges(new_definition_changes[0])
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id))
## Get the definition changes container. ## Get the definition changes container.
# #
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. # \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -249,18 +148,6 @@ class CuraContainerStack(ContainerStack):
def setDefinition(self, new_definition: DefinitionContainerInterface) -> None: def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
self.replaceContainer(_ContainerIndexes.Definition, new_definition) self.replaceContainer(_ContainerIndexes.Definition, new_definition)
## Set the definition container by an ID.
#
# \param new_definition_id The ID of the new definition container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setDefinitionById(self, new_definition_id: str) -> None:
new_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = new_definition_id)
if new_definition:
self.setDefinition(new_definition[0])
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_id))
## Get the definition container. ## Get the definition container.
# #
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer. # \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -348,6 +235,10 @@ class CuraContainerStack(ContainerStack):
elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type: elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type"))) raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type")))
current_container = self._containers[index]
if current_container.getId() == container.getId():
return
super().replaceContainer(index, container, postpone_emit) super().replaceContainer(index, container, postpone_emit)
## Overridden from ContainerStack ## Overridden from ContainerStack
@ -391,243 +282,6 @@ class CuraContainerStack(ContainerStack):
self._containers = new_containers self._containers = new_containers
## Find the variant that should be used as "default" variant.
#
# This will search for variants that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# The following criteria are used to find the default variant:
# - If the machine definition does not have a metadata entry "has_variants" set to True, return None
# - The definition of the variant should be the same as the machine definition for this stack.
# - The container should have a metadata entry "type" with value "variant".
# - If the machine definition has a metadata entry "preferred_variant", filter the variant IDs based on that.
#
# \return The container that should be used as default, or None if nothing was found or the machine does not use variants.
#
# \note This method assumes the stack has a valid machine definition.
def findDefaultVariant(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
# has_variants can be overridden in other containers and stacks.
# In the case of UM2, it is overridden in the GlobalStack
if not self.getMetaDataEntry("has_variants"):
# If the machine does not use variants, we should never set a variant.
return None
# First add any variant. Later, overwrite with preference if the preference is valid.
variant = None
definition_id = self._findInstanceContainerDefinitionId(definition)
variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant")
if variants:
variant = variants[0]
preferred_variant_id = definition.getMetaDataEntry("preferred_variant")
if preferred_variant_id:
preferred_variants = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_id, definition = definition_id, type = "variant")
if preferred_variants:
variant = preferred_variants[0]
else:
Logger.log("w", "The preferred variant \"{variant}\" of stack {stack} does not exist or is not a variant.", variant = preferred_variant_id, stack = self.id)
# And leave it at the default variant.
if variant:
return variant
Logger.log("w", "Could not find a valid default variant for stack {stack}", stack = self.id)
return None
## Find the global variant that should be used as "default". This is used for the buildplates.
#
# This will search for variants that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# The following criteria are used to find the default global variant:
# - If the machine definition does not have a metadata entry "has_variant_buildplates" set to True, return None
# - The definition of the variant should be the same as the machine definition for this stack.
# - The container should have a metadata entry "type" with value "variant" and "hardware_type" with value "buildplate".
# - If the machine definition has a metadata entry "preferred_variant_buildplate", filter the variant IDs based on that.
#
# \return The container that should be used as default, or None if nothing was found or the machine does not use variants.
#
# \note This method assumes the stack has a valid machine definition.
def findDefaultVariantBuildplate(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
# has_variant_buildplates can be overridden in other containers and stacks.
# In the case of UM2, it is overridden in the GlobalStack
if not self.getMetaDataEntry("has_variant_buildplates"):
# If the machine does not use variants, we should never set a variant.
return None
# First add any variant. Later, overwrite with preference if the preference is valid.
variant = None
definition_id = self._findInstanceContainerDefinitionId(definition)
variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant", hardware_type = "buildplate")
if variants:
variant = variants[0]
preferred_variant_buildplate_id = definition.getMetaDataEntry("preferred_variant_buildplate")
if preferred_variant_buildplate_id:
preferred_variant_buildplates = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_buildplate_id, definition = definition_id, type = "variant")
if preferred_variant_buildplates:
variant = preferred_variant_buildplates[0]
else:
Logger.log("w", "The preferred variant buildplate \"{variant}\" of stack {stack} does not exist or is not a variant.",
variant = preferred_variant_buildplate_id, stack = self.id)
# And leave it at the default variant.
if variant:
return variant
Logger.log("w", "Could not find a valid default buildplate variant for stack {stack}", stack = self.id)
return None
## Find the material that should be used as "default" material.
#
# This will search for materials that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# The following criteria are used to find the default material:
# - If the machine definition does not have a metadata entry "has_materials" set to True, return None
# - If the machine definition has a metadata entry "has_machine_materials", the definition of the material should
# be the same as the machine definition for this stack. Otherwise, the definition should be "fdmprinter".
# - The container should have a metadata entry "type" with value "material".
# - The material should have an approximate diameter that matches the machine
# - If the machine definition has a metadata entry "has_variants" and set to True, the "variant" metadata entry of
# the material should be the same as the ID of the variant in the stack. Only applies if "has_machine_materials" is also True.
# - If the stack currently has a material set, try to find a material that matches the current material by name.
# - Otherwise, if the machine definition has a metadata entry "preferred_material", try to find a material that matches the specified ID.
#
# \return The container that should be used as default, or None if nothing was found or the machine does not use materials.
def findDefaultMaterial(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
if not definition.getMetaDataEntry("has_materials"):
# Machine does not use materials, never try to set it.
return None
search_criteria = {"type": "material"}
if definition.getMetaDataEntry("has_machine_materials"):
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
if definition.getMetaDataEntry("has_variants"):
search_criteria["variant"] = self.variant.id
else:
search_criteria["definition"] = "fdmprinter"
if self.material != self._empty_material:
search_criteria["name"] = self.material.name
else:
preferred_material = definition.getMetaDataEntry("preferred_material")
if preferred_material:
search_criteria["id"] = preferred_material
approximate_material_diameter = str(round(self.getProperty("material_diameter", "value")))
search_criteria["approximate_diameter"] = approximate_material_diameter
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if not materials:
Logger.log("w", "The preferred material \"{material}\" could not be found for stack {stack}", material = preferred_material, stack = self.id)
# We failed to find any materials matching the specified criteria, drop some specific criteria and try to find
# a material that sort-of matches what we want.
search_criteria.pop("variant", None)
search_criteria.pop("id", None)
search_criteria.pop("name", None)
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if not materials:
Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
return None
for material in materials:
# Prefer a read-only material
if ContainerRegistry.getInstance().isReadOnly(material.getId()):
return material
return materials[0]
## Find the quality that should be used as "default" quality.
#
# This will search for qualities that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# \return The container that should be used as default, or None if nothing was found.
def findDefaultQuality(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
registry = ContainerRegistry.getInstance()
material_container = self.material if self.material.getId() not in (self._empty_material.getId(), self._empty_instance_container.getId()) else None
search_criteria = {"type": "quality"}
if definition.getMetaDataEntry("has_machine_quality"):
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
if definition.getMetaDataEntry("has_materials") and material_container:
search_criteria["material"] = material_container.id
else:
search_criteria["definition"] = "fdmprinter"
if self.quality != self._empty_quality:
search_criteria["name"] = self.quality.name
else:
preferred_quality = definition.getMetaDataEntry("preferred_quality")
if preferred_quality:
search_criteria["id"] = preferred_quality
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
if "material" in search_criteria:
# First check if we can solve our material not found problem by checking if we can find quality containers
# that are assigned to the parents of this material profile.
try:
inherited_files = material_container.getInheritedFiles()
except AttributeError: # Material_container does not support inheritance.
inherited_files = []
if inherited_files:
for inherited_file in inherited_files:
# Extract the ID from the path we used to load the file.
search_criteria["material"] = os.path.basename(inherited_file).split(".")[0]
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
# We still weren't able to find a quality for this specific material.
# Try to find qualities for a generic version of the material.
material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
if definition.getMetaDataEntry("has_machine_quality"):
if self.material != self._empty_instance_container:
material_search_criteria["definition"] = material_container.getMetaDataEntry("definition")
if definition.getMetaDataEntry("has_variants"):
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
else:
material_search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
if definition.getMetaDataEntry("has_variants") and self.variant != self._empty_instance_container:
material_search_criteria["variant"] = self.variant.id
else:
material_search_criteria["definition"] = "fdmprinter"
material_containers = registry.findInstanceContainersMetadata(**material_search_criteria)
# Try all materials to see if there is a quality profile available.
for material_container in material_containers:
search_criteria["material"] = material_container["id"]
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
if "name" in search_criteria or "id" in search_criteria:
# If a quality by this name can not be found, try a wider set of search criteria
search_criteria.pop("name", None)
search_criteria.pop("id", None)
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
return None
## protected: ## protected:
# Helper to make sure we emit a PyQt signal on container changes. # Helper to make sure we emit a PyQt signal on container changes.

View file

@ -1,15 +1,18 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger from typing import Optional
from UM.Logger import Logger
from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
from UM.Util import parseBool
from cura.Machines.VariantManager import VariantType
from .GlobalStack import GlobalStack from .GlobalStack import GlobalStack
from .ExtruderStack import ExtruderStack from .ExtruderStack import ExtruderStack
from typing import Optional
## Contains helper functions to create new machines. ## Contains helper functions to create new machines.
@ -22,7 +25,13 @@ class CuraStackBuilder:
# \return The new global stack or None if an error occurred. # \return The new global stack or None if an error occurred.
@classmethod @classmethod
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]: def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
variant_manager = application.getVariantManager()
material_manager = application.getMaterialManager()
quality_manager = application.getQualityManager()
registry = ContainerRegistry.getInstance() registry = ContainerRegistry.getInstance()
definitions = registry.findDefinitionContainers(id = definition_id) definitions = registry.findDefinitionContainers(id = definition_id)
if not definitions: if not definitions:
Logger.log("w", "Definition {definition} was not found!", definition = definition_id) Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
@ -30,7 +39,21 @@ class CuraStackBuilder:
machine_definition = definitions[0] machine_definition = definitions[0]
generated_name = registry.createUniqueName("machine", "", name, machine_definition.name) # get variant container for the global stack
global_variant_container = application.empty_variant_container
global_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.BUILD_PLATE)
if global_variant_node:
global_variant_container = global_variant_node.getContainer()
# get variant container for extruders
extruder_variant_container = application.empty_variant_container
extruder_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.NOZZLE)
extruder_variant_name = None
if extruder_variant_node:
extruder_variant_container = extruder_variant_node.getContainer()
extruder_variant_name = extruder_variant_container.getName()
generated_name = registry.createUniqueName("machine", "", name, machine_definition.getName())
# Make sure the new name does not collide with any definition or (quality) profile # Make sure the new name does not collide with any definition or (quality) profile
# createUniqueName() only looks at other stacks, but not at definitions or quality profiles # createUniqueName() only looks at other stacks, but not at definitions or quality profiles
# Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true # Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true
@ -40,49 +63,55 @@ class CuraStackBuilder:
new_global_stack = cls.createGlobalStack( new_global_stack = cls.createGlobalStack(
new_stack_id = generated_name, new_stack_id = generated_name,
definition = machine_definition, definition = machine_definition,
quality = "default", variant_container = global_variant_container,
material = "default", material_container = application.empty_material_container,
variant = "default", quality_container = application.empty_quality_container,
) )
new_global_stack.setName(generated_name) new_global_stack.setName(generated_name)
extruder_definition = registry.findDefinitionContainers(machine = machine_definition.getId()) # get material container for extruders
material_container = application.empty_material_container
material_node = material_manager.getDefaultMaterial(new_global_stack, extruder_variant_name)
if material_node:
material_container = material_node.getContainer()
if not extruder_definition: # Create ExtruderStacks
# create extruder stack for single extrusion machines that have no separate extruder definition files extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains")
extruder_definition = registry.findDefinitionContainers(id = "fdmextruder")[0]
new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id) for position, extruder_definition_id in extruder_dict.items():
# Sanity check: make sure that the positions in the extruder definitions are same as in the machine
# definition
extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
position_in_extruder_def = extruder_definition.getMetaDataEntry("position")
if position_in_extruder_def != position:
raise RuntimeError("Extruder position [%s] defined in extruder definition [%s] is not the same as in machine definition [%s] position [%s]" %
(position_in_extruder_def, extruder_definition_id, definition_id, position))
new_extruder_id = registry.uniqueName(extruder_definition_id)
new_extruder = cls.createExtruderStack( new_extruder = cls.createExtruderStack(
new_extruder_id, new_extruder_id,
definition = extruder_definition, extruder_definition = extruder_definition,
machine_definition_id = machine_definition.getId(), machine_definition_id = definition_id,
quality = "default", position = position,
material = "default", variant_container = extruder_variant_container,
variant = "default", material_container = material_container,
next_stack = new_global_stack quality_container = application.empty_quality_container,
global_stack = new_global_stack,
) )
new_extruder.setNextStack(new_global_stack)
new_global_stack.addExtruder(new_extruder) new_global_stack.addExtruder(new_extruder)
registry.addContainer(new_extruder) registry.addContainer(new_extruder)
preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type")
quality_group_dict = quality_manager.getQualityGroups(new_global_stack)
quality_group = quality_group_dict.get(preferred_quality_type)
new_global_stack.quality = quality_group.node_for_global.getContainer()
for position, extruder_stack in new_global_stack.extruders.items():
if position in quality_group.nodes_for_extruders:
extruder_stack.quality = quality_group.nodes_for_extruders[position].getContainer()
else: else:
# create extruder stack for each found extruder definition extruder_stack.quality = application.empty_quality_container
for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id):
position = extruder_definition.getMetaDataEntry("position", None)
if not position:
Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id)
new_extruder_id = registry.uniqueName(extruder_definition.id)
new_extruder = cls.createExtruderStack(
new_extruder_id,
definition = extruder_definition,
machine_definition_id = machine_definition.getId(),
quality = "default",
material = "default",
variant = "default",
next_stack = new_global_stack
)
new_global_stack.addExtruder(new_extruder)
registry.addContainer(new_extruder)
# Register the global stack after the extruder stacks are created. This prevents the registry from adding another # Register the global stack after the extruder stacks are created. This prevents the registry from adding another
# extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1). # extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1).
@ -100,43 +129,27 @@ class CuraStackBuilder:
# #
# \return A new Global stack instance with the specified parameters. # \return A new Global stack instance with the specified parameters.
@classmethod @classmethod
def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, machine_definition_id: str, **kwargs) -> ExtruderStack: def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
stack = ExtruderStack(new_stack_id) position: int,
stack.setName(definition.getName()) variant_container, material_container, quality_container, global_stack) -> ExtruderStack:
stack.setDefinition(definition)
stack.addMetaDataEntry("position", definition.getMetaDataEntry("position"))
if "next_stack" in kwargs:
# Add stacks before containers are added, since they may trigger a setting update.
stack.setNextStack(kwargs["next_stack"])
user_container = InstanceContainer(new_stack_id + "_user")
user_container.addMetaDataEntry("type", "user")
user_container.addMetaDataEntry("extruder", new_stack_id)
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) application = CuraApplication.getInstance()
user_container.setDefinition(machine_definition_id)
stack.setUserChanges(user_container) stack = ExtruderStack(new_stack_id, parent = global_stack)
stack.setName(extruder_definition.getName())
stack.setDefinition(extruder_definition)
# Important! The order here matters, because that allows the stack to stack.addMetaDataEntry("position", position)
# assume the material and variant have already been set.
if "definition_changes" in kwargs:
stack.setDefinitionChangesById(kwargs["definition_changes"])
else:
stack.setDefinitionChanges(cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings"))
if "variant" in kwargs: user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id,
stack.setVariantById(kwargs["variant"]) is_global_stack = False)
if "material" in kwargs: stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
stack.setMaterialById(kwargs["material"]) stack.variant = variant_container
stack.material = material_container
if "quality" in kwargs: stack.quality = quality_container
stack.setQualityById(kwargs["quality"]) stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container
if "quality_changes" in kwargs:
stack.setQualityChangesById(kwargs["quality_changes"])
# Only add the created containers to the registry after we have set all the other # Only add the created containers to the registry after we have set all the other
# properties. This makes the create operation more transactional, since any problems # properties. This makes the create operation more transactional, since any problems
@ -153,44 +166,48 @@ class CuraStackBuilder:
# #
# \return A new Global stack instance with the specified parameters. # \return A new Global stack instance with the specified parameters.
@classmethod @classmethod
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, **kwargs) -> GlobalStack: def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface,
variant_container, material_container, quality_container) -> GlobalStack:
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
stack = GlobalStack(new_stack_id) stack = GlobalStack(new_stack_id)
stack.setDefinition(definition) stack.setDefinition(definition)
user_container = InstanceContainer(new_stack_id + "_user") # Create user container
user_container.addMetaDataEntry("type", "user") user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id,
user_container.addMetaDataEntry("machine", new_stack_id) is_global_stack = True)
from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(definition.getId())
stack.setUserChanges(user_container) stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
stack.variant = variant_container
# Important! The order here matters, because that allows the stack to stack.material = material_container
# assume the material and variant have already been set. stack.quality = quality_container
if "definition_changes" in kwargs: stack.qualityChanges = application.empty_quality_changes_container
stack.setDefinitionChangesById(kwargs["definition_changes"]) stack.userChanges = user_container
else:
stack.setDefinitionChanges(cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings"))
if "variant" in kwargs:
stack.setVariantById(kwargs["variant"])
if "material" in kwargs:
stack.setMaterialById(kwargs["material"])
if "quality" in kwargs:
stack.setQualityById(kwargs["quality"])
if "quality_changes" in kwargs:
stack.setQualityChangesById(kwargs["quality_changes"])
ContainerRegistry.getInstance().addContainer(user_container) ContainerRegistry.getInstance().addContainer(user_container)
return stack return stack
@classmethod @classmethod
def createDefinitionChangesContainer(cls, container_stack, container_name, container_index = None): def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
is_global_stack: bool) -> "InstanceContainer":
from cura.CuraApplication import CuraApplication
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
container = InstanceContainer(unique_container_name)
container.setDefinition(definition_id)
container.addMetaDataEntry("type", "user")
container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
metadata_key_to_add = "machine" if is_global_stack else "extruder"
container.addMetaDataEntry(metadata_key_to_add, stack_id)
return container
@classmethod
def createDefinitionChangesContainer(cls, container_stack, container_name):
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name) unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)

View file

@ -12,6 +12,7 @@ from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID. from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.SettingInstance import SettingInstance
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from typing import Optional, List, TYPE_CHECKING, Union from typing import Optional, List, TYPE_CHECKING, Union
@ -30,28 +31,22 @@ class ExtruderManager(QObject):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._application = Application.getInstance()
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders. self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
self._selected_object_extruders = [] self._selected_object_extruders = []
self._global_container_stack_definition_id = None
self._addCurrentMachineExtruders() self._addCurrentMachineExtruders()
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged) #Application.getInstance().globalContainerStackChanged.connect(self._globalContainerStackChanged)
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders) Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
## Signal to notify other components when the list of extruders for a machine definition changes. ## Signal to notify other components when the list of extruders for a machine definition changes.
extrudersChanged = pyqtSignal(QVariant) extrudersChanged = pyqtSignal(QVariant)
## Signal to notify other components when the global container stack is switched to a definition
# that has different extruders than the previous global container stack
globalContainerStackDefinitionChanged = pyqtSignal()
## Notify when the user switches the currently active extruder. ## Notify when the user switches the currently active extruder.
activeExtruderChanged = pyqtSignal() activeExtruderChanged = pyqtSignal()
## The signal notifies subscribers if extruders are added
extrudersAdded = pyqtSignal()
## Gets the unique identifier of the currently active extruder stack. ## Gets the unique identifier of the currently active extruder stack.
# #
# The currently active extruder stack is the stack that is currently being # The currently active extruder stack is the stack that is currently being
@ -184,6 +179,7 @@ class ExtruderManager(QObject):
self._selected_object_extruders = [] self._selected_object_extruders = []
self.selectedObjectExtrudersChanged.emit() self.selectedObjectExtrudersChanged.emit()
@pyqtSlot(result = QObject)
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]: def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
@ -371,12 +367,7 @@ class ExtruderManager(QObject):
return result[:machine_extruder_count] return result[:machine_extruder_count]
def __globalContainerStackChanged(self) -> None: def _globalContainerStackChanged(self) -> None:
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id:
self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
self.globalContainerStackDefinitionChanged.emit()
# If the global container changed, the machine changed and might have extruders that were not registered yet # If the global container changed, the machine changed and might have extruders that were not registered yet
self._addCurrentMachineExtruders() self._addCurrentMachineExtruders()
@ -384,7 +375,7 @@ class ExtruderManager(QObject):
## Adds the extruders of the currently active machine. ## Adds the extruders of the currently active machine.
def _addCurrentMachineExtruders(self) -> None: def _addCurrentMachineExtruders(self) -> None:
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = self._application.getGlobalContainerStack()
extruders_changed = False extruders_changed = False
if global_stack: if global_stack:
@ -404,19 +395,81 @@ class ExtruderManager(QObject):
self._extruder_trains[global_stack_id][extruder_train.getMetaDataEntry("position")] = extruder_train self._extruder_trains[global_stack_id][extruder_train.getMetaDataEntry("position")] = extruder_train
# regardless of what the next stack is, we have to set it again, because of signal routing. ??? # regardless of what the next stack is, we have to set it again, because of signal routing. ???
extruder_train.setParent(global_stack)
extruder_train.setNextStack(global_stack) extruder_train.setNextStack(global_stack)
extruders_changed = True extruders_changed = True
# FIX: We have to remove those settings here because we know that those values have been copied to all self._fixMaterialDiameterAndNozzleSize(global_stack, extruder_trains)
if extruders_changed:
self.extrudersChanged.emit(global_stack_id)
self.setActiveExtruderIndex(0)
#
# This function tries to fix the problem with per-extruder-settable nozzle size and material diameter problems
# in early versions (3.0 - 3.2.1).
#
# In earlier versions, "nozzle size" and "material diameter" are only applicable to the complete machine, so all
# extruders share the same values. In this case, "nozzle size" and "material diameter" are saved in the
# GlobalStack's DefinitionChanges container.
#
# Later, we could have different "nozzle size" for each extruder, but "material diameter" could only be set for
# the entire machine. In this case, "nozzle size" should be saved in each ExtruderStack's DefinitionChanges, but
# "material diameter" still remains in the GlobalStack's DefinitionChanges.
#
# Lateer, both "nozzle size" and "material diameter" are settable per-extruder, and both settings should be saved
# in the ExtruderStack's DefinitionChanges.
#
# There were some bugs in upgrade so the data weren't saved correct as described above. This function tries fix
# this.
#
# One more thing is about material diameter and single-extrusion machines. Most single-extrusion machines don't
# specifically define their extruder definition, so they reuse "fdmextruder", but for those machines, they may
# define "material diameter = 1.75" in their machine definition, but in "fdmextruder", it's still "2.85". This
# causes a problem with incorrect default values.
#
# This is also fixed here in this way: If no "material diameter" is specified, it will look for the default value
# in both the Extruder's definition and the Global's definition. If 2 values don't match, we will use the value
# from the Global definition by setting it in the Extruder's DefinitionChanges container.
#
def _fixMaterialDiameterAndNozzleSize(self, global_stack, extruder_stack_list):
keys_to_copy = ["material_diameter", "machine_nozzle_size"] # these will be copied over to all extruders
extruder_positions_to_update = set()
for extruder_stack in extruder_stack_list:
for key in keys_to_copy:
# Only copy the value when this extruder doesn't have the value.
if extruder_stack.definitionChanges.hasProperty(key, "value"):
continue
setting_value_in_global_def_changes = global_stack.definitionChanges.getProperty(key, "value")
setting_value_in_global_def = global_stack.definition.getProperty(key, "value")
setting_value = setting_value_in_global_def
if setting_value_in_global_def_changes is not None:
setting_value = setting_value_in_global_def_changes
if setting_value == extruder_stack.definition.getProperty(key, "value"):
continue
setting_definition = global_stack.getSettingDefinition(key)
new_instance = SettingInstance(setting_definition, extruder_stack.definitionChanges)
new_instance.setProperty("value", setting_value)
new_instance.resetState() # Ensure that the state is not seen as a user state.
extruder_stack.definitionChanges.addInstance(new_instance)
extruder_stack.definitionChanges.setDirty(True)
# Make sure the material diameter is up to date for the extruder stack.
if key == "material_diameter":
position = int(extruder_stack.getMetaDataEntry("position"))
extruder_positions_to_update.add(position)
# We have to remove those settings here because we know that those values have been copied to all
# the extruders at this point. # the extruders at this point.
for key in ("material_diameter", "machine_nozzle_size"): for key in keys_to_copy:
if global_stack.definitionChanges.hasProperty(key, "value"): if global_stack.definitionChanges.hasProperty(key, "value"):
global_stack.definitionChanges.removeInstance(key, postpone_emit = True) global_stack.definitionChanges.removeInstance(key, postpone_emit = True)
if extruders_changed: # Update material diameter for extruders
self.extrudersChanged.emit(global_stack_id) for position in extruder_positions_to_update:
self.extrudersAdded.emit() self.updateMaterialForDiameter(position, global_stack = global_stack)
self.setActiveExtruderIndex(0)
## Get all extruder values for a certain setting. ## Get all extruder values for a certain setting.
# #
@ -503,7 +556,8 @@ class ExtruderManager(QObject):
return ExtruderManager.getExtruderValues(key) return ExtruderManager.getExtruderValues(key)
## Updates the material container to a material that matches the material diameter set for the printer ## Updates the material container to a material that matches the material diameter set for the printer
def updateMaterialForDiameter(self, extruder_position: int): def updateMaterialForDiameter(self, extruder_position: int, global_stack = None):
if not global_stack:
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack: if not global_stack:
return return

View file

@ -3,16 +3,16 @@
from typing import Any, TYPE_CHECKING, Optional from typing import Any, TYPE_CHECKING, Optional
from UM.Application import Application from PyQt5.QtCore import pyqtProperty
from UM.Decorators import override from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
from UM.Settings.SettingInstance import SettingInstance
from . import Exceptions from . import Exceptions
from .CuraContainerStack import CuraContainerStack from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
from .ExtruderManager import ExtruderManager from .ExtruderManager import ExtruderManager
if TYPE_CHECKING: if TYPE_CHECKING:
@ -34,7 +34,7 @@ class ExtruderStack(CuraContainerStack):
# #
# This will set the next stack and ensure that we register this stack as an extruder. # This will set the next stack and ensure that we register this stack as an extruder.
@override(ContainerStack) @override(ContainerStack)
def setNextStack(self, stack: ContainerStack) -> None: def setNextStack(self, stack: CuraContainerStack) -> None:
super().setNextStack(stack) super().setNextStack(stack)
stack.addExtruder(self) stack.addExtruder(self)
self.addMetaDataEntry("machine", stack.id) self.addMetaDataEntry("machine", stack.id)
@ -42,75 +42,6 @@ class ExtruderStack(CuraContainerStack):
# For backward compatibility: Register the extruder with the Extruder Manager # For backward compatibility: Register the extruder with the Extruder Manager
ExtruderManager.getInstance().registerExtruder(self, stack.id) ExtruderManager.getInstance().registerExtruder(self, stack.id)
# Now each machine will have at least one extruder stack. If this is the first extruder, the extruder-specific
# settings such as nozzle size and material diameter should be moved from the machine's definition_changes to
# the this extruder's definition_changes.
#
# We do this here because it is tooooo expansive to do it in the version upgrade: During the version upgrade,
# when we are upgrading a definition_changes container file, there is NO guarantee that other files such as
# machine an extruder stack files are upgraded before this, so we cannot read those files assuming they are in
# the latest format.
#
# MORE:
# For single-extrusion machines, nozzle size is saved in the global stack, so the nozzle size value should be
# carried to the first extruder.
# For material diameter, it was supposed to be applied to all extruders, so its value should be copied to all
# extruders.
keys_to_copy = ["material_diameter", "machine_nozzle_size"] # these will be copied over to all extruders
for key in keys_to_copy:
# Only copy the value when this extruder doesn't have the value.
if self.definitionChanges.hasProperty(key, "value"):
continue
# WARNING: this might be very dangerous and should be refactored ASAP!
#
# We cannot add a setting definition of "material_diameter" into the extruder's definition at runtime
# because all other machines which uses "fdmextruder" as the extruder definition will be affected.
#
# The problem is that single extrusion machines have their default material diameter defined in the global
# definitions. Now we automatically create an extruder stack for those machines using "fdmextruder"
# definition, which doesn't have the specific "material_diameter" and "machine_nozzle_size" defined for
# each machine. This results in wrong values which can be found in the MachineSettings dialog.
#
# To solve this, we put "material_diameter" back into the "fdmextruder" definition because modifying it in
# the extruder definition will affect all machines which uses the "fdmextruder" definition. Moreover, now
# we also check the value defined in the machine definition. If present, the value defined in the global
# stack's definition changes container will be copied. Otherwise, we will check if the default values in the
# machine definition and the extruder definition are the same, and if not, the default value in the machine
# definition will be copied to the extruder stack's definition changes.
#
setting_value_in_global_def_changes = stack.definitionChanges.getProperty(key, "value")
setting_value_in_global_def = stack.definition.getProperty(key, "value")
setting_value = setting_value_in_global_def
if setting_value_in_global_def_changes is not None:
setting_value = setting_value_in_global_def_changes
if setting_value == self.definition.getProperty(key, "value"):
continue
setting_definition = stack.getSettingDefinition(key)
new_instance = SettingInstance(setting_definition, self.definitionChanges)
new_instance.setProperty("value", setting_value)
new_instance.resetState() # Ensure that the state is not seen as a user state.
self.definitionChanges.addInstance(new_instance)
self.definitionChanges.setDirty(True)
# Make sure the material diameter is up to date for the extruder stack.
if key == "material_diameter":
from cura.CuraApplication import CuraApplication
machine_manager = CuraApplication.getInstance().getMachineManager()
position = self.getMetaDataEntry("position", "0")
func = lambda p = position: CuraApplication.getInstance().getExtruderManager().updateMaterialForDiameter(p)
machine_manager.machine_extruder_material_update_dict[stack.getId()].append(func)
# NOTE: We cannot remove the setting from the global stack's definition changes container because for
# material diameter, it needs to be applied to all extruders, but here we don't know how many extruders
# a machine actually has and how many extruders has already been loaded for that machine, so we have to
# keep this setting for any remaining extruders that haven't been loaded yet.
#
# Those settings will be removed in ExtruderManager which knows all those info.
@override(ContainerStack) @override(ContainerStack)
def getNextStack(self) -> Optional["GlobalStack"]: def getNextStack(self) -> Optional["GlobalStack"]:
return super().getNextStack() return super().getNextStack()
@ -119,6 +50,29 @@ class ExtruderStack(CuraContainerStack):
def getLoadingPriority(cls) -> int: def getLoadingPriority(cls) -> int:
return 3 return 3
## Return the filament diameter that the machine requires.
#
# If the machine has no requirement for the diameter, -1 is returned.
# \return The filament diameter for the printer
@property
def materialDiameter(self) -> float:
context = PropertyEvaluationContext(self)
context.context["evaluate_from_container_index"] = _ContainerIndexes.Variant
return self.getProperty("material_diameter", "value", context = context)
## Return the approximate filament diameter that the machine requires.
#
# The approximate material diameter is the material diameter rounded to
# the nearest millimetre.
#
# If the machine has no requirement for the diameter, -1 is returned.
#
# \return The approximate filament diameter for the printer
@pyqtProperty(float)
def approximateMaterialDiameter(self) -> float:
return round(float(self.materialDiameter))
## Overridden from ContainerStack ## Overridden from ContainerStack
# #
# It will perform a few extra checks when trying to get properties. # It will perform a few extra checks when trying to get properties.
@ -187,11 +141,6 @@ class ExtruderStack(CuraContainerStack):
if has_global_dependencies: if has_global_dependencies:
self.getNextStack().propertiesChanged.emit(key, properties) self.getNextStack().propertiesChanged.emit(key, properties)
def findDefaultVariant(self):
# The default variant is defined in the machine stack and/or definition, so use the machine stack to find
# the default variant.
return self.getNextStack().findDefaultVariant()
extruder_stack_mime = MimeType( extruder_stack_mime = MimeType(
name = "application/x-cura-extruderstack", name = "application/x-cura-extruderstack",

View file

@ -125,21 +125,6 @@ class GlobalStack(CuraContainerStack):
def setNextStack(self, next_stack: ContainerStack) -> None: def setNextStack(self, next_stack: ContainerStack) -> None:
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!") raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
## Gets the approximate filament diameter that the machine requires.
#
# The approximate material diameter is the material diameter rounded to
# the nearest millimetre.
#
# If the machine has no requirement for the diameter, -1 is returned.
#
# \return The approximate filament diameter for the printer, as a string.
@pyqtProperty(str)
def approximateMaterialDiameter(self) -> str:
material_diameter = self.definition.getProperty("material_diameter", "value")
if material_diameter is None:
return "-1"
return str(round(float(material_diameter))) #Round, then convert back to string.
# protected: # protected:
# Determine whether or not we should try to get the "resolve" property instead of the # Determine whether or not we should try to get the "resolve" property instead of the

File diff suppressed because it is too large Load diff

View file

@ -1,57 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSlot #To expose data to QML.
from cura.Settings.ContainerManager import ContainerManager
from UM.Logger import Logger
from UM.Message import Message #To create a warning message about material diameter.
from UM.i18n import i18nCatalog #Translated strings.
catalog = i18nCatalog("cura")
## Handles material-related data, processing requests to change them and
# providing data for the GUI.
#
# TODO: Move material-related managing over from the machine manager to here.
class MaterialManager(QObject):
## Creates the global values for the material manager to use.
def __init__(self, parent = None):
super().__init__(parent)
#Material diameter changed warning message.
self._material_diameter_warning_message = Message(catalog.i18nc("@info:status Has a cancel button next to it.",
"The selected material diameter causes the material to become incompatible with the current printer."), title = catalog.i18nc("@info:title", "Incompatible Material"))
self._material_diameter_warning_message.addAction("Undo", catalog.i18nc("@action:button", "Undo"), None, catalog.i18nc("@action", "Undo changing the material diameter."))
self._material_diameter_warning_message.actionTriggered.connect(self._materialWarningMessageAction)
## Creates an instance of the MaterialManager.
#
# This should only be called by PyQt to create the singleton instance of
# this class.
@staticmethod
def createMaterialManager(engine = None, script_engine = None):
return MaterialManager()
@pyqtSlot(str, str)
def showMaterialWarningMessage(self, material_id, previous_diameter):
self._material_diameter_warning_message.previous_diameter = previous_diameter #Make sure that the undo button can properly undo the action.
self._material_diameter_warning_message.material_id = material_id
self._material_diameter_warning_message.show()
## Called when clicking "undo" on the warning dialogue for disappeared
# materials.
#
# This executes the undo action, restoring the material diameter.
#
# \param button The identifier of the button that was pressed.
def _materialWarningMessageAction(self, message, button):
if button == "Undo":
container_manager = ContainerManager.getInstance()
container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "properties/diameter", self._material_diameter_warning_message.previous_diameter)
approximate_previous_diameter = str(round(float(self._material_diameter_warning_message.previous_diameter)))
container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "approximate_diameter", approximate_previous_diameter)
container_manager.setContainerProperty(self._material_diameter_warning_message.material_id, "material_diameter", "value", self._material_diameter_warning_message.previous_diameter);
message.hide()
else:
Logger.log("w", "Unknown button action for material diameter warning message: {action}".format(action = button))

View file

@ -3,6 +3,7 @@
import UM.Settings.Models.SettingVisibilityHandler import UM.Settings.Models.SettingVisibilityHandler
class MaterialSettingsVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler): class MaterialSettingsVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler):
def __init__(self, parent = None, *args, **kwargs): def __init__(self, parent = None, *args, **kwargs):
super().__init__(parent = parent, *args, **kwargs) super().__init__(parent = parent, *args, **kwargs)

View file

@ -1,37 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Any, List
from UM.Settings.ContainerRegistry import ContainerRegistry #To listen for changes to the materials.
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel #We're extending this class.
## A model that shows a list of currently valid materials.
class MaterialsModel(InstanceContainersModel):
def __init__(self, parent = None):
super().__init__(parent)
ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerMetaDataChanged)
## Called when the metadata of the container was changed.
#
# This makes sure that we only update when it was a material that changed.
#
# \param container The container whose metadata was changed.
def _onContainerMetaDataChanged(self, container):
if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed.
self._container_change_timer.start()
def _onContainerChanged(self, container):
if container.getMetaDataEntry("type", "") == "material":
super()._onContainerChanged(container)
## Group brand together
def _sortKey(self, item) -> List[Any]:
result = []
result.append(item["metadata"]["brand"])
result.append(item["metadata"]["material"])
result.append(item["metadata"]["name"])
result.append(item["metadata"]["color_name"])
result.append(item["metadata"]["id"])
result.extend(super()._sortKey(item))
return result

View file

@ -1,222 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from collections import OrderedDict
from PyQt5.QtCore import Qt
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel
from cura.QualityManager import QualityManager
from cura.Settings.ExtruderManager import ExtruderManager
from typing import List, TYPE_CHECKING
if TYPE_CHECKING:
from cura.Settings.ExtruderStack import ExtruderStack
## QML Model for listing the current list of valid quality profiles.
#
class ProfilesModel(InstanceContainersModel):
LayerHeightRole = Qt.UserRole + 1001
LayerHeightWithoutUnitRole = Qt.UserRole + 1002
AvailableRole = Qt.UserRole + 1003
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.LayerHeightRole, "layer_height")
self.addRoleName(self.LayerHeightWithoutUnitRole, "layer_height_without_unit")
self.addRoleName(self.AvailableRole, "available")
Application.getInstance().globalContainerStackChanged.connect(self._update)
Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
self._empty_quality = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
# Factory function, used by QML
@staticmethod
def createProfilesModel(engine, js_engine):
return ProfilesModel.getInstance()
## Get the singleton instance for this class.
@classmethod
def getInstance(cls) -> "ProfilesModel":
# Note: Explicit use of class name to prevent issues with inheritance.
if not ProfilesModel.__instance:
ProfilesModel.__instance = cls()
return ProfilesModel.__instance
@classmethod
def hasInstance(cls) -> bool:
return ProfilesModel.__instance is not None
__instance = None # type: "ProfilesModel"
## Fetch the list of containers to display.
#
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
def _fetchInstanceContainers(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None:
return {}, {}
global_stack_definition = global_container_stack.definition
# Get the list of extruders and place the selected extruder at the front of the list.
extruder_stacks = self._getOrderedExtruderStacksList()
materials = [extruder.material for extruder in extruder_stacks]
# Fetch the list of usable qualities across all extruders.
# The actual list of quality profiles come from the first extruder in the extruder list.
result = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
# The usable quality types are set
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in result])
# Fetch all qualities available for this machine and the materials selected in extruders
all_qualities = QualityManager.getInstance().findAllQualitiesForMachineAndMaterials(global_stack_definition, materials)
# If in the all qualities there is some of them that are not available due to incompatibility with materials
# we also add it so that they will appear in the slide quality bar. However in recomputeItems will be marked as
# not available so they will be shown in gray
for quality in all_qualities:
if quality.getMetaDataEntry("quality_type") not in quality_type_set:
result.append(quality)
if len(result) > 1 and self._empty_quality in result:
result.remove(self._empty_quality)
return {item.getId(): item for item in result}, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
## Re-computes the items in this model, and adds the layer height role.
def _recomputeItems(self):
# Some globals that we can re-use.
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None:
return
extruder_stacks = self._getOrderedExtruderStacksList()
container_registry = ContainerRegistry.getInstance()
# Get a list of usable/available qualities for this machine and material
qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
unit = global_container_stack.getBottom().getProperty("layer_height", "unit")
if not unit:
unit = ""
# group all quality items according to quality_types, so we know which profile suits the currently
# active machine and material, and later yield the right ones.
tmp_all_quality_items = OrderedDict()
for item in super()._recomputeItems():
profiles = container_registry.findContainersMetadata(id = item["id"])
if not profiles or "quality_type" not in profiles[0]:
quality_type = ""
else:
quality_type = profiles[0]["quality_type"]
if quality_type not in tmp_all_quality_items:
tmp_all_quality_items[quality_type] = {"suitable_container": None, "all_containers": []}
tmp_all_quality_items[quality_type]["all_containers"].append(item)
if tmp_all_quality_items[quality_type]["suitable_container"] is None:
tmp_all_quality_items[quality_type]["suitable_container"] = item
# reverse the ordering (finest first, coarsest last)
all_quality_items = OrderedDict()
for key in reversed(tmp_all_quality_items.keys()):
all_quality_items[key] = tmp_all_quality_items[key]
# First the suitable containers are set in the model
containers = []
for data_item in all_quality_items.values():
suitable_item = data_item["suitable_container"]
if suitable_item is not None:
containers.append(suitable_item)
# Once the suitable containers are collected, the rest of the containers are appended
for data_item in all_quality_items.values():
for item in data_item["all_containers"]:
if item not in containers:
containers.append(item)
# Now all the containers are set
for item in containers:
profile = container_registry.findContainers(id = item["id"])
# When for some reason there is no profile container in the registry
if not profile:
self._setItemLayerHeight(item, "", "")
item["available"] = False
yield item
continue
profile = profile[0]
# When there is a profile but it's an empty quality should. It's shown in the list (they are "Not Supported" profiles)
if profile.getId() == "empty_quality":
self._setItemLayerHeight(item, "", "")
item["available"] = True
yield item
continue
item["available"] = profile in qualities
# Easy case: This profile defines its own layer height.
if profile.hasProperty("layer_height", "value"):
self._setItemLayerHeight(item, profile.getProperty("layer_height", "value"), unit)
yield item
continue
machine_manager = Application.getInstance().getMachineManager()
# Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile.
quality_type = profile.getMetaDataEntry("quality_type", None)
if quality_type:
quality_results = machine_manager.determineQualityAndQualityChangesForQualityType(quality_type)
for quality_result in quality_results:
if quality_result["stack"] is global_container_stack:
quality = quality_result["quality"]
break
else:
# No global container stack in the results:
if quality_results:
# Take any of the extruders.
quality = quality_results[0]["quality"]
else:
quality = None
if quality and quality.hasProperty("layer_height", "value"):
self._setItemLayerHeight(item, quality.getProperty("layer_height", "value"), unit)
yield item
continue
# Quality has no value for layer height either. Get the layer height from somewhere lower in the stack.
skip_until_container = global_container_stack.material
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No material in stack.
skip_until_container = global_container_stack.variant
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No variant in stack.
skip_until_container = global_container_stack.getBottom()
self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material.
yield item
## Get a list of extruder stacks with the active extruder at the front of the list.
@staticmethod
def _getOrderedExtruderStacksList() -> List["ExtruderStack"]:
extruder_manager = ExtruderManager.getInstance()
extruder_stacks = extruder_manager.getActiveExtruderStacks()
active_extruder = extruder_manager.getActiveExtruderStack()
if active_extruder in extruder_stacks:
extruder_stacks.remove(active_extruder)
extruder_stacks = [active_extruder] + extruder_stacks
return extruder_stacks
@staticmethod
def _setItemLayerHeight(item, value, unit):
item["layer_height"] = str(value) + unit
item["layer_height_without_unit"] = str(value)

View file

@ -1,54 +0,0 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.QualityManager import QualityManager
from cura.Settings.ProfilesModel import ProfilesModel
from cura.Settings.ExtruderManager import ExtruderManager
## QML Model for listing the current list of valid quality and quality changes profiles.
#
class QualityAndUserProfilesModel(ProfilesModel):
def __init__(self, parent = None):
super().__init__(parent)
self._empty_quality = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0]
## Fetch the list of containers to display.
#
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
def _fetchInstanceContainers(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack:
return {}, {}
# Fetch the list of quality changes.
quality_manager = QualityManager.getInstance()
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
extruder_manager = ExtruderManager.getInstance()
active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = self._getOrderedExtruderStacksList()
# Fetch the list of usable qualities across all extruders.
# The actual list of quality profiles come from the first extruder in the extruder list.
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
# Filter the quality_change by the list of available quality_types
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
# Also show custom profiles based on "Not Supported" quality profile
quality_type_set.add(self._empty_quality.getMetaDataEntry("quality_type"))
filtered_quality_changes = {qc.getId(): qc for qc in quality_changes_list if
qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is not None and
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
result = filtered_quality_changes
for q in quality_list:
if q.getId() != "empty_quality":
result[q.getId()] = q
return result, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.

View file

@ -1,249 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
from UM.Logger import Logger
import UM.Qt
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
import os
from UM.i18n import i18nCatalog
class QualitySettingsModel(UM.Qt.ListModel.ListModel):
KeyRole = Qt.UserRole + 1
LabelRole = Qt.UserRole + 2
UnitRole = Qt.UserRole + 3
ProfileValueRole = Qt.UserRole + 4
ProfileValueSourceRole = Qt.UserRole + 5
UserValueRole = Qt.UserRole + 6
CategoryRole = Qt.UserRole + 7
def __init__(self, parent = None):
super().__init__(parent = parent)
self._container_registry = ContainerRegistry.getInstance()
self._extruder_id = None
self._extruder_definition_id = None
self._quality_id = None
self._material_id = None
self._i18n_catalog = None
self.addRoleName(self.KeyRole, "key")
self.addRoleName(self.LabelRole, "label")
self.addRoleName(self.UnitRole, "unit")
self.addRoleName(self.ProfileValueRole, "profile_value")
self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
self.addRoleName(self.UserValueRole, "user_value")
self.addRoleName(self.CategoryRole, "category")
self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
def setExtruderId(self, extruder_id):
if extruder_id != self._extruder_id:
self._extruder_id = extruder_id
self._update()
self.extruderIdChanged.emit()
extruderIdChanged = pyqtSignal()
@pyqtProperty(str, fset = setExtruderId, notify = extruderIdChanged)
def extruderId(self):
return self._extruder_id
def setExtruderDefinition(self, extruder_definition):
if extruder_definition != self._extruder_definition_id:
self._extruder_definition_id = extruder_definition
self._update()
self.extruderDefinitionChanged.emit()
extruderDefinitionChanged = pyqtSignal()
@pyqtProperty(str, fset = setExtruderDefinition, notify = extruderDefinitionChanged)
def extruderDefinition(self):
return self._extruder_definition_id
def setQuality(self, quality):
if quality != self._quality_id:
self._quality_id = quality
self._update()
self.qualityChanged.emit()
qualityChanged = pyqtSignal()
@pyqtProperty(str, fset = setQuality, notify = qualityChanged)
def quality(self):
return self._quality_id
def setMaterial(self, material):
if material != self._material_id:
self._material_id = material
self._update()
self.materialChanged.emit()
materialChanged = pyqtSignal()
@pyqtProperty(str, fset = setMaterial, notify = materialChanged)
def material(self):
return self._material_id
def _update(self):
if not self._quality_id:
return
items = []
definition_container = Application.getInstance().getGlobalContainerStack().getBottom()
containers = self._container_registry.findInstanceContainers(id = self._quality_id)
if not containers:
Logger.log("w", "Could not find a quality container with id %s", self._quality_id)
return
quality_container = None
quality_changes_container = None
if containers[0].getMetaDataEntry("type") == "quality":
quality_container = containers[0]
else:
quality_changes_container = containers[0]
if quality_changes_container.getMetaDataEntry("quality_type") == self._empty_quality.getMetaDataEntry("quality_type"):
quality_container = self._empty_quality
else:
criteria = {
"type": "quality",
"quality_type": quality_changes_container.getMetaDataEntry("quality_type"),
"definition": quality_changes_container.getDefinition().getId()
}
quality_container = self._container_registry.findInstanceContainers(**criteria)
if not quality_container:
Logger.log("w", "Could not find a quality container matching quality changes %s", quality_changes_container.getId())
return
quality_container = quality_container[0]
quality_type = quality_container.getMetaDataEntry("quality_type")
if quality_type == "not_supported":
containers = []
else:
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition())
definition = quality_container.getDefinition()
# Check if the definition container has a translation file.
definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix
catalog = i18nCatalog(os.path.basename(definition_id + "." + definition_suffix))
if catalog.hasTranslationLoaded():
self._i18n_catalog = catalog
for file_name in quality_container.getDefinition().getInheritedFiles():
catalog = i18nCatalog(os.path.basename(file_name))
if catalog.hasTranslationLoaded():
self._i18n_catalog = catalog
criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}
if self._material_id and self._material_id != "empty_material":
criteria["material"] = self._material_id
criteria["extruder"] = self._extruder_id
containers = self._container_registry.findInstanceContainers(**criteria)
if not containers:
# Try again, this time without extruder
new_criteria = criteria.copy()
new_criteria.pop("extruder")
containers = self._container_registry.findInstanceContainers(**new_criteria)
if not containers and "material" in criteria:
# Try again, this time without material
criteria.pop("material", None)
containers = self._container_registry.findInstanceContainers(**criteria)
if not containers:
# Try again, this time without material or extruder
criteria.pop("extruder") # "material" has already been popped
containers = self._container_registry.findInstanceContainers(**criteria)
if not containers:
Logger.log("w", "Could not find any quality containers matching the search criteria %s" % str(criteria))
return
if quality_changes_container:
if quality_type == "not_supported":
criteria = {"type": "quality_changes", "quality_type": quality_type, "name": quality_changes_container.getName()}
else:
criteria = {"type": "quality_changes", "quality_type": quality_type, "definition": definition_id, "name": quality_changes_container.getName()}
if self._extruder_definition_id != "":
extruder_definitions = self._container_registry.findDefinitionContainers(id = self._extruder_definition_id)
if extruder_definitions:
criteria["extruder"] = Application.getInstance().getMachineManager().getQualityDefinitionId(extruder_definitions[0])
criteria["name"] = quality_changes_container.getName()
else:
criteria["extruder"] = None
changes = self._container_registry.findInstanceContainers(**criteria)
if changes:
containers.extend(changes)
global_container_stack = Application.getInstance().getGlobalContainerStack()
current_category = ""
for definition in definition_container.findDefinitions():
if definition.type == "category":
current_category = definition.label
if self._i18n_catalog:
current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
continue
profile_value = None
profile_value_source = ""
for container in containers:
new_value = container.getProperty(definition.key, "value")
if new_value is not None:
profile_value_source = container.getMetaDataEntry("type")
profile_value = new_value
# Global tab should use resolve (if there is one)
if not self._extruder_id:
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
if resolve_value is not None and profile_value is not None and profile_value_source != "quality_changes":
profile_value = resolve_value
user_value = None
if not self._extruder_id:
user_value = global_container_stack.getTop().getProperty(definition.key, "value")
else:
extruder_stack = self._container_registry.findContainerStacks(id = self._extruder_id)
if extruder_stack:
user_value = extruder_stack[0].getTop().getProperty(definition.key, "value")
if profile_value is None and user_value is None:
continue
settable_per_extruder = global_container_stack.getProperty(definition.key, "settable_per_extruder")
# If a setting is not settable per extruder (global) and we're looking at an extruder tab, don't show this value.
if self._extruder_id != "" and not settable_per_extruder:
continue
# If a setting is settable per extruder (not global) and we're looking at global tab, don't show this value.
if self._extruder_id == "" and settable_per_extruder:
continue
label = definition.label
if self._i18n_catalog:
label = self._i18n_catalog.i18nc(definition.key + " label", label)
items.append({
"key": definition.key,
"label": label,
"unit": definition.unit,
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
"profile_value_source": profile_value_source,
"user_value": "" if user_value is None else str(user_value),
"category": current_category
})
self.setItems(items)

View file

@ -1,85 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.QualityManager import QualityManager
from cura.Settings.ProfilesModel import ProfilesModel
from cura.Settings.ExtruderManager import ExtruderManager
## QML Model for listing the current list of valid quality changes profiles.
#
class UserProfilesModel(ProfilesModel):
def __init__(self, parent = None):
super().__init__(parent)
#Need to connect to the metaDataChanged signal of the active materials.
self.__current_extruders = []
self.__current_materials = []
Application.getInstance().getExtruderManager().extrudersChanged.connect(self.__onExtrudersChanged)
self.__onExtrudersChanged()
self.__current_materials = [extruder.material for extruder in self.__current_extruders]
for material in self.__current_materials:
material.metaDataChanged.connect(self._onContainerChanged)
self._empty_quality = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
## Fetch the list of containers to display.
#
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
def _fetchInstanceContainers(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack:
return {}, {}
# Fetch the list of quality changes.
quality_manager = QualityManager.getInstance()
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
extruder_manager = ExtruderManager.getInstance()
active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = self._getOrderedExtruderStacksList()
# Fetch the list of usable qualities across all extruders.
# The actual list of quality profiles come from the first extruder in the extruder list.
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
# Filter the quality_change by the list of available quality_types
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
quality_type_set.add(self._empty_quality.getMetaDataEntry("quality_type"))
filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if
qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is not None and
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
return filtered_quality_changes, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
## Called when a container changed on an extruder stack.
#
# If it's the material we need to connect to the metaDataChanged signal of
# that.
def __onContainerChanged(self, new_container):
#Careful not to update when a quality or quality changes profile changed!
#If you then update you're going to have an infinite recursion because the update may change the container.
if new_container.getMetaDataEntry("type") == "material":
for material in self.__current_materials:
material.metaDataChanged.disconnect(self._onContainerChanged)
self.__current_materials = [extruder.material for extruder in self.__current_extruders]
for material in self.__current_materials:
material.metaDataChanged.connect(self._onContainerChanged)
## Called when the current set of extruders change.
#
# This makes sure that we are listening to the signal for when the
# materials change.
def __onExtrudersChanged(self):
for extruder in self.__current_extruders:
extruder.containersChanged.disconnect(self.__onContainerChanged)
self.__current_extruders = Application.getInstance().getExtruderManager().getExtruderStacks()
for extruder in self.__current_extruders:
extruder.containersChanged.connect(self.__onContainerChanged)

View file

@ -4,26 +4,28 @@
import os.path import os.path
import zipfile import zipfile
import numpy
import Savitar
from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.Matrix import Matrix from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader from UM.Mesh.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator from UM.Scene.GroupDecorator import GroupDecorator
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from UM.Application import Application
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from cura.QualityManager import QualityManager
from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
MYPY = False MYPY = False
import Savitar
import numpy
try: try:
if not MYPY: if not MYPY:
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
@ -77,7 +79,7 @@ class ThreeMFReader(MeshReader):
self._object_count += 1 self._object_count += 1
node_name = "Object %s" % self._object_count node_name = "Object %s" % self._object_count
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
um_node = CuraSceneNode() um_node = CuraSceneNode()
um_node.addDecorator(BuildPlateDecorator(active_build_plate)) um_node.addDecorator(BuildPlateDecorator(active_build_plate))
@ -120,8 +122,8 @@ class ThreeMFReader(MeshReader):
um_node.callDecoration("setActiveExtruder", default_stack.getId()) um_node.callDecoration("setActiveExtruder", default_stack.getId())
# Get the definition & set it # Get the definition & set it
definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom()) definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack)
um_node.callDecoration("getStack").getTop().setDefinition(definition.getId()) um_node.callDecoration("getStack").getTop().setDefinition(definition_id)
setting_container = um_node.callDecoration("getStack").getTop() setting_container = um_node.callDecoration("getStack").getTop()

View file

@ -23,7 +23,6 @@ from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.CuraContainerStack import _ContainerIndexes from cura.Settings.CuraContainerStack import _ContainerIndexes
from cura.QualityManager import QualityManager
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from configparser import ConfigParser from configparser import ConfigParser
@ -855,10 +854,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if machine_extruder_count is not None: if machine_extruder_count is not None:
extruder_stacks_in_use = extruder_stacks[:machine_extruder_count] extruder_stacks_in_use = extruder_stacks[:machine_extruder_count]
available_quality = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_stack, quality_manager = CuraApplication.getInstance()._quality_manager
extruder_stacks_in_use) all_quality_groups = quality_manager.getQualityGroups(global_stack)
available_quality_types = [qt for qt, qg in all_quality_groups.items() if qg.is_available]
if not has_not_supported: if not has_not_supported:
has_not_supported = not available_quality has_not_supported = not available_quality_types
quality_has_been_changed = False quality_has_been_changed = False
@ -872,8 +872,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# The machine in the project has non-empty quality and there are usable qualities for this machine. # The machine in the project has non-empty quality and there are usable qualities for this machine.
# We need to check if the current quality_type is still usable for this machine, if not, then the quality # We need to check if the current quality_type is still usable for this machine, if not, then the quality
# will be reset to the "preferred quality" if present, otherwise "normal". # will be reset to the "preferred quality" if present, otherwise "normal".
available_quality_types = [q.getMetaDataEntry("quality_type") for q in available_quality]
if global_stack.quality.getMetaDataEntry("quality_type") not in available_quality_types: if global_stack.quality.getMetaDataEntry("quality_type") not in available_quality_types:
# We are here because the quality_type specified in the project is not supported any more, # We are here because the quality_type specified in the project is not supported any more,
# so we need to switch it to the "preferred quality" if present, otherwise "normal". # so we need to switch it to the "preferred quality" if present, otherwise "normal".
@ -1084,10 +1082,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit() CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit()
# Actually change the active machine. # Actually change the active machine.
Application.getInstance().setGlobalContainerStack(global_stack) #
# This is scheduled for later is because it depends on the Variant/Material/Qualitiy Managers to have the latest
# Notify everything/one that is to notify about changes. # data, but those managers will only update upon a container/container metadata changed signal. Because this
global_stack.containersChanged.emit(global_stack.getTop()) # function is running on the main thread (Qt thread), although those "changed" signals have been emitted, but
# they won't take effect until this function is done.
# To solve this, we schedule _updateActiveMachine() for later so it will have the latest data.
CuraApplication.getInstance().callLater(self._updateActiveMachine, global_stack)
# Load all the nodes / meshdata of the workspace # Load all the nodes / meshdata of the workspace
nodes = self._3mf_mesh_reader.read(file_name) nodes = self._3mf_mesh_reader.read(file_name)
@ -1100,6 +1101,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self.setWorkspaceName(base_file_name) self.setWorkspaceName(base_file_name)
return nodes return nodes
def _updateActiveMachine(self, global_stack):
# Actually change the active machine.
machine_manager = Application.getInstance().getMachineManager()
machine_manager.setActiveMachine(global_stack.getId())
# Notify everything/one that is to notify about changes.
global_stack.containersChanged.emit(global_stack.getTop())
## HACK: Replaces the material container in the given stack with a newly created material container. ## HACK: Replaces the material container in the given stack with a newly created material container.
# This function is used when the user chooses to resolve material conflicts by creating new ones. # This function is used when the user chooses to resolve material conflicts by creating new ones.
def _replaceStackMaterialWithNew(self, stack, old_new_material_dict): def _replaceStackMaterialWithNew(self, stack, old_new_material_dict):

View file

@ -68,7 +68,7 @@ class ThreeMFWriter(MeshWriter):
if not isinstance(um_node, SceneNode): if not isinstance(um_node, SceneNode):
return None return None
active_build_plate_nr = CuraApplication.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate_nr = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
if um_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr: if um_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr:
return return

View file

@ -70,7 +70,7 @@ class CuraEngineBackend(QObject, Backend):
# Workaround to disable layer view processing if layer view is not active. # Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False self._layer_view_active = False
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
Application.getInstance().getBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged) Application.getInstance().getMultiBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged() self._onActiveViewChanged()
self._stored_layer_data = [] self._stored_layer_data = []
self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
@ -88,7 +88,6 @@ class CuraEngineBackend(QObject, Backend):
# #
self._global_container_stack = None self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
Application.getInstance().getExtruderManager().extrudersAdded.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged() self._onGlobalStackChanged()
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished) Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)
@ -207,7 +206,7 @@ class CuraEngineBackend(QObject, Backend):
self._scene.gcode_dict = {} self._scene.gcode_dict = {}
# see if we really have to slice # see if we really have to slice
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0) build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced) Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
num_objects = self._numObjects() num_objects = self._numObjects()
@ -497,7 +496,7 @@ class CuraEngineBackend(QObject, Backend):
node.getParent().removeChild(node) node.getParent().removeChild(node)
def markSliceAll(self): def markSliceAll(self):
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1): for build_plate_number in range(Application.getInstance().getMultiBuildPlateModel().maxBuildPlate + 1):
if build_plate_number not in self._build_plates_to_be_sliced: if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number) self._build_plates_to_be_sliced.append(build_plate_number)
@ -582,7 +581,7 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time ) Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
# See if we need to process the sliced layers job. # See if we need to process the sliced layers job.
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate: if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate:
self._startProcessSlicedLayersJob(active_build_plate) self._startProcessSlicedLayersJob(active_build_plate)
# self._onActiveViewChanged() # self._onActiveViewChanged()
@ -702,7 +701,7 @@ class CuraEngineBackend(QObject, Backend):
application = Application.getInstance() application = Application.getInstance()
view = application.getController().getActiveView() view = application.getController().getActiveView()
if view: if view:
active_build_plate = application.getBuildPlateModel().activeBuildPlate active_build_plate = application.getMultiBuildPlateModel().activeBuildPlate
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet. if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
self._layer_view_active = True self._layer_view_active = True
# There is data and we're not slicing at the moment # There is data and we're not slicing at the moment

View file

@ -12,6 +12,6 @@ class ProcessGCodeLayerJob(Job):
self._message = message self._message = message
def run(self): def run(self):
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
gcode_list = self._scene.gcode_dict[active_build_plate_id] gcode_list = self._scene.gcode_dict[active_build_plate_id]
gcode_list.append(self._message.data.decode("utf-8", "replace")) gcode_list.append(self._message.data.decode("utf-8", "replace"))

View file

@ -437,7 +437,7 @@ class FlavorParser:
scene_node.addDecorator(gcode_list_decorator) scene_node.addDecorator(gcode_list_decorator)
# gcode_dict stores gcode_lists for a number of build plates. # gcode_dict stores gcode_lists for a number of build plates.
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
gcode_dict = {active_build_plate_id: gcode_list} gcode_dict = {active_build_plate_id: gcode_list}
Application.getInstance().getController().getScene().gcode_dict = gcode_dict Application.getInstance().getController().getScene().gcode_dict = gcode_dict

View file

@ -60,7 +60,7 @@ class GCodeWriter(MeshWriter):
Logger.log("e", "GCodeWriter does not support non-text mode.") Logger.log("e", "GCodeWriter does not support non-text mode.")
return False return False
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
scene = Application.getInstance().getController().getScene() scene = Application.getInstance().getController().getScene()
gcode_dict = getattr(scene, "gcode_dict") gcode_dict = getattr(scene, "gcode_dict")
if not gcode_dict: if not gcode_dict:

View file

@ -32,11 +32,11 @@ UM.TooltipArea
if(checked) if(checked)
{ {
addedSettingsModel.setVisible(model.key, checked); addedSettingsModel.setVisible(model.key, checked);
UM.ActiveTool.triggerAction("subscribeForSettingValidation", model.key) UM.ActiveTool.triggerActionWithData("subscribeForSettingValidation", model.key)
} }
else else
{ {
UM.ActiveTool.triggerAction("unsubscribeForSettingValidation", model.key) UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
addedSettingsModel.setVisible(model.key, checked); addedSettingsModel.setVisible(model.key, checked);
} }
UM.ActiveTool.forceUpdate(); UM.ActiveTool.forceUpdate();

View file

@ -242,7 +242,7 @@ Item {
onClicked: { onClicked: {
addedSettingsModel.setVisible(model.key, false) addedSettingsModel.setVisible(model.key, false)
UM.ActiveTool.triggerAction("unsubscribeForSettingValidation", model.key) UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
} }
style: ButtonStyle style: ButtonStyle

View file

@ -62,7 +62,7 @@ class PostProcessingPlugin(QObject, Extension):
return return
# get gcode list for the active build plate # get gcode list for the active build plate
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
gcode_list = gcode_dict[active_build_plate_id] gcode_list = gcode_dict[active_build_plate_id]
if not gcode_list: if not gcode_list:
return return

View file

@ -93,7 +93,7 @@ class SimulationPass(RenderPass):
self.bind() self.bind()
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True) tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True)
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
head_position = None # Indicates the current position of the print head head_position = None # Indicates the current position of the print head
nozzle_node = None nozzle_node = None

View file

@ -45,7 +45,7 @@ class SupportEraser(Tool):
move_vector = Vector(0, 5, 0) move_vector = Vector(0, 5, 0)
node.setPosition(move_vector) node.setPosition(move_vector)
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
node.addDecorator(SettingOverrideDecorator()) node.addDecorator(SettingOverrideDecorator())
node.addDecorator(BuildPlateDecorator(active_build_plate)) node.addDecorator(BuildPlateDecorator(active_build_plate))

View file

@ -81,7 +81,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self.writeStarted.emit(self) self.writeStarted.emit(self)
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", []) gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
gcode_list = gcode_dict[active_build_plate_id] gcode_list = gcode_dict[active_build_plate_id]
if not gcode_list: if not gcode_list:

View file

@ -184,7 +184,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self.writeStarted.emit(self) self.writeStarted.emit(self)
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", []) gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
gcode_list = gcode_dict[active_build_plate_id] gcode_list = gcode_dict[active_build_plate_id]
if not gcode_list: if not gcode_list:

View file

@ -99,7 +99,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
Application.getInstance().getController().setActiveStage("MonitorStage") Application.getInstance().getController().setActiveStage("MonitorStage")
# find the G-code for the active build plate to print # find the G-code for the active build plate to print
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict") gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict")
gcode_list = gcode_dict[active_build_plate_id] gcode_list = gcode_dict[active_build_plate_id]

View file

@ -3,14 +3,9 @@
import configparser #To parse preference files. import configparser #To parse preference files.
import io #To serialise the preference files afterwards. import io #To serialise the preference files afterwards.
import os
from urllib.parse import quote_plus
from UM.Resources import Resources
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this. from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
from cura.CuraApplication import CuraApplication
# a list of all legacy "Not Supported" quality profiles # a list of all legacy "Not Supported" quality profiles
_OLD_NOT_SUPPORTED_PROFILES = [ _OLD_NOT_SUPPORTED_PROFILES = [
@ -130,7 +125,6 @@ class VersionUpgrade30to31(VersionUpgrade):
parser.write(output) parser.write(output)
return [filename], [output.getvalue()] return [filename], [output.getvalue()]
## Upgrades a container stack from version 3.0 to 3.1. ## Upgrades a container stack from version 3.0 to 3.1.
# #
# \param serialised The serialised form of a container stack. # \param serialised The serialised form of a container stack.
@ -172,71 +166,3 @@ class VersionUpgrade30to31(VersionUpgrade):
output = io.StringIO() output = io.StringIO()
parser.write(output) parser.write(output)
return [filename], [output.getvalue()] return [filename], [output.getvalue()]
def _getSingleExtrusionMachineQualityChanges(self, quality_changes_container):
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
quality_changes_containers = []
for item in os.listdir(quality_changes_dir):
file_path = os.path.join(quality_changes_dir, item)
if not os.path.isfile(file_path):
continue
parser = configparser.ConfigParser(interpolation = None)
try:
parser.read([file_path])
except:
# skip, it is not a valid stack file
continue
if not parser.has_option("metadata", "type"):
continue
if "quality_changes" != parser["metadata"]["type"]:
continue
if not parser.has_option("general", "name"):
continue
if quality_changes_container["general"]["name"] != parser["general"]["name"]:
continue
quality_changes_containers.append(parser)
return quality_changes_containers
def _createExtruderQualityChangesForSingleExtrusionMachine(self, filename, global_quality_changes):
suffix = "_" + quote_plus(global_quality_changes["general"]["name"].lower())
machine_name = os.path.os.path.basename(filename).replace(".inst.cfg", "").replace(suffix, "")
# Why is this here?!
# When we load a .curaprofile file the deserialize will trigger a version upgrade, creating a dangling file.
# This file can be recognized by it's lack of a machine name in the target filename.
# So when we detect that situation here, we don't create the file and return.
if machine_name == "":
return
new_filename = machine_name + "_" + "fdmextruder" + suffix
extruder_quality_changes_parser = configparser.ConfigParser(interpolation = None)
extruder_quality_changes_parser.add_section("general")
extruder_quality_changes_parser["general"]["version"] = str(2)
extruder_quality_changes_parser["general"]["name"] = global_quality_changes["general"]["name"]
extruder_quality_changes_parser["general"]["definition"] = global_quality_changes["general"]["definition"]
# check renamed definition
if extruder_quality_changes_parser["general"]["definition"] in _RENAMED_DEFINITION_DICT:
extruder_quality_changes_parser["general"]["definition"] = _RENAMED_DEFINITION_DICT[extruder_quality_changes_parser["general"]["definition"]]
extruder_quality_changes_parser.add_section("metadata")
extruder_quality_changes_parser["metadata"]["quality_type"] = global_quality_changes["metadata"]["quality_type"]
extruder_quality_changes_parser["metadata"]["type"] = global_quality_changes["metadata"]["type"]
extruder_quality_changes_parser["metadata"]["setting_version"] = str(4)
extruder_quality_changes_parser["metadata"]["extruder"] = "fdmextruder"
extruder_quality_changes_output = io.StringIO()
extruder_quality_changes_parser.write(extruder_quality_changes_output)
extruder_quality_changes_filename = quote_plus(new_filename) + ".inst.cfg"
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
with open(os.path.join(quality_changes_dir, extruder_quality_changes_filename), "w", encoding = "utf-8") as f:
f.write(extruder_quality_changes_output.getvalue())

View file

@ -48,18 +48,35 @@ class XmlMaterialProfile(InstanceContainer):
## Overridden from InstanceContainer ## Overridden from InstanceContainer
# set the meta data for all machine / variant combinations # set the meta data for all machine / variant combinations
def setMetaDataEntry(self, key, value): #
# The "apply_to_all" flag indicates whether this piece of metadata should be applied to all material containers
# or just this specific container.
# For example, when you change the material name, you want to apply it to all its derived containers, but for
# some specific settings, they should only be applied to a machine/variant-specific container.
#
def setMetaDataEntry(self, key, value, apply_to_all = True):
registry = ContainerRegistry.getInstance() registry = ContainerRegistry.getInstance()
if registry.isReadOnly(self.getId()): if registry.isReadOnly(self.getId()):
return return
# Prevent recursion
if not apply_to_all:
super().setMetaDataEntry(key, value) super().setMetaDataEntry(key, value)
return
basefile = self.getMetaDataEntry("base_file", self.getId()) #if basefile is self.getId, this is a basefile. # Get the MaterialGroup
# Update all containers that share basefile material_manager = CuraApplication.getInstance().getMaterialManager()
for container in registry.findInstanceContainers(base_file = basefile): root_material_id = self.getMetaDataEntry("base_file") #if basefile is self.getId, this is a basefile.
if container.getMetaDataEntry(key, None) != value: # Prevent recursion material_group = material_manager.getMaterialGroup(root_material_id)
container.setMetaDataEntry(key, value)
# Update the root material container
root_material_container = material_group.root_material_node.getContainer()
root_material_container.setMetaDataEntry(key, value, apply_to_all = False)
# Update all containers derived from it
for node in material_group.derived_material_node_list:
container = node.getContainer()
container.setMetaDataEntry(key, value, apply_to_all = False)
## Overridden from InstanceContainer, similar to setMetaDataEntry. ## Overridden from InstanceContainer, similar to setMetaDataEntry.
# without this function the setName would only set the name of the specific nozzle / material / machine combination container # without this function the setName would only set the name of the specific nozzle / material / machine combination container
@ -190,6 +207,8 @@ class XmlMaterialProfile(InstanceContainer):
machine_container_map = {} machine_container_map = {}
machine_nozzle_map = {} machine_nozzle_map = {}
variant_manager = CuraApplication.getInstance()._variant_manager
all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId()) all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId())
for container in all_containers: for container in all_containers:
definition_id = container.getDefinition().getId() definition_id = container.getDefinition().getId()
@ -202,9 +221,10 @@ class XmlMaterialProfile(InstanceContainer):
if definition_id not in machine_nozzle_map: if definition_id not in machine_nozzle_map:
machine_nozzle_map[definition_id] = {} machine_nozzle_map[definition_id] = {}
variant = container.getMetaDataEntry("variant") variant_name = container.getMetaDataEntry("variant_name")
if variant: if variant_name:
machine_nozzle_map[definition_id][variant] = container machine_nozzle_map[definition_id][variant_name] = variant_manager.getVariantNode(definition_id,
variant_name)
continue continue
machine_container_map[definition_id] = container machine_container_map[definition_id] = container
@ -236,16 +256,12 @@ class XmlMaterialProfile(InstanceContainer):
self._addSettingElement(builder, instance) self._addSettingElement(builder, instance)
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile. # Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
for hotend_id, hotend in machine_nozzle_map[definition_id].items(): for hotend_name, variant_node in machine_nozzle_map[definition_id].items():
variant_containers = registry.findInstanceContainersMetadata(id = hotend.getMetaDataEntry("variant"))
if not variant_containers:
continue
# The hotend identifier is not the containers name, but its "name". # The hotend identifier is not the containers name, but its "name".
builder.start("hotend", {"id": variant_containers[0]["name"]}) builder.start("hotend", {"id": hotend_name})
# Compatible is a special case, as it's added as a meta data entry (instead of an instance). # Compatible is a special case, as it's added as a meta data entry (instead of an instance).
compatible = hotend.getMetaDataEntry("compatible") compatible = variant_node.metadata.get("compatible")
if compatible is not None: if compatible is not None:
builder.start("setting", {"key": "hardware compatible"}) builder.start("setting", {"key": "hardware compatible"})
if compatible: if compatible:
@ -254,7 +270,7 @@ class XmlMaterialProfile(InstanceContainer):
builder.data("no") builder.data("no")
builder.end("setting") builder.end("setting")
for instance in hotend.findInstances(): for instance in variant_node.getContainer().findInstances():
if container.getInstance(instance.definition.key) and container.getProperty(instance.definition.key, "value") == instance.value: if container.getInstance(instance.definition.key) and container.getProperty(instance.definition.key, "value") == instance.value:
# If the settings match that of the machine profile, just skip since we inherit the machine profile. # If the settings match that of the machine profile, just skip since we inherit the machine profile.
continue continue
@ -590,14 +606,10 @@ class XmlMaterialProfile(InstanceContainer):
if buildplate_id is None: if buildplate_id is None:
continue continue
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata( from cura.Machines.VariantManager import VariantType
id = buildplate_id) variant_manager = CuraApplication.getInstance().getVariantManager()
if not variant_containers: variant_node = variant_manager.getVariantNode(machine_id, buildplate_id)
# It is not really properly defined what "ID" is so also search for variants by name. if not variant_node:
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(
definition = machine_id, name = buildplate_id)
if not variant_containers:
continue continue
buildplate_compatibility = machine_compatibility buildplate_compatibility = machine_compatibility
@ -618,16 +630,14 @@ class XmlMaterialProfile(InstanceContainer):
hotends = machine.iterfind("./um:hotend", self.__namespaces) hotends = machine.iterfind("./um:hotend", self.__namespaces)
for hotend in hotends: for hotend in hotends:
hotend_id = hotend.get("id") # The "id" field for hotends in material profiles are actually
if hotend_id is None: hotend_name = hotend.get("id")
if hotend_name is None:
continue continue
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id) variant_manager = CuraApplication.getInstance().getVariantManager()
if not variant_containers: variant_node = variant_manager.getVariantNode(machine_id, hotend_name)
# It is not really properly defined what "ID" is so also search for variants by name. if not variant_node:
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
if not variant_containers:
continue continue
hotend_compatibility = machine_compatibility hotend_compatibility = machine_compatibility
@ -643,20 +653,20 @@ class XmlMaterialProfile(InstanceContainer):
else: else:
Logger.log("d", "Unsupported material setting %s", key) Logger.log("d", "Unsupported material setting %s", key)
new_hotend_id = self.getId() + "_" + machine_id + "_" + hotend_id.replace(" ", "_") new_hotend_specific_material_id = self.getId() + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
# Same as machine compatibility, keep the derived material containers consistent with the parent material # Same as machine compatibility, keep the derived material containers consistent with the parent material
if ContainerRegistry.getInstance().isLoaded(new_hotend_id): if ContainerRegistry.getInstance().isLoaded(new_hotend_specific_material_id):
new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0] new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_specific_material_id)[0]
is_new_material = False is_new_material = False
else: else:
new_hotend_material = XmlMaterialProfile(new_hotend_id) new_hotend_material = XmlMaterialProfile(new_hotend_specific_material_id)
is_new_material = True is_new_material = True
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData())) new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_hotend_material.getMetaData()["id"] = new_hotend_id new_hotend_material.getMetaData()["id"] = new_hotend_specific_material_id
new_hotend_material.getMetaData()["name"] = self.getName() new_hotend_material.getMetaData()["name"] = self.getName()
new_hotend_material.getMetaData()["variant"] = variant_containers[0]["id"] new_hotend_material.getMetaData()["variant_name"] = hotend_name
new_hotend_material.setDefinition(machine_id) new_hotend_material.setDefinition(machine_id)
# Don't use setMetadata, as that overrides it for all materials with same base file # Don't use setMetadata, as that overrides it for all materials with same base file
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
@ -833,39 +843,30 @@ class XmlMaterialProfile(InstanceContainer):
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_map["buildplate_recommended"] buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_map["buildplate_recommended"]
for hotend in machine.iterfind("./um:hotend", cls.__namespaces): for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
hotend_id = hotend.get("id") hotend_name = hotend.get("id")
if hotend_id is None: if hotend_name is None:
continue continue
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
if not variant_containers:
# It is not really properly defined what "ID" is so also search for variants by name.
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
hotend_compatibility = machine_compatibility hotend_compatibility = machine_compatibility
for entry in hotend.iterfind("./um:setting", cls.__namespaces): for entry in hotend.iterfind("./um:setting", cls.__namespaces):
key = entry.get("key") key = entry.get("key")
if key == "hardware compatible": if key == "hardware compatible":
hotend_compatibility = cls._parseCompatibleValue(entry.text) hotend_compatibility = cls._parseCompatibleValue(entry.text)
new_hotend_id = container_id + "_" + machine_id + "_" + hotend_id.replace(" ", "_") new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
# Same as machine compatibility, keep the derived material containers consistent with the parent material # Same as machine compatibility, keep the derived material containers consistent with the parent material
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_id) found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_specific_material_id)
if found_materials: if found_materials:
new_hotend_material_metadata = found_materials[0] new_hotend_material_metadata = found_materials[0]
else: else:
new_hotend_material_metadata = {} new_hotend_material_metadata = {}
new_hotend_material_metadata.update(base_metadata) new_hotend_material_metadata.update(base_metadata)
if variant_containers: new_hotend_material_metadata["variant_name"] = hotend_name
new_hotend_material_metadata["variant"] = variant_containers[0]["id"]
else:
new_hotend_material_metadata["variant"] = hotend_id
_with_missing_variants.append(new_hotend_material_metadata)
new_hotend_material_metadata["compatible"] = hotend_compatibility new_hotend_material_metadata["compatible"] = hotend_compatibility
new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer
new_hotend_material_metadata["id"] = new_hotend_id new_hotend_material_metadata["id"] = new_hotend_specific_material_id
new_hotend_material_metadata["definition"] = machine_id new_hotend_material_metadata["definition"] = machine_id
if buildplate_map["buildplate_compatible"]: if buildplate_map["buildplate_compatible"]:
new_hotend_material_metadata["buildplate_compatible"] = buildplate_map["buildplate_compatible"] new_hotend_material_metadata["buildplate_compatible"] = buildplate_map["buildplate_compatible"]
@ -992,21 +993,3 @@ def _indent(elem, level = 0):
# before the last } # before the last }
def _tag_without_namespace(element): def _tag_without_namespace(element):
return element.tag[element.tag.rfind("}") + 1:] return element.tag[element.tag.rfind("}") + 1:]
#While loading XML profiles, some of these profiles don't know what variant
#they belong to. We'd like to search by the machine ID and the variant's
#name, but we don't know the variant's ID. Not all variants have been loaded
#yet so we can't run a filter on the name and machine. The ID is unknown
#so we can't lazily load the variant either. So we have to wait until all
#the rest is loaded properly and then assign the correct variant to the
#material files that were missing it.
_with_missing_variants = []
def _fillMissingVariants():
registry = ContainerRegistry.getInstance()
for variant_metadata in _with_missing_variants:
variants = registry.findContainersMetadata(definition = variant_metadata["definition"], name = variant_metadata["variant"])
if not variants:
Logger.log("w", "Could not find variant for variant-specific material {material_id}.".format(material_id = variant_metadata["id"]))
continue
variant_metadata["variant"] = variants[0]["id"]
ContainerRegistry.allMetadataLoaded.connect(_fillMissingVariants)

View file

@ -12,7 +12,7 @@
"platform": "anycubic_i3_mega_platform.stl", "platform": "anycubic_i3_mega_platform.stl",
"has_materials": false, "has_materials": false,
"has_machine_quality": true, "has_machine_quality": true,
"preferred_quality": "*normal*" "preferred_quality_type": "normal"
}, },
"overrides": "overrides":

View file

@ -12,7 +12,7 @@
"platform": "builder_premium_platform.stl", "platform": "builder_premium_platform.stl",
"platform_offset": [-126, -36, 117], "platform_offset": [-126, -36, 117],
"has_machine_quality": true, "has_machine_quality": true,
"preferred_quality": "*Normal*", "preferred_quality_type": "normal",
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "builder_premium_large_rear", "0": "builder_premium_large_rear",
@ -20,8 +20,6 @@
} }
}, },
"overrides": { "overrides": {
"machine_name": { "default_value": "Builder Premium Large" }, "machine_name": { "default_value": "Builder Premium Large" },
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },

View file

@ -12,7 +12,7 @@
"platform": "builder_premium_platform.stl", "platform": "builder_premium_platform.stl",
"platform_offset": [-126, -36, 117], "platform_offset": [-126, -36, 117],
"has_machine_quality": true, "has_machine_quality": true,
"preferred_quality": "*Normal*", "preferred_quality_type": "normal",
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "builder_premium_medium_rear", "0": "builder_premium_medium_rear",
@ -20,8 +20,6 @@
} }
}, },
"overrides": { "overrides": {
"machine_name": { "default_value": "Builder Premium Medium" }, "machine_name": { "default_value": "Builder Premium Medium" },
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },

View file

@ -11,7 +11,7 @@
"platform": "builder_premium_platform.stl", "platform": "builder_premium_platform.stl",
"platform_offset": [-126, -36, 117], "platform_offset": [-126, -36, 117],
"has_machine_quality": true, "has_machine_quality": true,
"preferred_quality": "*Normal*", "preferred_quality_type": "normal",
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "builder_premium_small_rear", "0": "builder_premium_small_rear",
@ -19,8 +19,6 @@
} }
}, },
"overrides": { "overrides": {
"machine_name": { "default_value": "Builder Premium Small" }, "machine_name": { "default_value": "Builder Premium Small" },
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },

View file

@ -15,9 +15,9 @@
"has_variants": true, "has_variants": true,
"variants_name": "Tool", "variants_name": "Tool",
"preferred_variant": "*0.8*", "preferred_variant_name": "0.8 mm",
"preferred_material": "*pla*", "preferred_material": "generic_pla",
"preferred_quality": "*normal*", "preferred_quality_type": "normal",
"machine_extruder_trains": "machine_extruder_trains":
{ {

View file

@ -7,7 +7,7 @@
"author": "Michael Wildermuth", "author": "Michael Wildermuth",
"manufacturer": "Creality3D", "manufacturer": "Creality3D",
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"preferred_quality": "*Draft*" "preferred_quality_type": "draft"
}, },
"overrides": { "overrides": {
"machine_width": { "machine_width": {

View file

@ -13,8 +13,8 @@
"has_machine_quality": true, "has_machine_quality": true,
"has_variants": true, "has_variants": true,
"variants_name": "Head", "variants_name": "Head",
"preferred_variant": "*lite04*", "preferred_variant_name": "Lite 0.4 mm",
"preferred_material": "*fabtotum_pla*", "preferred_material": "fabtotum_pla",
"supports_usb_connection": false "supports_usb_connection": false
}, },

View file

@ -11,8 +11,8 @@
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g", "file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
"visible": false, "visible": false,
"has_materials": true, "has_materials": true,
"preferred_material": "*generic_pla*", "preferred_material": "generic_pla",
"preferred_quality": "*normal*", "preferred_quality_type": "normal",
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "fdmextruder" "0": "fdmextruder"

View file

@ -10,11 +10,10 @@
"category": "Other", "category": "Other",
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl", "platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
"has_machine_quality": true,
"has_variants": true, "has_variants": true,
"variants_name": "Hotend", "variants_name": "Hotend",
"preferred_variant": "*0.5mm E3D (Default)*" "preferred_variant_name": "0.5mm E3D (Default)"
}, },
"overrides": { "overrides": {

View file

@ -11,8 +11,9 @@
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl", "platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
"has_variants": true, "has_variants": true,
"has_machine_quality": true,
"variants_name": "Hotend", "variants_name": "Hotend",
"preferred_variant": "*0.5mm E3D (Default)*", "preferred_variant_name": "0.5mm E3D (Default)",
"machine_extruder_trains": { "machine_extruder_trains": {
"0": "gmax15plus_dual_extruder_0", "0": "gmax15plus_dual_extruder_0",
"1": "gmax15plus_dual_extruder_1" "1": "gmax15plus_dual_extruder_1"

View file

@ -9,9 +9,8 @@
"platform": "imade3d_jellybox_platform.stl", "platform": "imade3d_jellybox_platform.stl",
"platform_offset": [ 0, -0.3, 0], "platform_offset": [ 0, -0.3, 0],
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"preferred_variant": "*0.4*", "preferred_variant_name": "0.4 mm",
"preferred_material": "*generic_pla*", "preferred_quality_type": "fast",
"preferred_quality": "*fast*",
"has_materials": true, "has_materials": true,
"has_variants": true, "has_variants": true,
"has_machine_materials": true, "has_machine_materials": true,

View file

@ -11,7 +11,7 @@
"platform": "malyan_m200_platform.stl", "platform": "malyan_m200_platform.stl",
"has_machine_quality": true, "has_machine_quality": true,
"has_materials": true, "has_materials": true,
"preferred_quality": "*normal*", "preferred_quality_type": "normal",
"supports_usb_connection": true, "supports_usb_connection": true,
"visible": true, "visible": true,
"first_start_actions": ["MachineSettingsAction"], "first_start_actions": ["MachineSettingsAction"],

View file

@ -10,7 +10,7 @@
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"has_machine_quality": true, "has_machine_quality": true,
"has_materials": true, "has_materials": true,
"preferred_quality": "*normal*", "preferred_quality_type": "normal",
"visible": true "visible": true
}, },

View file

@ -11,7 +11,7 @@
"has_materials": false, "has_materials": false,
"has_machine_quality": true, "has_machine_quality": true,
"platform": "tevo_blackwidow.stl", "platform": "tevo_blackwidow.stl",
"preferred_quality": "*normal*" "preferred_quality_type": "normal"
}, },
"overrides": "overrides":
{ {

View file

@ -9,7 +9,7 @@
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"platform": "ultimaker2_platform.obj", "platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2Plusbackplate.png", "platform_texture": "Ultimaker2Plusbackplate.png",
"preferred_variant": "*0.4*", "preferred_variant_name": "0.4 mm",
"has_variants": true, "has_variants": true,
"has_materials": true, "has_materials": true,
"has_machine_materials": true, "has_machine_materials": true,

View file

@ -15,8 +15,8 @@
"has_machine_materials": true, "has_machine_materials": true,
"has_variant_materials": true, "has_variant_materials": true,
"has_variants": true, "has_variants": true,
"preferred_variant": "*aa04*", "preferred_variant_name": "AA 0.4",
"preferred_quality": "*Normal*", "preferred_quality_type": "normal",
"variants_name": "Print core", "variants_name": "Print core",
"machine_extruder_trains": "machine_extruder_trains":
{ {

View file

@ -16,7 +16,7 @@
"has_variant_materials": true, "has_variant_materials": true,
"has_materials": true, "has_materials": true,
"has_variants": true, "has_variants": true,
"preferred_variant": "*aa04*", "preferred_variant_name": "AA 0.4",
"variants_name": "Print core", "variants_name": "Print core",
"machine_extruder_trains": "machine_extruder_trains":
{ {

View file

@ -172,7 +172,7 @@ Item
Action Action
{ {
id: updateProfileAction; id: updateProfileAction;
enabled: !Cura.MachineManager.stacksHaveErrors && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId) enabled: !Cura.MachineManager.stacksHaveErrors && Cura.MachineManager.hasUserSettings && Cura.MachineManager.activeQualityChangesGroup != null
text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings/overrides"); text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings/overrides");
onTriggered: Cura.ContainerManager.updateQualityChanges(); onTriggered: Cura.ContainerManager.updateQualityChanges();
} }

View file

@ -190,24 +190,25 @@ UM.MainWindow
model: Cura.ExtrudersModel { simpleNames: true } model: Cura.ExtrudersModel { simpleNames: true }
Menu { Menu {
title: model.name title: model.name
visible: machineExtruderCount.properties.value > 1
NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: Cura.MachineManager.hasVariants; extruderIndex: index } NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: Cura.MachineManager.hasVariants; extruderIndex: index }
MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: Cura.MachineManager.hasMaterials; extruderIndex: index } MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: Cura.MachineManager.hasMaterials; extruderIndex: index }
ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); }
MenuSeparator { } MenuSeparator {
visible: Cura.MachineManager.hasVariants || Cura.MachineManager.hasMaterials
}
MenuItem { text: catalog.i18nc("@action:inmenu", "Set as Active Extruder"); onTriggered: Cura.ExtruderManager.setActiveExtruderIndex(model.index) } MenuItem {
text: catalog.i18nc("@action:inmenu", "Set as Active Extruder")
onTriggered: Cura.ExtruderManager.setActiveExtruderIndex(model.index)
}
} }
onObjectAdded: settingsMenu.insertItem(index, object) onObjectAdded: settingsMenu.insertItem(index, object)
onObjectRemoved: settingsMenu.removeItem(object) onObjectRemoved: settingsMenu.removeItem(object)
} }
BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates } BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates }
NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: machineExtruderCount.properties.value <= 1 && Cura.MachineManager.hasVariants } ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); }
MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: machineExtruderCount.properties.value <= 1 && Cura.MachineManager.hasMaterials }
ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); visible: machineExtruderCount.properties.value <= 1 }
MenuSeparator { } MenuSeparator { }

View file

@ -7,7 +7,7 @@ import QtQuick.Dialogs 1.2
import QtQuick.Window 2.1 import QtQuick.Window 2.1
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.1 as Cura import Cura 1.0 as Cura
UM.Dialog UM.Dialog
{ {

View file

@ -1,8 +1,8 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.8
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
@ -12,28 +12,22 @@ Menu
id: menu id: menu
title: "Build plate" title: "Build plate"
property Cura.BuildPlateModel buildPlateModel: CuraApplication.getBuildPlateModel()
Instantiator Instantiator
{ {
id: buildplateInstantiator model: menu.buildPlateModel
model: UM.InstanceContainersModel
{
filter:
{
"type": "variant",
"hardware_type": "buildplate",
"definition": Cura.MachineManager.activeDefinitionId //Only show variants of this machine
}
}
MenuItem { MenuItem {
text: model.name text: model.name
checkable: true checkable: true
checked: model.id == Cura.MachineManager.globalVariantId checked: model.name == Cura.MachineManager.globalVariantName
exclusiveGroup: group exclusiveGroup: group
onTriggered: onTriggered: {
{ Cura.MachineManager.setGlobalVariant(model.container_node);
Cura.MachineManager.setActiveVariantBuildplate(model.id);
} }
} }
onObjectAdded: menu.insertItem(index, object) onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object) onObjectRemoved: menu.removeItem(object)
} }

View file

@ -7,7 +7,7 @@ import QtQuick.Dialogs 1.2
import QtQuick.Window 2.1 import QtQuick.Window 2.1
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.2 as Cura import Cura 1.0 as Cura
Menu Menu
{ {
@ -15,6 +15,8 @@ Menu
property bool shouldShowExtruders: machineExtruderCount.properties.value > 1; property bool shouldShowExtruders: machineExtruderCount.properties.value > 1;
property Cura.MultiBuildPlateModel multiBuildPlateModel: CuraApplication.getMultiBuildPlateModel()
// Selection-related actions. // Selection-related actions.
MenuItem { action: Cura.Actions.centerSelection; } MenuItem { action: Cura.Actions.centerSelection; }
MenuItem { action: Cura.Actions.deleteSelection; } MenuItem { action: Cura.Actions.deleteSelection; }
@ -45,13 +47,13 @@ Menu
Instantiator Instantiator
{ {
model: Cura.BuildPlateModel model: base.multiBuildPlateModel
MenuItem { MenuItem {
enabled: UM.Selection.hasSelection enabled: UM.Selection.hasSelection
text: Cura.BuildPlateModel.getItem(index).name; text: base.multiBuildPlateModel.getItem(index).name;
onTriggered: CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.getItem(index).buildPlateNumber); onTriggered: CuraActions.setBuildPlateForSelection(base.multiBuildPlateModel.getItem(index).buildPlateNumber);
checkable: true checkable: true
checked: Cura.BuildPlateModel.selectionBuildPlates.indexOf(Cura.BuildPlateModel.getItem(index).buildPlateNumber) != -1; checked: base.multiBuildPlateModel.selectionBuildPlates.indexOf(base.multiBuildPlateModel.getItem(index).buildPlateNumber) != -1;
visible: UM.Preferences.getValue("cura/use_multi_build_plate") visible: UM.Preferences.getValue("cura/use_multi_build_plate")
} }
onObjectAdded: base.insertItem(index, object); onObjectAdded: base.insertItem(index, object);
@ -62,7 +64,7 @@ Menu
enabled: UM.Selection.hasSelection enabled: UM.Selection.hasSelection
text: "New build plate"; text: "New build plate";
onTriggered: { onTriggered: {
CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.maxBuildPlate + 1); CuraActions.setBuildPlateForSelection(base.multiBuildPlateModel.maxBuildPlate + 1);
checked = false; checked = false;
} }
checkable: true checkable: true

View file

@ -1,8 +1,8 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.8
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
@ -13,64 +13,6 @@ Menu
title: "Material" title: "Material"
property int extruderIndex: 0 property int extruderIndex: 0
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool isClusterPrinter:
{
if(Cura.MachineManager.printerOutputDevices.length == 0)
{
return false;
}
var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize;
// This is not a cluster printer or the cluster it is just one printer
if(clusterSize == undefined || clusterSize == 1)
{
return false;
}
return true;
}
UM.SettingPropertyProvider
{
id: materialDiameterProvider
containerStackId: Cura.ExtruderManager.activeExtruderStackId
key: "material_diameter"
watchedProperties: [ "value" ]
storeIndex: 5
}
MenuItem
{
id: automaticMaterial
text:
{
if(visible)
{
var materialName = Cura.MachineManager.printerOutputDevices[0].materialNames[extruderIndex];
return catalog.i18nc("@title:menuitem %1 is the automatically selected material", "Automatic: %1").arg(materialName);
}
return "";
}
visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].materialNames != undefined && Cura.MachineManager.printerOutputDevices[0].materialNames.length > extruderIndex && !isClusterPrinter
onTriggered:
{
var materialId = Cura.MachineManager.printerOutputDevices[0].materialIds[extruderIndex];
var items = materialsModel.items;
for(var i in items)
{
if (items[i]["metadata"]["GUID"] == materialId)
{
Cura.MachineManager.setActiveMaterial(items[i].id);
break;
}
}
}
}
MenuSeparator
{
visible: automaticMaterial.visible
}
Instantiator Instantiator
{ {
@ -79,16 +21,11 @@ Menu
{ {
text: model.name text: model.name
checkable: true checkable: true
checked: model.id == Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]] checked: model.root_material_id == Cura.MachineManager.currentRootMaterialId[extruderIndex]
exclusiveGroup: group exclusiveGroup: group
onTriggered: onTriggered:
{ {
// This workaround is done because of the application menus for materials and variants for multiextrusion printers. Cura.MachineManager.setMaterial(extruderIndex, model.container_node);
// The extruder menu would always act on the correspoding extruder only, instead of acting on the extruder selected in the UI.
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
Cura.MachineManager.setActiveMaterial(model.id);
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
} }
} }
onObjectAdded: menu.insertItem(index, object) onObjectAdded: menu.insertItem(index, object)
@ -126,12 +63,8 @@ Menu
exclusiveGroup: group exclusiveGroup: group
onTriggered: onTriggered:
{ {
// This workaround is done because of the application menus for materials and variants for multiextrusion printers.
// The extruder menu would always act on the correspoding extruder only, instead of acting on the extruder selected in the UI.
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); Cura.MachineManager.setMaterial(activeExtruderIndex, model.container_node);
Cura.MachineManager.setActiveMaterial(model.id);
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
} }
} }
onObjectAdded: brandMaterialsMenu.insertItem(index, object) onObjectAdded: brandMaterialsMenu.insertItem(index, object)
@ -146,24 +79,16 @@ Menu
onObjectRemoved: menu.removeItem(object) onObjectRemoved: menu.removeItem(object)
} }
ListModel Cura.GenericMaterialsModel
{ {
id: genericMaterialsModel id: genericMaterialsModel
Component.onCompleted: populateMenuModels() extruderPosition: menu.extruderIndex
} }
ListModel Cura.BrandMaterialsModel
{ {
id: brandModel id: brandModel
} extruderPosition: menu.extruderIndex
//: Model used to populate the brandModel
Cura.MaterialsModel
{
id: materialsModel
filter: materialFilter()
onModelReset: populateMenuModels()
onDataChanged: populateMenuModels()
} }
ExclusiveGroup { id: group } ExclusiveGroup { id: group }
@ -171,80 +96,4 @@ Menu
MenuSeparator { } MenuSeparator { }
MenuItem { action: Cura.Actions.manageMaterials } MenuItem { action: Cura.Actions.manageMaterials }
function materialFilter()
{
var result = { "type": "material", "approximate_diameter": Math.round(materialDiameterProvider.properties.value).toString() };
if(Cura.MachineManager.filterMaterialsByMachine)
{
result.definition = Cura.MachineManager.activeQualityDefinitionId;
if(Cura.MachineManager.hasVariants)
{
result.variant = Cura.MachineManager.activeQualityVariantId;
}
}
else
{
result.definition = "fdmprinter";
result.compatible = true; //NB: Only checks for compatibility in global version of material, but we don't have machine-specific materials anyway.
}
return result;
}
function populateMenuModels()
{
// Create a structure of unique brands and their material-types
genericMaterialsModel.clear()
brandModel.clear();
var items = materialsModel.items;
var materialsByBrand = {};
for (var i in items) {
var brandName = items[i]["metadata"]["brand"];
var materialName = items[i]["metadata"]["material"];
if (brandName == "Generic")
{
// Add to top section
var materialId = items[i].id;
genericMaterialsModel.append({
id: materialId,
name: items[i].name
});
}
else
{
// Add to per-brand, per-material menu
if (!materialsByBrand.hasOwnProperty(brandName))
{
materialsByBrand[brandName] = {};
}
if (!materialsByBrand[brandName].hasOwnProperty(materialName))
{
materialsByBrand[brandName][materialName] = [];
}
materialsByBrand[brandName][materialName].push({
id: items[i].id,
name: items[i].name
});
}
}
for (var brand in materialsByBrand)
{
var materialsByBrandModel = [];
var materials = materialsByBrand[brand];
for (var material in materials)
{
materialsByBrandModel.push({
name: material,
colors: materials[material]
})
}
brandModel.append({
name: brand,
materials: materialsByBrandModel
});
}
}
} }

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.8
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
@ -13,89 +13,31 @@ Menu
title: "Nozzle" title: "Nozzle"
property int extruderIndex: 0 property int extruderIndex: 0
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool isClusterPrinter:
{
if(Cura.MachineManager.printerOutputDevices.length == 0)
{
return false;
}
var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize;
// This is not a cluster printer or the cluster it is just one printer
if(clusterSize == undefined || clusterSize == 1)
{
return false;
}
return true;
}
MenuItem Cura.NozzleModel
{ {
id: automaticNozzle id: nozzleModel
text:
{
if(visible)
{
var nozzleName = Cura.MachineManager.printerOutputDevices[0].hotendIds[extruderIndex];
return catalog.i18nc("@title:menuitem %1 is the nozzle currently loaded in the printer", "Automatic: %1").arg(nozzleName);
}
return "";
}
visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].hotendIds != undefined && Cura.MachineManager.printerOutputDevices[0].hotendIds.length > extruderIndex && !isClusterPrinter
onTriggered:
{
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
var hotendId = Cura.MachineManager.printerOutputDevices[0].hotendIds[extruderIndex];
var itemIndex = nozzleInstantiator.model.find("name", hotendId);
if(itemIndex > -1)
{
Cura.MachineManager.setActiveVariant(nozzleInstantiator.model.getItem(itemIndex).id);
}
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
}
}
MenuSeparator
{
visible: automaticNozzle.visible
} }
Instantiator Instantiator
{ {
id: nozzleInstantiator model: nozzleModel
model: UM.InstanceContainersModel
MenuItem
{ {
filter: text: model.hotend_name
{ checkable: true
var filter_dict = checked: {
{ return Cura.MachineManager.activeVariantNames[extruderIndex] == model.hotend_name
"type": "variant", }
"definition": Cura.MachineManager.activeQualityDefinitionId //Only show variants of this machine exclusiveGroup: group
onTriggered: {
Cura.MachineManager.setVariantGroup(menu.extruderIndex, model.container_node);
} }
if (Cura.MachineManager.hasVariantBuildplates)
{
filter_dict["hardware_type"] = "nozzle"
} }
return filter_dict onObjectAdded: menu.insertItem(index, object);
} onObjectRemoved: menu.removeItem(object);
}
MenuItem {
text: model.name
checkable: true
checked: model.id == Cura.MachineManager.allActiveVariantIds[Cura.ExtruderManager.extruderIds[extruderIndex]]
exclusiveGroup: group
onTriggered:
{
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
Cura.MachineManager.setActiveVariant(model.id);
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
}
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
} }
ExclusiveGroup { id: group } ExclusiveGroup { id: group }

View file

@ -1,8 +1,8 @@
// Copyright (c) 2016 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.8
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
@ -13,15 +13,17 @@ Menu
Instantiator Instantiator
{ {
model: Cura.ProfilesModel model: Cura.QualityProfilesDropDownMenuModel
MenuItem MenuItem
{ {
text: (model.layer_height != "") ? model.name + " - " + model.layer_height : model.name text: (model.layer_height != "") ? model.name + " - " + model.layer_height + model.layer_height_unit : model.name
checkable: true checkable: true
checked: Cura.MachineManager.activeQualityId == model.id checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
exclusiveGroup: group exclusiveGroup: group
onTriggered: Cura.MachineManager.setActiveQuality(model.id) onTriggered: {
Cura.MachineManager.setQualityGroup(model.quality_group)
}
visible: model.available visible: model.available
} }
@ -32,24 +34,27 @@ Menu
MenuSeparator MenuSeparator
{ {
id: customSeparator id: customSeparator
visible: Cura.UserProfilesModel.rowCount > 0 visible: Cura.CustomQualityProfilesDropDownMenuModel.rowCount > 0
} }
Instantiator Instantiator
{ {
id: customProfileInstantiator id: customProfileInstantiator
model: Cura.UserProfilesModel model: Cura.CustomQualityProfilesDropDownMenuModel
Connections
{ {
onModelReset: customSeparator.visible = rowCount() > 0 target: Cura.CustomQualityProfilesDropDownMenuModel
onModelReset: customSeparator.visible = Cura.CustomQualityProfilesDropDownMenuModel.rowCount() > 0
} }
MenuItem MenuItem
{ {
text: model.name text: model.name
checkable: true checkable: model.available
checked: Cura.MachineManager.activeQualityChangesId == model.id checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
exclusiveGroup: group exclusiveGroup: group
onTriggered: Cura.MachineManager.setActiveQuality(model.id) onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)
} }
onObjectAdded: onObjectAdded:
@ -73,23 +78,4 @@ Menu
MenuItem { action: Cura.Actions.resetProfile } MenuItem { action: Cura.Actions.resetProfile }
MenuSeparator { } MenuSeparator { }
MenuItem { action: Cura.Actions.manageProfiles } MenuItem { action: Cura.Actions.manageProfiles }
function getFilter(initial_conditions)
{
var result = initial_conditions;
if(Cura.MachineManager.filterQualityByMachine)
{
result.definition = Cura.MachineManager.activeQualityDefinitionId;
if(Cura.MachineManager.hasMaterials)
{
result.material = Cura.MachineManager.activeQualityMaterialId;
}
}
else
{
result.definition = "fdmprinter"
}
return result
}
} }

View file

@ -5,7 +5,7 @@ import QtQuick 2.2
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.2 as Cura import Cura 1.0 as Cura
Menu Menu
{ {
@ -13,6 +13,8 @@ Menu
id: base id: base
enabled: !PrintInformation.preSliced enabled: !PrintInformation.preSliced
property Cura.MultiBuildPlateModel multiBuildPlateModel: CuraApplication.getMultiBuildPlateModel()
// main views // main views
Instantiator Instantiator
{ {
@ -53,12 +55,12 @@ Menu
visible: UM.Preferences.getValue("cura/use_multi_build_plate") visible: UM.Preferences.getValue("cura/use_multi_build_plate")
Instantiator Instantiator
{ {
model: Cura.BuildPlateModel model: base.multiBuildPlateModel
MenuItem { MenuItem {
text: Cura.BuildPlateModel.getItem(index).name; text: base.multiBuildPlateModel.getItem(index).name;
onTriggered: Cura.SceneController.setActiveBuildPlate(Cura.BuildPlateModel.getItem(index).buildPlateNumber); onTriggered: Cura.SceneController.setActiveBuildPlate(base.multiBuildPlateModel.getItem(index).buildPlateNumber);
checkable: true; checkable: true;
checked: Cura.BuildPlateModel.getItem(index).buildPlateNumber == Cura.BuildPlateModel.activeBuildPlate; checked: base.multiBuildPlateModel.getItem(index).buildPlateNumber == base.multiBuildPlateModel.activeBuildPlate;
exclusiveGroup: buildPlateGroup; exclusiveGroup: buildPlateGroup;
visible: UM.Preferences.getValue("cura/use_multi_build_plate") visible: UM.Preferences.getValue("cura/use_multi_build_plate")
} }

View file

@ -8,7 +8,7 @@ import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1 import QtQuick.Dialogs 1.1
import UM 1.3 as UM import UM 1.3 as UM
import Cura 1.2 as Cura import Cura 1.0 as Cura
import "Menus" import "Menus"
@ -33,6 +33,8 @@ Rectangle
property bool collapsed: true; property bool collapsed: true;
property Cura.MultiBuildPlateModel multiBuildPlateModel: CuraApplication.getMultiBuildPlateModel()
SystemPalette { id: palette } SystemPalette { id: palette }
Button { Button {
@ -67,7 +69,7 @@ Rectangle
Rectangle Rectangle
{ {
height: childrenRect.height height: childrenRect.height
color: Cura.BuildPlateModel.getItem(index).buildPlateNumber == Cura.BuildPlateModel.activeBuildPlate ? palette.highlight : index % 2 ? palette.base : palette.alternateBase color: multiBuildPlateModel.getItem(index).buildPlateNumber == multiBuildPlateModel.activeBuildPlate ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
width: parent.width width: parent.width
Label Label
{ {
@ -75,8 +77,8 @@ Rectangle
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.leftMargin: UM.Theme.getSize("default_margin").width
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30 width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
text: Cura.BuildPlateModel.getItem(index) ? Cura.BuildPlateModel.getItem(index).name : ""; text: multiBuildPlateModel.getItem(index) ? multiBuildPlateModel.getItem(index).name : "";
color: Cura.BuildPlateModel.activeBuildPlate == index ? palette.highlightedText : palette.text color: multiBuildPlateModel.activeBuildPlate == index ? palette.highlightedText : palette.text
elide: Text.ElideRight elide: Text.ElideRight
} }
@ -118,13 +120,12 @@ Rectangle
ListView ListView
{ {
id: buildPlateListView id: buildPlateListView
model: Cura.BuildPlateModel model: multiBuildPlateModel
width: parent.width width: parent.width
delegate: buildPlateDelegate delegate: buildPlateDelegate
} }
} }
Component { Component {
id: objectDelegate id: objectDelegate
Rectangle Rectangle
@ -200,7 +201,6 @@ Rectangle
} }
} }
CheckBox CheckBox
{ {
id: filterBuildPlateCheckbox id: filterBuildPlateCheckbox
@ -260,6 +260,4 @@ Rectangle
} }
action: Cura.Actions.arrangeAll; action: Cura.Actions.arrangeAll;
} }
} }

View file

@ -1,13 +1,14 @@
// Copyright (c) 2016 Ultimaker B.V. // Copyright (c) 2016 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.8
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import QtQuick.Window 2.1 import QtQuick.Window 2.1
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
UM.ManagementPage UM.ManagementPage
{ {
id: base; id: base;

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.8
import QtQuick.Controls 1.3 import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2 import QtQuick.Dialogs 1.2
import UM 1.2 as UM import UM 1.2 as UM
@ -12,7 +12,10 @@ TabView
{ {
id: base id: base
property QtObject properties; property QtObject materialManager: CuraApplication.getMaterialManager()
property QtObject properties
property var currentMaterialNode: null
property bool editingEnabled: false; property bool editingEnabled: false;
property string currency: UM.Preferences.getValue("cura/currency") ? UM.Preferences.getValue("cura/currency") : "€" property string currency: UM.Preferences.getValue("cura/currency") ? UM.Preferences.getValue("cura/currency") : "€"
@ -27,18 +30,23 @@ TabView
property bool reevaluateLinkedMaterials: false property bool reevaluateLinkedMaterials: false
property string linkedMaterialNames: property string linkedMaterialNames:
{ {
if (reevaluateLinkedMaterials) if (reevaluateLinkedMaterials) {
{
reevaluateLinkedMaterials = false; reevaluateLinkedMaterials = false;
} }
if(!base.containerId || !base.editingEnabled) if (!base.containerId || !base.editingEnabled) {
{ return ""
}
var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode);
if (linkedMaterials.length <= 1) {
return "" return ""
} }
var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.containerId);
return linkedMaterials.join(", "); return linkedMaterials.join(", ");
} }
function getApproximateDiameter(diameter) {
return Math.round(diameter);
}
Tab Tab
{ {
title: catalog.i18nc("@title", "Information") title: catalog.i18nc("@title", "Information")
@ -65,6 +73,34 @@ TabView
width: base.width width: base.width
property real rowHeight: textField.height + UM.Theme.getSize("default_lining").height property real rowHeight: textField.height + UM.Theme.getSize("default_lining").height
MessageDialog
{
id: confirmDiameterChangeDialog
icon: StandardIcon.Question;
title: catalog.i18nc("@title:window", "Confirm Diameter Change")
text: catalog.i18nc("@label (%1 is object name)", "The new material diameter is set to %1 mm, which is not compatible to the current machine. Do you wish to continue?".arg(new_diameter_value))
standardButtons: StandardButton.Yes | StandardButton.No
modality: Qt.ApplicationModal
property var new_diameter_value: null;
property var old_diameter_value: null;
property var old_approximate_diameter_value: null;
onYes:
{
Cura.ContainerManager.setContainerProperty(base.containerId, "material_diameter", "value", new_diameter_value);
base.setMetaDataEntry("approximate_diameter", old_approximate_diameter_value, getApproximateDiameter(new_diameter_value).toString());
base.setMetaDataEntry("properties/diameter", properties.diameter, new_diameter_value);
}
onNo:
{
properties.diameter = old_diameter_value;
diameterSpinBox.value = properties.diameter;
}
}
Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Display Name") } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Display Name") }
ReadOnlyTextField ReadOnlyTextField
{ {
@ -80,18 +116,18 @@ TabView
{ {
id: textField; id: textField;
width: scrollView.columnWidth; width: scrollView.columnWidth;
text: properties.supplier; text: properties.brand;
readOnly: !base.editingEnabled; readOnly: !base.editingEnabled;
onEditingFinished: base.updateMaterialSupplier(properties.supplier, text) onEditingFinished: base.updateMaterialBrand(properties.brand, text)
} }
Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") }
ReadOnlyTextField ReadOnlyTextField
{ {
width: scrollView.columnWidth; width: scrollView.columnWidth;
text: properties.material_type; text: properties.material;
readOnly: !base.editingEnabled; readOnly: !base.editingEnabled;
onEditingFinished: base.updateMaterialType(properties.material_type, text) onEditingFinished: base.updateMaterialType(properties.material, text)
} }
Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") }
@ -172,14 +208,20 @@ TabView
// which derive from the same base_file // which derive from the same base_file
var old_diameter = Cura.ContainerManager.getContainerProperty(base.containerId, "material_diameter", "value").toString(); var old_diameter = Cura.ContainerManager.getContainerProperty(base.containerId, "material_diameter", "value").toString();
var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter"); var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter");
base.setMetaDataEntry("approximate_diameter", old_approximate_diameter, Math.round(value).toString()); var new_approximate_diameter = getApproximateDiameter(value);
base.setMetaDataEntry("properties/diameter", properties.diameter, value); if (Cura.MachineManager.filterMaterialsByMachine && new_approximate_diameter != Cura.ExtruderManager.getActiveExtruderStack().approximateMaterialDiameter)
var new_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter");
if (Cura.MachineManager.filterMaterialsByMachine && new_approximate_diameter != Cura.MachineManager.activeMachine.approximateMaterialDiameter)
{ {
Cura.MaterialManager.showMaterialWarningMessage(base.containerId, old_diameter); confirmDiameterChangeDialog.old_diameter_value = old_diameter;
confirmDiameterChangeDialog.new_diameter_value = value;
confirmDiameterChangeDialog.old_approximate_diameter_value = old_approximate_diameter;
confirmDiameterChangeDialog.open()
} }
else {
Cura.ContainerManager.setContainerProperty(base.containerId, "material_diameter", "value", value); Cura.ContainerManager.setContainerProperty(base.containerId, "material_diameter", "value", value);
base.setMetaDataEntry("approximate_diameter", old_approximate_diameter, getApproximateDiameter(value).toString());
base.setMetaDataEntry("properties/diameter", properties.diameter, value);
}
} }
onValueChanged: updateCostPerMeter() onValueChanged: updateCostPerMeter()
} }
@ -251,7 +293,7 @@ TabView
visible: base.linkedMaterialNames != "" visible: base.linkedMaterialNames != ""
onClicked: onClicked:
{ {
Cura.ContainerManager.unlinkMaterial(base.containerId) Cura.ContainerManager.unlinkMaterial(base.currentMaterialNode)
base.reevaluateLinkedMaterials = true base.reevaluateLinkedMaterials = true
} }
} }
@ -357,8 +399,20 @@ TabView
onEditingFinished: materialPropertyProvider.setPropertyValue("value", value) onEditingFinished: materialPropertyProvider.setPropertyValue("value", value)
} }
UM.ContainerPropertyProvider { id: materialPropertyProvider; containerId: base.containerId; watchedProperties: [ "value" ]; key: model.key } UM.ContainerPropertyProvider
UM.ContainerPropertyProvider { id: machinePropertyProvider; containerId: Cura.MachineManager.activeDefinitionId; watchedProperties: [ "value" ]; key: model.key } {
id: materialPropertyProvider
containerId: base.containerId
watchedProperties: [ "value" ]
key: model.key
}
UM.ContainerPropertyProvider
{
id: machinePropertyProvider
containerId: Cura.MachineManager.activeDefinitionId
watchedProperties: [ "value" ]
key: model.key
}
} }
} }
} }
@ -405,7 +459,7 @@ TabView
// Tiny convenience function to check if a value really changed before trying to set it. // Tiny convenience function to check if a value really changed before trying to set it.
function setMetaDataEntry(entry_name, old_value, new_value) { function setMetaDataEntry(entry_name, old_value, new_value) {
if (old_value != new_value) { if (old_value != new_value) {
Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, entry_name, new_value) Cura.ContainerManager.setContainerMetaDataEntry(base.currentMaterialNode, entry_name, new_value)
// make sure the UI properties are updated as well since we don't re-fetch the entire model here // make sure the UI properties are updated as well since we don't re-fetch the entire model here
// When the entry_name is something like properties/diameter, we take the last part of the entry_name // When the entry_name is something like properties/diameter, we take the last part of the entry_name
var list = entry_name.split("/") var list = entry_name.split("/")
@ -442,26 +496,25 @@ TabView
// update the display name of the material // update the display name of the material
function updateMaterialDisplayName (old_name, new_name) { function updateMaterialDisplayName (old_name, new_name) {
// don't change when new name is the same // don't change when new name is the same
if (old_name == new_name) { if (old_name == new_name) {
return return
} }
// update the values // update the values
Cura.ContainerManager.setContainerName(base.containerId, new_name) base.materialManager.setMaterialName(base.currentMaterialNode, new_name)
materialProperties.name = new_name materialProperties.name = new_name
} }
// update the type of the material // update the type of the material
function updateMaterialType (old_type, new_type) { function updateMaterialType (old_type, new_type) {
base.setMetaDataEntry("material", old_type, new_type) base.setMetaDataEntry("material", old_type, new_type)
materialProperties.material_type = new_type materialProperties.material= new_type
} }
// update the supplier of the material // update the brand of the material
function updateMaterialSupplier (old_supplier, new_supplier) { function updateMaterialBrand (old_brand, new_brand) {
base.setMetaDataEntry("brand", old_supplier, new_supplier) base.setMetaDataEntry("brand", old_brand, new_brand)
materialProperties.supplier = new_supplier materialProperties.brand = new_brand
} }
} }

View file

@ -1,116 +1,303 @@
//Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
//Cura is released under the terms of the LGPLv3 or higher. // Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.8
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2 import QtQuick.Dialogs 1.2
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
UM.ManagementPage
{
id: base;
title: catalog.i18nc("@title:tab", "Materials"); Item
Component.onCompleted:
{ {
// Workaround to make sure all of the items are visible id: base
objectList.positionViewAtBeginning();
property QtObject materialManager: CuraApplication.getMaterialManager()
property var resetEnabled: false // Keep PreferencesDialog happy
UM.I18nCatalog { id: catalog; name: "cura"; }
Cura.MaterialManagementModel {
id: materialsModel
} }
model: Cura.MaterialsModel Label {
{ id: titleLabel
filter:
{ anchors {
var result = { "type": "material", "approximate_diameter": Math.round(materialDiameterProvider.properties.value).toString() } top: parent.top
if(Cura.MachineManager.filterMaterialsByMachine) left: parent.left
{ right: parent.right
result.definition = Cura.MachineManager.activeQualityDefinitionId; margins: 5 * screenScaleFactor
if(Cura.MachineManager.hasVariants)
{
result.variant = Cura.MachineManager.activeQualityVariantId;
}
}
else
{
result.definition = "fdmprinter";
result.compatible = true; //NB: Only checks for compatibility in global version of material, but we don't have machine-specific materials anyway.
}
return result
} }
sectionProperty: "brand" font.pointSize: 18
text: catalog.i18nc("@title:tab", "Materials")
} }
delegate: Rectangle property var hasCurrentItem: materialListView.currentItem != null
{
width: objectList.width;
height: childrenRect.height;
color: isCurrentItem ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
property bool isCurrentItem: ListView.isCurrentItem
Row property var currentItem:
{ { // is soon to be overwritten
spacing: (UM.Theme.getSize("default_margin").width / 2) | 0 var current_index = materialListView.currentIndex;
anchors.left: parent.left return materialsModel.getItem(current_index);
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
Rectangle
{
width: Math.round(parent.height * 0.8)
height: Math.round(parent.height * 0.8)
color: model.metadata.color_code
border.color: isCurrentItem ? palette.highlightedText : palette.text;
anchors.verticalCenter: parent.verticalCenter
} }
Label
property var isCurrentItemActivated:
{ {
width: Math.round((parent.width * 0.3)) const extruder_position = Cura.ExtruderManager.activeExtruderIndex;
text: model.metadata.material const root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position];
elide: Text.ElideRight return base.currentItem.root_material_id == root_material_id;
font.italic: model.id == activeId
color: isCurrentItem ? palette.highlightedText : palette.text;
} }
Label
Row // Button Row
{ {
text: (model.name != model.metadata.material) ? model.name : "" id: buttonRow
elide: Text.ElideRight anchors
font.italic: model.id == activeId {
color: isCurrentItem ? palette.highlightedText : palette.text; left: parent.left
right: parent.right
top: titleLabel.bottom
}
height: childrenRect.height
// Activate button
Button
{
text: catalog.i18nc("@action:button", "Activate")
iconName: "list-activate"
enabled: !isCurrentItemActivated
onClicked:
{
forceActiveFocus()
const extruder_position = Cura.ExtruderManager.activeExtruderIndex;
Cura.MachineManager.setMaterial(extruder_position, base.currentItem.container_node);
} }
} }
MouseArea // Create button
Button
{ {
anchors.fill: parent; text: catalog.i18nc("@action:button", "Create")
iconName: "list-add"
onClicked: onClicked:
{ {
forceActiveFocus(); forceActiveFocus();
if(!parent.ListView.isCurrentItem) base.newRootMaterialIdToSwitchTo = base.materialManager.createMaterial();
{ base.toActivateNewMaterial = true;
parent.ListView.view.currentIndex = index;
base.itemActivated();
}
}
} }
} }
activeId: Cura.MachineManager.activeMaterialId // Duplicate button
activeIndex: getIndexById(activeId) Button
function getIndexById(material_id)
{ {
for(var i = 0; i < model.rowCount(); i++) { text: catalog.i18nc("@action:button", "Duplicate");
if (model.getItem(i).id == material_id) { iconName: "list-add"
return i; enabled: base.hasCurrentItem
onClicked:
{
forceActiveFocus();
base.newRootMaterialIdToSwitchTo = base.materialManager.duplicateMaterial(base.currentItem.container_node);
base.toActivateNewMaterial = true;
} }
} }
return -1;
}
scrollviewCaption: // Remove button
Button
{ {
text: catalog.i18nc("@action:button", "Remove")
iconName: "list-remove"
enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated
onClicked:
{
forceActiveFocus();
confirmRemoveMaterialDialog.open();
}
}
// Import button
Button
{
text: catalog.i18nc("@action:button", "Import")
iconName: "document-import"
onClicked:
{
forceActiveFocus();
importMaterialDialog.open();
}
visible: true
}
// Export button
Button
{
text: catalog.i18nc("@action:button", "Export")
iconName: "document-export"
onClicked:
{
forceActiveFocus();
exportMaterialDialog.open();
}
enabled: currentItem != null
}
}
property string newRootMaterialIdToSwitchTo: ""
property bool toActivateNewMaterial: false
// This connection makes sure that we will switch to the new
Connections
{
target: materialsModel
onItemsChanged: {
var currentItemId = base.currentItem == null ? "" : base.currentItem.root_material_id;
var position = Cura.ExtruderManager.activeExtruderIndex;
// try to pick the currently selected item; it may have been moved
if (base.newRootMaterialIdToSwitchTo == "") {
base.newRootMaterialIdToSwitchTo = currentItemId;
}
for (var idx = 0; idx < materialsModel.rowCount(); ++idx) {
var item = materialsModel.getItem(idx);
if (item.root_material_id == base.newRootMaterialIdToSwitchTo) {
// Switch to the newly created profile if needed
materialListView.currentIndex = idx;
materialListView.activateDetailsWithIndex(materialListView.currentIndex);
if (base.toActivateNewMaterial) {
Cura.MachineManager.setMaterial(position, item.container_node);
}
base.newRootMaterialIdToSwitchTo = "";
base.toActivateNewMaterial = false;
return
}
}
materialListView.currentIndex = 0;
materialListView.activateDetailsWithIndex(materialListView.currentIndex);
if (base.toActivateNewMaterial) {
Cura.MachineManager.setMaterial(position, materialsModel.getItem(0).container_node);
}
base.newRootMaterialIdToSwitchTo = "";
base.toActivateNewMaterial = false;
}
}
MessageDialog
{
id: confirmRemoveMaterialDialog
icon: StandardIcon.Question;
title: catalog.i18nc("@title:window", "Confirm Remove")
text: catalog.i18nc("@label (%1 is object name)", "Are you sure you wish to remove %1? This cannot be undone!").arg(base.currentItem.name)
standardButtons: StandardButton.Yes | StandardButton.No
modality: Qt.ApplicationModal
onYes:
{
base.materialManager.removeMaterial(base.currentItem.container_node);
}
}
FileDialog
{
id: importMaterialDialog
title: catalog.i18nc("@title:window", "Import Material")
selectExisting: true
nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
folder: CuraApplication.getDefaultPath("dialog_material_path")
onAccepted:
{
var result = Cura.ContainerManager.importMaterialContainer(fileUrl);
messageDialog.title = catalog.i18nc("@title:window", "Import Material");
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Could not import material <filename>%1</filename>: <message>%2</message>").arg(fileUrl).arg(result.message);
if (result.status == "success") {
messageDialog.icon = StandardIcon.Information;
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Successfully imported material <filename>%1</filename>").arg(fileUrl);
}
else if (result.status == "duplicate") {
messageDialog.icon = StandardIcon.Warning;
}
else {
messageDialog.icon = StandardIcon.Critical;
}
messageDialog.open();
CuraApplication.setDefaultPath("dialog_material_path", folder);
}
}
FileDialog
{
id: exportMaterialDialog
title: catalog.i18nc("@title:window", "Export Material")
selectExisting: false
nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
folder: CuraApplication.getDefaultPath("dialog_material_path")
onAccepted:
{
var result = Cura.ContainerManager.exportContainer(base.currentItem.root_material_id, selectedNameFilter, fileUrl);
messageDialog.title = catalog.i18nc("@title:window", "Export Material");
if (result.status == "error") {
messageDialog.icon = StandardIcon.Critical;
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags <filename> and <message>!", "Failed to export material to <filename>%1</filename>: <message>%2</message>").arg(fileUrl).arg(result.message);
messageDialog.open();
}
else if (result.status == "success") {
messageDialog.icon = StandardIcon.Information;
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Successfully exported material to <filename>%1</filename>").arg(result.path);
messageDialog.open();
}
CuraApplication.setDefaultPath("dialog_material_path", folder);
}
}
MessageDialog
{
id: messageDialog
}
Item {
id: contentsItem
anchors {
top: titleLabel.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
margins: 5 * screenScaleFactor
bottomMargin: 0
}
clip: true
}
Item
{
anchors {
top: buttonRow.bottom
topMargin: UM.Theme.getSize("default_margin").height
left: parent.left
right: parent.right
bottom: parent.bottom
}
SystemPalette { id: palette }
Label
{
id: captionLabel
anchors {
top: parent.top
left: parent.left
}
visible: text != ""
text: {
var caption = catalog.i18nc("@action:label", "Printer") + ": " + Cura.MachineManager.activeMachineName; var caption = catalog.i18nc("@action:label", "Printer") + ": " + Cura.MachineManager.activeMachineName;
if (Cura.MachineManager.hasVariants) if (Cura.MachineManager.hasVariants)
{ {
@ -118,103 +305,177 @@ UM.ManagementPage
} }
return caption; return caption;
} }
detailsVisible: true width: materialScrollView.width
elide: Text.ElideRight
}
section.property: "section" ScrollView
section.delegate: Label
{ {
id: materialScrollView
anchors {
top: captionLabel.visible ? captionLabel.bottom : parent.top
topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0
bottom: parent.bottom
left: parent.left
}
Rectangle {
parent: viewport
anchors.fill: parent
color: palette.light
}
width: true ? (parent.width * 0.4) | 0 : parent.width
ListView
{
id: materialListView
model: materialsModel
section.property: "brand"
section.criteria: ViewSection.FullString
section.delegate: Rectangle
{
width: materialScrollView.width
height: childrenRect.height
color: palette.light
Label
{
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_lining").width
text: section text: section
font.bold: true font.bold: true
anchors.left: parent.left; color: palette.text
anchors.leftMargin: UM.Theme.getSize("default_lining").width; }
} }
buttons: [ delegate: Rectangle
{
width: materialScrollView.width
height: childrenRect.height
color: ListView.isCurrentItem ? palette.highlight : (model.index % 2) ? palette.base : palette.alternateBase
// Activate button Row
Button { {
text: catalog.i18nc("@action:button", "Activate") id: materialRow
iconName: "list-activate"; spacing: (UM.Theme.getSize("default_margin").width / 2) | 0
enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId && Cura.MachineManager.hasMaterials anchors.left: parent.left
onClicked: { anchors.leftMargin: UM.Theme.getSize("default_margin").width
forceActiveFocus() anchors.right: parent.right
Cura.MachineManager.setActiveMaterial(base.currentItem.id)
currentItem = base.model.getItem(base.objectList.currentIndex) // Refresh the current item.
}
},
// Create button property bool isItemActivated:
Button { {
text: catalog.i18nc("@action:button", "Create") const extruder_position = Cura.ExtruderManager.activeExtruderIndex;
iconName: "list-add" const root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position];
onClicked: { return model.root_material_id == root_material_id;
forceActiveFocus()
Cura.ContainerManager.createMaterial()
}
},
// Duplicate button
Button {
text: catalog.i18nc("@action:button", "Duplicate");
iconName: "list-add";
enabled: base.currentItem != null
onClicked: {
forceActiveFocus()
Cura.ContainerManager.duplicateOriginalMaterial(base.currentItem.id)
}
},
// Remove button
Button {
text: catalog.i18nc("@action:button", "Remove")
iconName: "list-remove"
enabled: base.currentItem != null && !base.currentItem.readOnly && !Cura.ContainerManager.isContainerUsed(base.currentItem.id)
onClicked: {
forceActiveFocus()
confirmDialog.open()
}
},
// Import button
Button {
text: catalog.i18nc("@action:button", "Import")
iconName: "document-import"
onClicked: {
forceActiveFocus()
importDialog.open()
}
visible: true
},
// Export button
Button {
text: catalog.i18nc("@action:button", "Export")
iconName: "document-export"
onClicked: {
forceActiveFocus()
exportDialog.open()
}
enabled: currentItem != null
} }
] Rectangle
{
width: Math.floor(parent.height * 0.8)
height: Math.floor(parent.height * 0.8)
color: model.color_code
border.color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text;
anchors.verticalCenter: parent.verticalCenter
}
Label
{
width: Math.floor((parent.width * 0.3))
text: model.material
elide: Text.ElideRight
font.italic: materialRow.isItemActivated
color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text;
}
Label
{
text: (model.name != model.material) ? model.name : ""
elide: Text.ElideRight
font.italic: materialRow.isItemActivated
color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text;
}
}
Item { MouseArea
visible: base.currentItem != null {
anchors.fill: parent anchors.fill: parent
onClicked: {
parent.ListView.view.currentIndex = model.index;
}
}
}
function activateDetailsWithIndex(index) {
var model = materialsModel.getItem(index);
base.currentItem = model;
materialDetailsView.containerId = model.container_id;
materialDetailsView.currentMaterialNode = model.container_node;
detailsPanel.updateMaterialPropertiesObject();
}
onCurrentIndexChanged:
{
forceActiveFocus(); // causes the changed fields to be saved
activateDetailsWithIndex(currentIndex);
}
}
}
Item Item
{ {
id: profileName id: detailsPanel
width: parent.width; anchors {
height: childrenRect.height left: materialScrollView.right
leftMargin: UM.Theme.getSize("default_margin").width
Label { text: materialProperties.name; font: UM.Theme.getFont("large"); } top: parent.top
bottom: parent.bottom
right: parent.right
} }
MaterialView function updateMaterialPropertiesObject()
{ {
var currentItem = materialsModel.getItem(materialListView.currentIndex);
materialProperties.name = currentItem.name;
materialProperties.guid = currentItem.guid;
materialProperties.brand = currentItem.brand ? currentItem.brand : "Unknown";
materialProperties.material = currentItem.material ? currentItem.material : "Unknown";
materialProperties.color_name = currentItem.color_name ? currentItem.color_name : "Yellow";
materialProperties.color_code = currentItem.color_code ? currentItem.color_code : "yellow";
materialProperties.description = currentItem.description ? currentItem.description : "";
materialProperties.adhesion_info = currentItem.adhesion_info ? currentItem.adhesion_info : "";
materialProperties.density = currentItem.density ? currentItem.density : 0.0;
materialProperties.diameter = currentItem.diameter ? currentItem.diameter : 0.0;
materialProperties.approximate_diameter = currentItem.approximate_diameter ? currentItem.approximate_diameter : "0";
}
Item
{
anchors.fill: parent
Item // Material title Label
{
id: profileName
width: parent.width
height: childrenRect.height
Label {
text: materialProperties.name
font: UM.Theme.getFont("large")
}
}
MaterialView // Material detailed information view below the title Label
{
id: materialDetailsView
anchors anchors
{ {
left: parent.left left: parent.left
@ -224,10 +485,11 @@ UM.ManagementPage
bottom: parent.bottom bottom: parent.bottom
} }
editingEnabled: base.currentItem != null && !base.currentItem.readOnly editingEnabled: base.currentItem != null && !base.currentItem.is_read_only
properties: materialProperties properties: materialProperties
containerId: base.currentItem != null ? base.currentItem.id : "" containerId: base.currentItem != null ? base.currentItem.container_id : ""
currentMaterialNode: base.currentItem.container_node
property alias pane: base property alias pane: base
} }
@ -239,8 +501,9 @@ UM.ManagementPage
property string guid: "00000000-0000-0000-0000-000000000000" property string guid: "00000000-0000-0000-0000-000000000000"
property string name: "Unknown"; property string name: "Unknown";
property string profile_type: "Unknown"; property string profile_type: "Unknown";
property string supplier: "Unknown"; property string brand: "Unknown";
property string material_type: "Unknown"; property string material: "Unknown"; // This needs to be named as "material" to be consistent with
// the material container's metadata entry
property string color_name: "Yellow"; property string color_name: "Yellow";
property color color_code: "yellow"; property color color_code: "yellow";
@ -257,156 +520,7 @@ UM.ManagementPage
property string description: ""; property string description: "";
property string adhesion_info: ""; property string adhesion_info: "";
} }
}
UM.ConfirmRemoveDialog
{
id: confirmDialog
object: base.currentItem != null ? base.currentItem.name : ""
onYes:
{
// A material container can actually be multiple items, so we need to find (and remove) all of them.
var base_file = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "base_file")
if(base_file == "")
{
base_file = base.currentItem.id
}
var guid = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "GUID")
// remove base container first, it otherwise triggers loading the base file while removing other containers
var base_containers = Cura.ContainerManager.findInstanceContainers({"GUID": guid, "id": base_file, "base_file": base_file, "type": "material"})
for(var i in base_containers)
{
Cura.ContainerManager.removeContainer(base_containers[i]);
}
var containers = Cura.ContainerManager.findInstanceContainers({"GUID": guid, "base_file": base_file, "type": "material"})
for(var i in containers)
{
Cura.ContainerManager.removeContainer(containers[i]);
}
if(base.objectList.currentIndex > 0)
{
base.objectList.currentIndex--;
}
currentItem = base.model.getItem(base.objectList.currentIndex) // Refresh the current item.
}
}
FileDialog
{
id: importDialog;
title: catalog.i18nc("@title:window", "Import Material");
selectExisting: true;
nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
folder: CuraApplication.getDefaultPath("dialog_material_path")
onAccepted:
{
var result = Cura.ContainerManager.importMaterialContainer(fileUrl)
messageDialog.title = catalog.i18nc("@title:window", "Import Material")
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Could not import material <filename>%1</filename>: <message>%2</message>").arg(fileUrl).arg(result.message)
if(result.status == "success")
{
messageDialog.icon = StandardIcon.Information
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Successfully imported material <filename>%1</filename>").arg(fileUrl)
}
else if(result.status == "duplicate")
{
messageDialog.icon = StandardIcon.Warning
}
else
{
messageDialog.icon = StandardIcon.Critical
}
messageDialog.open()
CuraApplication.setDefaultPath("dialog_material_path", folder)
}
}
FileDialog
{
id: exportDialog;
title: catalog.i18nc("@title:window", "Export Material");
selectExisting: false;
nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
folder: CuraApplication.getDefaultPath("dialog_material_path")
onAccepted:
{
if(base.currentItem.metadata.base_file)
{
var result = Cura.ContainerManager.exportContainer(base.currentItem.metadata.base_file, selectedNameFilter, fileUrl)
}
else
{
var result = Cura.ContainerManager.exportContainer(base.currentItem.id, selectedNameFilter, fileUrl)
}
messageDialog.title = catalog.i18nc("@title:window", "Export Material")
if(result.status == "error")
{
messageDialog.icon = StandardIcon.Critical
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags <filename> and <message>!", "Failed to export material to <filename>%1</filename>: <message>%2</message>").arg(fileUrl).arg(result.message)
messageDialog.open()
}
else if(result.status == "success")
{
messageDialog.icon = StandardIcon.Information
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Successfully exported material to <filename>%1</filename>").arg(result.path)
messageDialog.open()
}
CuraApplication.setDefaultPath("dialog_material_path", folder)
}
}
MessageDialog
{
id: messageDialog
}
UM.SettingPropertyProvider
{
id: materialDiameterProvider
containerStackId: Cura.ExtruderManager.activeExtruderStackId
key: "material_diameter"
watchedProperties: [ "value" ]
storeIndex: 5
}
UM.I18nCatalog { id: catalog; name: "cura"; }
SystemPalette { id: palette }
}
onCurrentItemChanged:
{
if(currentItem == null)
{
return
}
materialProperties.name = currentItem.name;
materialProperties.guid = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "GUID");
if(currentItem.metadata != undefined && currentItem.metadata != null)
{
materialProperties.supplier = currentItem.metadata.brand ? currentItem.metadata.brand : "Unknown";
materialProperties.material_type = currentItem.metadata.material ? currentItem.metadata.material : "Unknown";
materialProperties.color_name = currentItem.metadata.color_name ? currentItem.metadata.color_name : "Yellow";
materialProperties.color_code = currentItem.metadata.color_code ? currentItem.metadata.color_code : "yellow";
materialProperties.description = currentItem.metadata.description ? currentItem.metadata.description : "";
materialProperties.adhesion_info = currentItem.metadata.adhesion_info ? currentItem.metadata.adhesion_info : "";
if(currentItem.metadata.properties != undefined && currentItem.metadata.properties != null)
{
materialProperties.density = currentItem.metadata.properties.density ? currentItem.metadata.properties.density : 0.0;
materialProperties.diameter = currentItem.metadata.properties.diameter ? currentItem.metadata.properties.diameter : 0.0;
materialProperties.approximate_diameter = currentItem.metadata.approximate_diameter ? currentItem.metadata.approximate_diameter : "0";
}
else
{
materialProperties.density = 0.0;
materialProperties.diameter = 0.0;
materialProperties.approximate_diameter = "0";
}
} }
} }
} }

View file

@ -1,8 +1,8 @@
// Copyright (c) 2016 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.8
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
@ -11,10 +11,8 @@ Tab
{ {
id: base id: base
property string extruderId: ""; property string extruderPosition: ""
property string extruderDefinition: ""; property var qualityItem: null
property string quality: "";
property string material: "";
TableView TableView
{ {
@ -38,8 +36,8 @@ Tab
anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right anchors.right: parent.right
text: (styleData.value.substr(0,1) == "=") ? catalog.i18nc("@info:status", "Calculated") : styleData.value text: (styleData.value.substr(0,1) == "=") ? catalog.i18nc("@info:status", "Calculated") : styleData.value
font.strikeout: styleData.column == 1 && quality == Cura.MachineManager.globalQualityId && setting.user_value != "" font.strikeout: styleData.column == 1 && setting.user_value != "" && qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName
font.italic: setting.profile_value_source == "quality_changes" || (quality == Cura.MachineManager.globalQualityId && setting.user_value != "") font.italic: setting.profile_value_source == "quality_changes" || (setting.user_value != "" && qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName)
opacity: font.strikeout ? 0.5 : 1 opacity: font.strikeout ? 0.5 : 1
color: styleData.textColor color: styleData.textColor
elide: Text.ElideRight elide: Text.ElideRight
@ -65,7 +63,7 @@ Tab
{ {
role: "user_value" role: "user_value"
title: catalog.i18nc("@title:column", "Current"); title: catalog.i18nc("@title:column", "Current");
visible: quality == Cura.MachineManager.globalQualityId visible: qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName
width: (parent.width * 0.18) | 0 width: (parent.width * 0.18) | 0
delegate: itemDelegate delegate: itemDelegate
} }
@ -87,10 +85,8 @@ Tab
model: Cura.QualitySettingsModel model: Cura.QualitySettingsModel
{ {
id: qualitySettings id: qualitySettings
extruderId: base.extruderId selectedPosition: base.extruderPosition
extruderDefinition: base.extruderDefinition selectedQualityItem: base.qualityItem
quality: base.quality != null ? base.quality : ""
material: base.material != null ? base.material : ""
} }
SystemPalette { id: palette } SystemPalette { id: palette }

View file

@ -1,159 +1,459 @@
// Copyright (c) 2016 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.8
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2 import QtQuick.Dialogs 1.2
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
UM.ManagementPage
{
id: base;
title: catalog.i18nc("@title:tab", "Profiles"); Item
{
id: base
property QtObject qualityManager: CuraApplication.getQualityManager()
property var resetEnabled: false // Keep PreferencesDialog happy
property var extrudersModel: Cura.ExtrudersModel {} property var extrudersModel: Cura.ExtrudersModel {}
model: Cura.QualityAndUserProfilesModel { } UM.I18nCatalog { id: catalog; name: "cura"; }
section.property: "readOnly" Cura.QualityManagementModel {
section.delegate: Rectangle id: qualitiesModel
}
Label {
id: titleLabel
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 5 * screenScaleFactor
}
font.pointSize: 18
text: catalog.i18nc("@title:tab", "Profiles")
}
property var hasCurrentItem: qualityListView.currentItem != null
property var currentItem: {
var current_index = qualityListView.currentIndex;
return qualitiesModel.getItem(current_index);
}
property var isCurrentItemActivated: {
if (!base.currentItem) {
return false;
}
return base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName;
}
property var canCreateProfile: {
return isCurrentItemActivated && Cura.MachineManager.hasUserSettings;
}
Row // Button Row
{ {
height: childrenRect.height; id: buttonRow
anchors {
left: parent.left
right: parent.right
top: titleLabel.bottom
}
height: childrenRect.height
Label // Activate button
{
anchors.left: parent.left;
anchors.leftMargin: UM.Theme.getSize("default_lining").width;
text: section == "true" ? catalog.i18nc("@label", "Protected profiles") : catalog.i18nc("@label", "Custom profiles")
font.bold: true
}
}
activeId: Cura.MachineManager.activeQualityId
activeIndex: {
for(var i = 0; i < model.rowCount(); i++) {
if (model.getItem(i).id == Cura.MachineManager.activeQualityId) {
return i;
}
}
return -1;
}
function canCreateProfile() {
return base.currentItem && (base.currentItem.id == Cura.MachineManager.activeQualityId) && Cura.MachineManager.hasUserSettings;
}
buttons: [
Button Button
{ {
text: catalog.i18nc("@action:button", "Activate"); text: catalog.i18nc("@action:button", "Activate")
iconName: "list-activate"; iconName: "list-activate"
enabled: base.currentItem != null ? base.currentItem.id != Cura.MachineManager.activeQualityId : false; enabled: !isCurrentItemActivated
onClicked: onClicked: {
{ if (base.currentItem.is_read_only) {
Cura.MachineManager.setActiveQuality(base.currentItem.id) Cura.MachineManager.setQualityGroup(base.currentItem.quality_group);
currentItem = base.model.getItem(base.objectList.currentIndex) // Refresh the current item. } else {
Cura.MachineManager.setQualityChangesGroup(base.currentItem.quality_changes_group);
}
}
} }
},
// Create button // Create button
Button Button
{ {
text: catalog.i18nc("@label", "Create") text: catalog.i18nc("@label", "Create")
enabled: base.canCreateProfile() && !Cura.MachineManager.stacksHaveErrors iconName: "list-add"
visible: base.canCreateProfile() enabled: base.canCreateProfile && !Cura.MachineManager.stacksHaveErrors
iconName: "list-add"; visible: base.canCreateProfile
onClicked: onClicked: {
{ createQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
newNameDialog.object = base.currentItem != null ? Cura.ContainerManager.makeUniqueName(base.currentItem.name) : ""; createQualityDialog.open();
newNameDialog.open(); createQualityDialog.selectText();
newNameDialog.selectText(); }
} }
},
// Duplicate button // Duplicate button
Button Button
{ {
text: catalog.i18nc("@label", "Duplicate") text: catalog.i18nc("@label", "Duplicate")
enabled: ! base.canCreateProfile() iconName: "list-add"
visible: ! base.canCreateProfile() enabled: !base.canCreateProfile
iconName: "list-add"; visible: !base.canCreateProfile
onClicked: onClicked: {
{ duplicateQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
newDuplicateNameDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name); duplicateQualityDialog.open();
newDuplicateNameDialog.open(); duplicateQualityDialog.selectText();
newDuplicateNameDialog.selectText(); }
} }
},
// Remove button
Button Button
{ {
text: catalog.i18nc("@action:button", "Remove"); text: catalog.i18nc("@action:button", "Remove")
iconName: "list-remove"; iconName: "list-remove"
enabled: base.currentItem != null ? !base.currentItem.readOnly && !Cura.ContainerManager.isContainerUsed(base.currentItem.id) : false; enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated
onClicked: confirmDialog.open(); onClicked: {
}, forceActiveFocus();
Button confirmRemoveQualityDialog.open();
{
text: catalog.i18nc("@action:button", "Rename");
iconName: "edit-rename";
enabled: base.currentItem != null ? !base.currentItem.readOnly : false;
onClicked:
{
renameDialog.open();
renameDialog.selectText();
} }
}, }
// Rename button
Button Button
{ {
text: catalog.i18nc("@action:button", "Import"); text: catalog.i18nc("@action:button", "Rename")
iconName: "document-import"; iconName: "edit-rename"
onClicked: importDialog.open(); enabled: base.hasCurrentItem && !base.currentItem.is_read_only
}, onClicked: {
renameQualityDialog.object = base.currentItem.name;
renameQualityDialog.open();
renameQualityDialog.selectText();
}
}
// Import button
Button
{
text: catalog.i18nc("@action:button", "Import")
iconName: "document-import"
onClicked: {
importDialog.open();
}
}
// Export button
Button Button
{ {
text: catalog.i18nc("@action:button", "Export") text: catalog.i18nc("@action:button", "Export")
iconName: "document-export" iconName: "document-export"
onClicked: exportDialog.open() enabled: base.hasCurrentItem && !base.currentItem.is_read_only
enabled: currentItem != null && !base.currentItem.readOnly onClicked: {
exportDialog.open();
}
}
} }
]
scrollviewCaption: catalog.i18nc("@label %1 is printer name","Printer: %1").arg(Cura.MachineManager.activeMachineName)
// Click create profile from ... in Profile context menu
signal createProfile() signal createProfile()
onCreateProfile: onCreateProfile:
{ {
newNameDialog.object = base.currentItem != null ? Cura.ContainerManager.makeUniqueName(base.currentItem.name) : ""; createQualityDialog.object = Cura.ContainerManager.makeUniqueName(Cura.MachineManager.activeQualityOrQualityChangesName);
newNameDialog.open(); createQualityDialog.open();
newNameDialog.selectText(); createQualityDialog.selectText();
} }
signal selectContainer(string name) // Dialog to request a name when creating a new profile
onSelectContainer: UM.RenameDialog
{ {
objectList.currentIndex = objectList.model.find("name", name); id: createQualityDialog
title: catalog.i18nc("@title:window", "Create Profile")
object: "<new name>"
onAccepted:
{
base.newQualityNameToSelect = newName; // We want to switch to the new profile once it's created
base.toActivateNewQuality = true;
Cura.ContainerManager.createQualityChanges(newName);
}
}
property string newQualityNameToSelect: ""
property bool toActivateNewQuality: false
// This connection makes sure that we will switch to the correct quality after the model gets updated
Connections
{
target: qualitiesModel
onItemsChanged: {
var toSelectItemName = base.currentItem == null ? "" : base.currentItem.name;
if (newQualityNameToSelect != "") {
toSelectItemName = newQualityNameToSelect;
}
var newIdx = -1; // Default to nothing if nothing can be found
if (toSelectItemName != "") {
// Select the required quality name if given
for (var idx = 0; idx < qualitiesModel.rowCount(); ++idx) {
var item = qualitiesModel.getItem(idx);
if (item.name == toSelectItemName) {
// Switch to the newly created profile if needed
newIdx = idx;
if (base.toActivateNewQuality) {
// Activate this custom quality if required
Cura.MachineManager.setQualityChangesGroup(item.quality_changes_group);
}
break;
}
}
}
qualityListView.currentIndex = newIdx;
// Reset states
base.newQualityNameToSelect = "";
base.toActivateNewQuality = false;
}
}
// Dialog to request a name when duplicating a new profile
UM.RenameDialog
{
id: duplicateQualityDialog
title: catalog.i18nc("@title:window", "Duplicate Profile")
object: "<new name>"
onAccepted:
{
base.qualityManager.duplicateQualityChanges(newName, base.currentItem);
}
}
// Confirmation dialog for removing a profile
MessageDialog
{
id: confirmRemoveQualityDialog
icon: StandardIcon.Question;
title: catalog.i18nc("@title:window", "Confirm Remove")
text: catalog.i18nc("@label (%1 is object name)", "Are you sure you wish to remove %1? This cannot be undone!").arg(base.currentItem.name)
standardButtons: StandardButton.Yes | StandardButton.No
modality: Qt.ApplicationModal
onYes:
{
base.qualityManager.removeQualityChangesGroup(base.currentItem.quality_changes_group);
// reset current item to the first if available
qualityListView.currentIndex = -1; // Reset selection.
}
}
// Dialog to rename a quality profile
UM.RenameDialog
{
id: renameQualityDialog
title: catalog.i18nc("@title:window", "Rename Profile")
object: "<new name>"
onAccepted:
{
var actualNewName = base.qualityManager.renameQualityChangesGroup(base.currentItem.quality_changes_group, newName);
base.newQualityNameToSelect = actualNewName; // Select the new name after the model gets updated
}
}
// Dialog for importing a quality profile
FileDialog
{
id: importDialog
title: catalog.i18nc("@title:window", "Import Profile")
selectExisting: true
nameFilters: qualitiesModel.getFileNameFilters("profile_reader")
folder: CuraApplication.getDefaultPath("dialog_profile_path")
onAccepted:
{
var result = Cura.ContainerManager.importProfile(fileUrl);
messageDialog.text = result.message;
if (result.status == "ok") {
messageDialog.icon = StandardIcon.Information;
}
else if (result.status == "duplicate") {
messageDialog.icon = StandardIcon.Warning;
}
else {
messageDialog.icon = StandardIcon.Critical;
}
messageDialog.open();
CuraApplication.setDefaultPath("dialog_profile_path", folder);
}
}
// Dialog for exporting a quality profile
FileDialog
{
id: exportDialog
title: catalog.i18nc("@title:window", "Export Profile")
selectExisting: false
nameFilters: qualitiesModel.getFileNameFilters("profile_writer")
folder: CuraApplication.getDefaultPath("dialog_profile_path")
onAccepted:
{
var result = Cura.ContainerManager.exportQualityChangesGroup(base.currentItem.quality_changes_group,
fileUrl, selectedNameFilter);
if (result && result.status == "error") {
messageDialog.icon = StandardIcon.Critical;
messageDialog.text = result.message;
messageDialog.open();
}
// else pop-up Message thing from python code
CuraApplication.setDefaultPath("dialog_profile_path", folder);
}
} }
Item { Item {
visible: base.currentItem != null id: contentsItem
anchors {
top: titleLabel.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
margins: 5 * screenScaleFactor
bottomMargin: 0
}
clip: true
}
Item
{
anchors {
top: buttonRow.bottom
topMargin: UM.Theme.getSize("default_margin").height
left: parent.left
right: parent.right
bottom: parent.bottom
}
SystemPalette { id: palette }
Label
{
id: captionLabel
anchors {
top: parent.top
left: parent.left
}
visible: text != ""
text: catalog.i18nc("@label %1 is printer name", "Printer: %1").arg(Cura.MachineManager.activeMachineName)
width: profileScrollView.width
elide: Text.ElideRight
}
ScrollView
{
id: profileScrollView
anchors {
top: captionLabel.visible ? captionLabel.bottom : parent.top
topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0
bottom: parent.bottom
left: parent.left
}
Rectangle {
parent: viewport
anchors.fill: parent
color: palette.light
}
width: true ? (parent.width * 0.4) | 0 : parent.width
ListView
{
id: qualityListView
model: qualitiesModel
section.property: "is_read_only"
section.delegate: Rectangle
{
height: childrenRect.height
Label
{
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_lining").width
text: section == "true" ? catalog.i18nc("@label", "Protected profiles") : catalog.i18nc("@label", "Custom profiles")
font.bold: true
}
}
delegate: Rectangle
{
width: profileScrollView.width
height: childrenRect.height
color: ListView.isCurrentItem ? palette.highlight : (model.index % 2) ? palette.base : palette.alternateBase
Row
{
spacing: (UM.Theme.getSize("default_margin").width / 2) | 0
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
Label
{
width: Math.floor((parent.width * 0.8))
text: model.name
elide: Text.ElideRight
font.italic: model.name == Cura.MachineManager.activeQualityOrQualityChangesName
color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text
}
}
MouseArea
{
anchors.fill: parent
onClicked: {
parent.ListView.view.currentIndex = model.index;
}
}
}
}
}
// details panel on the right
Item
{
id: detailsPanel
anchors {
left: profileScrollView.right
leftMargin: UM.Theme.getSize("default_margin").width
top: parent.top
bottom: parent.bottom
right: parent.right
}
Item
{
anchors.fill: parent anchors.fill: parent
Label { Item // Profile title Label
{
id: profileName id: profileName
text: base.currentItem ? base.currentItem.name: ""
font: UM.Theme.getFont("large")
width: parent.width width: parent.width
elide: Text.ElideRight height: childrenRect.height
Label {
text: base.currentItem.name
font: UM.Theme.getFont("large")
}
} }
Flow { Flow {
id: currentSettingsActions id: currentSettingsActions
visible: currentItem && currentItem.id == Cura.MachineManager.activeQualityId visible: base.hasCurrentItem && base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: profileName.bottom anchors.top: profileName.bottom
@ -161,10 +461,8 @@ UM.ManagementPage
Button Button
{ {
text: { text: catalog.i18nc("@action:button", "Update profile with current settings/overrides")
return catalog.i18nc("@action:button", "Update profile with current settings/overrides"); enabled: Cura.MachineManager.hasUserSettings && !base.currentItem.is_read_only
}
enabled: Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
onClicked: Cura.ContainerManager.updateQualityChanges() onClicked: Cura.ContainerManager.updateQualityChanges()
} }
@ -193,13 +491,14 @@ UM.ManagementPage
} }
Label { Label {
id: noCurrentSettingsMessage id: noCurrentSettingsMessage
visible: currentItem && currentItem.id == Cura.MachineManager.activeQualityId && !Cura.MachineManager.hasUserSettings visible: base.isCurrentItemActivated && !Cura.MachineManager.hasUserSettings
text: catalog.i18nc("@action:label", "Your current settings match the selected profile.") text: catalog.i18nc("@action:label", "Your current settings match the selected profile.")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
} }
} }
TabView TabView
{ {
anchors.left: parent.left anchors.left: parent.left
@ -208,13 +507,12 @@ UM.ManagementPage
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
currentIndex: Cura.ExtruderManager.extruderCount > 0 ? Cura.ExtruderManager.activeExtruderIndex + 1 : 0 currentIndex: 0
ProfileTab ProfileTab
{ {
title: catalog.i18nc("@title:tab", "Global Settings"); title: catalog.i18nc("@title:tab", "Global Settings")
quality: base.currentItem != null ? base.currentItem.id : ""; qualityItem: base.currentItem
material: Cura.MachineManager.allActiveMaterialIds[Cura.MachineManager.activeMachineId]
} }
Repeater Repeater
@ -223,134 +521,13 @@ UM.ManagementPage
ProfileTab ProfileTab
{ {
title: model.name; title: model.name
extruderId: model.id; extruderPosition: model.index
extruderDefinition: model.definition; qualityItem: base.currentItem
quality: base.currentItem != null ? base.currentItem.id : "";
material: Cura.MachineManager.allActiveMaterialIds[model.id]
} }
} }
} }
} }
Item
{
UM.I18nCatalog { id: catalog; name: "cura"; }
UM.ConfirmRemoveDialog
{
id: confirmDialog
object: base.currentItem != null ? base.currentItem.name : ""
onYes:
{
var name = base.currentItem.name;
Cura.ContainerManager.removeQualityChanges(name)
if(Cura.MachineManager.activeQualityName == name)
{
Cura.MachineManager.setActiveQuality(base.model.getItem(0).name)
}
objectList.currentIndex = -1 //Reset selection.
}
}
UM.RenameDialog
{
title: catalog.i18nc("@title:window", "Rename Profile")
id: renameDialog;
object: base.currentItem != null ? base.currentItem.name : ""
onAccepted:
{
Cura.ContainerManager.renameQualityChanges(base.currentItem.name, newName)
objectList.currentIndex = -1 //Reset selection.
}
}
// Dialog to request a name when creating a new profile
UM.RenameDialog
{
title: catalog.i18nc("@title:window", "Create Profile")
id: newNameDialog;
object: "<new name>";
onAccepted:
{
var selectedContainer = Cura.ContainerManager.createQualityChanges(newName);
base.selectContainer(selectedContainer);
objectList.currentIndex = -1 //Reset selection.
}
}
// Dialog to request a name when duplicating a new profile
UM.RenameDialog
{
title: catalog.i18nc("@title:window", "Duplicate Profile")
id: newDuplicateNameDialog;
object: "<new name>";
onAccepted:
{
var selectedContainer = Cura.ContainerManager.duplicateQualityOrQualityChanges(base.currentItem.name, newName);
base.selectContainer(selectedContainer);
objectList.currentIndex = -1 //Reset selection.
}
}
MessageDialog
{
id: messageDialog
title: catalog.i18nc("@window:title", "Import Profile");
standardButtons: StandardButton.Ok
modality: Qt.ApplicationModal
}
FileDialog
{
id: importDialog;
title: catalog.i18nc("@title:window", "Import Profile");
selectExisting: true;
nameFilters: base.model.getFileNameFilters("profile_reader")
folder: CuraApplication.getDefaultPath("dialog_profile_path")
onAccepted:
{
var result = Cura.ContainerManager.importProfile(fileUrl);
messageDialog.text = result.message
if(result.status == "ok")
{
messageDialog.icon = StandardIcon.Information
}
else if(result.status == "duplicate")
{
messageDialog.icon = StandardIcon.Warning
}
else
{
messageDialog.icon = StandardIcon.Critical
}
messageDialog.open()
CuraApplication.setDefaultPath("dialog_profile_path", folder)
}
}
FileDialog
{
id: exportDialog;
title: catalog.i18nc("@title:window", "Export Profile");
selectExisting: false;
nameFilters: base.model.getFileNameFilters("profile_writer")
folder: CuraApplication.getDefaultPath("dialog_profile_path")
onAccepted:
{
var containers = Cura.ContainerManager.findInstanceContainers({"type": "quality_changes", "name": base.currentItem.name})
var result = Cura.ContainerManager.exportProfile(containers, fileUrl, selectedNameFilter)
if(result && result.status == "error")
{
messageDialog.icon = StandardIcon.Critical
messageDialog.text = result.message
messageDialog.open()
}
// else pop-up Message thing from python code
CuraApplication.setDefaultPath("dialog_profile_path", folder)
}
} }
} }
} }

View file

@ -57,13 +57,13 @@ Item
height: UM.Theme.getSize("setting_control").height height: UM.Theme.getSize("setting_control").height
anchors.left: globalProfileLabel.right anchors.left: globalProfileLabel.right
anchors.right: parent.right anchors.right: parent.right
tooltip: Cura.MachineManager.activeQualityName tooltip: Cura.MachineManager.activeQualityOrQualityChangesName
style: UM.Theme.styles.sidebar_header_button style: UM.Theme.styles.sidebar_header_button
activeFocusOnPress: true activeFocusOnPress: true
menu: ProfileMenu { } menu: ProfileMenu { }
function generateActiveQualityText () { function generateActiveQualityText () {
var result = Cura.MachineManager.activeQualityName; var result = Cura.MachineManager.activeQualityOrQualityChangesName;
if (Cura.MachineManager.isActiveQualitySupported) { if (Cura.MachineManager.isActiveQualitySupported) {
if (Cura.MachineManager.activeQualityLayerHeight > 0) { if (Cura.MachineManager.activeQualityLayerHeight > 0) {

View file

@ -1,7 +1,7 @@
// Copyright (c) 2015 Ultimaker B.V. // Copyright (c) 2015 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.8
import QtQuick.Controls 2.0 import QtQuick.Controls 2.0
import "Settings" import "Settings"

View file

@ -252,8 +252,18 @@ Column
{ {
id: materialSelection id: materialSelection
text: Cura.MachineManager.activeMaterialName property var currentRootMaterialName:
tooltip: Cura.MachineManager.activeMaterialName {
var materials = Cura.MachineManager.currentRootMaterialName;
var materialName = "";
if (base.currentExtruderIndex in materials) {
materialName = materials[base.currentExtruderIndex];
}
return materialName;
}
text: currentRootMaterialName
tooltip: currentRootMaterialName
visible: Cura.MachineManager.hasMaterials visible: Cura.MachineManager.hasMaterials
enabled: !extrudersList.visible || base.currentExtruderIndex > -1 enabled: !extrudersList.visible || base.currentExtruderIndex > -1
height: UM.Theme.getSize("setting_control").height height: UM.Theme.getSize("setting_control").height

View file

@ -1,13 +1,13 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.8
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.2 as Cura import Cura 1.0 as Cura
Item Item
{ {
@ -57,7 +57,10 @@ Item
interval: 50 interval: 50
running: false running: false
repeat: false repeat: false
onTriggered: Cura.MachineManager.setActiveQuality(Cura.ProfilesModel.getItem(qualitySlider.value).id) onTriggered: {
var item = Cura.QualityProfilesDropDownMenuModel.getItem(qualitySlider.value);
Cura.MachineManager.activeQualityGroup = item.quality_group;
}
} }
Component.onCompleted: qualityModel.update() Component.onCompleted: qualityModel.update()
@ -102,14 +105,14 @@ Item
var availableMin = -1 var availableMin = -1
var availableMax = -1 var availableMax = -1
for (var i = 0; i < Cura.ProfilesModel.rowCount(); i++) { for (var i = 0; i < Cura.QualityProfilesDropDownMenuModel.rowCount(); i++) {
var qualityItem = Cura.ProfilesModel.getItem(i) var qualityItem = Cura.QualityProfilesDropDownMenuModel.getItem(i)
// Add each quality item to the UI quality model // Add each quality item to the UI quality model
qualityModel.append(qualityItem) qualityModel.append(qualityItem)
// Set selected value // Set selected value
if (Cura.MachineManager.activeQualityType == qualityItem.metadata.quality_type) { if (Cura.MachineManager.activeQualityType == qualityItem.quality_type) {
// set to -1 when switching to user created profile so all ticks are clickable // set to -1 when switching to user created profile so all ticks are clickable
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) { if (Cura.SimpleModeSettingsManager.isProfileUserCreated) {
@ -134,7 +137,7 @@ Item
// Set total available ticks for active slider part // Set total available ticks for active slider part
if (availableMin != -1) { if (availableMin != -1) {
qualityModel.availableTotalTicks = availableMax - availableMin qualityModel.availableTotalTicks = availableMax - availableMin + 1
} }
// Calculate slider values // Calculate slider values
@ -161,11 +164,11 @@ Item
function reset () { function reset () {
qualityModel.clear() qualityModel.clear()
qualityModel.availableTotalTicks = -1 qualityModel.availableTotalTicks = 0
qualityModel.existingQualityProfile = 0 qualityModel.existingQualityProfile = 0
// check, the ticks count cannot be less than zero // check, the ticks count cannot be less than zero
qualityModel.totalTicks = Math.max(0, Cura.ProfilesModel.rowCount() - 1) qualityModel.totalTicks = Math.max(0, Cura.QualityProfilesDropDownMenuModel.rowCount() - 1)
} }
} }
@ -191,13 +194,13 @@ Item
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: Math.round(UM.Theme.getSize("sidebar_margin").height / 2) anchors.topMargin: Math.round(UM.Theme.getSize("sidebar_margin").height / 2)
color: (Cura.MachineManager.activeMachine != null && Cura.ProfilesModel.getItem(index).available) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable") color: (Cura.MachineManager.activeMachine != null && Cura.QualityProfilesDropDownMenuModel.getItem(index).available) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
text: text:
{ {
var result = "" var result = ""
if(Cura.MachineManager.activeMachine != null) if(Cura.MachineManager.activeMachine != null)
{ {
result = Cura.ProfilesModel.getItem(index).layer_height_without_unit result = Cura.QualityProfilesDropDownMenuModel.getItem(index).layer_height
if(result == undefined) if(result == undefined)
{ {
@ -262,7 +265,7 @@ Item
Rectangle Rectangle
{ {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Cura.ProfilesModel.getItem(index).available ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable") color: Cura.QualityProfilesDropDownMenuModel.getItem(index).available ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
width: 1 * screenScaleFactor width: 1 * screenScaleFactor
height: 6 * screenScaleFactor height: 6 * screenScaleFactor
y: 0 y: 0
@ -270,32 +273,24 @@ Item
} }
} }
Rectangle {
id: disabledHandleButton
visible: !qualitySlider.visible
anchors.centerIn: parent
color: UM.Theme.getColor("quality_slider_unavailable")
implicitWidth: 10 * screenScaleFactor
implicitHeight: implicitWidth
radius: Math.round(width / 2)
}
Slider Slider
{ {
id: qualitySlider id: qualitySlider
height: UM.Theme.getSize("sidebar_margin").height height: UM.Theme.getSize("sidebar_margin").height
anchors.bottom: speedSlider.bottom anchors.bottom: speedSlider.bottom
enabled: qualityModel.availableTotalTicks > 0 && !Cura.SimpleModeSettingsManager.isProfileCustomized enabled: qualityModel.totalTicks > 0 && !Cura.SimpleModeSettingsManager.isProfileCustomized
visible: qualityModel.totalTicks > 0 visible: qualityModel.availableTotalTicks > 0
updateValueWhileDragging : false updateValueWhileDragging : false
minimumValue: qualityModel.qualitySliderAvailableMin >= 0 ? qualityModel.qualitySliderAvailableMin : 0 minimumValue: qualityModel.qualitySliderAvailableMin >= 0 ? qualityModel.qualitySliderAvailableMin : 0
maximumValue: qualityModel.qualitySliderAvailableMax >= 0 ? qualityModel.qualitySliderAvailableMax : 0 // maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly
// speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available)
maximumValue: qualityModel.qualitySliderAvailableMax >= 1 ? qualityModel.qualitySliderAvailableMax : 1
stepSize: 1 stepSize: 1
value: qualityModel.qualitySliderActiveIndex value: qualityModel.qualitySliderActiveIndex
width: qualityModel.qualitySliderStepWidth * qualityModel.availableTotalTicks width: qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks - 1)
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: qualityModel.qualitySliderMarginRight anchors.rightMargin: qualityModel.qualitySliderMarginRight
@ -373,7 +368,7 @@ Item
text: catalog.i18nc("@label", "Slower") text: catalog.i18nc("@label", "Slower")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: (qualityModel.availableTotalTicks > 0) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable") color: (qualityModel.availableTotalTicks > 1) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
} }
@ -384,7 +379,7 @@ Item
text: catalog.i18nc("@label", "Faster") text: catalog.i18nc("@label", "Faster")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: (qualityModel.availableTotalTicks > 0) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable") color: (qualityModel.availableTotalTicks > 1) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
} }
@ -408,9 +403,10 @@ Item
// if the current profile is user-created, switch to a built-in quality // if the current profile is user-created, switch to a built-in quality
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) if (Cura.SimpleModeSettingsManager.isProfileUserCreated)
{ {
if (Cura.ProfilesModel.rowCount() > 0) if (Cura.QualityProfilesDropDownMenuModel.rowCount() > 0)
{ {
Cura.MachineManager.setActiveQuality(Cura.ProfilesModel.getItem(0).id) var item = Cura.QualityProfilesDropDownMenuModel.getItem(0);
Cura.MachineManager.activeQualityGroup = item.quality_group;
} }
} }
if (Cura.SimpleModeSettingsManager.isProfileCustomized) if (Cura.SimpleModeSettingsManager.isProfileCustomized)

View file

@ -101,7 +101,7 @@ UM.Dialog
} }
Label Label
{ {
text: Cura.MachineManager.activeDefinitionName text: Cura.MachineManager.activeMachine.definition.name
width: (parent.width / 3) | 0 width: (parent.width / 3) | 0
} }
} }
@ -148,7 +148,7 @@ UM.Dialog
Repeater Repeater
{ {
model: Cura.MachineManager.activeMaterialNames model: Cura.MachineManager.currentExtruderPositions
delegate: Column delegate: Column
{ {
Item // Spacer Item // Spacer
@ -158,7 +158,7 @@ UM.Dialog
} }
Label Label
{ {
text: catalog.i18nc("@action:label", "Extruder %1").arg(index+1) text: catalog.i18nc("@action:label", "Extruder %1").arg(modelData)
} }
height: childrenRect.height height: childrenRect.height
width: parent.width width: parent.width
@ -173,7 +173,7 @@ UM.Dialog
} }
Label Label
{ {
text: Cura.MachineManager.activeVariantNames[index] + ", " + modelData text: Cura.MachineManager.activeVariantNames[modelData] + ", " + Cura.MachineManager.currentRootMaterialName[modelData]
width: (parent.width / 3) | 0 width: (parent.width / 3) | 0
} }
} }
@ -217,7 +217,7 @@ UM.Dialog
} }
Label Label
{ {
text: Cura.MachineManager.activeQualityName text: Cura.MachineManager.activeQualityOrQualityChangesName
width: (parent.width / 3) | 0 width: (parent.width / 3) | 0
} }

View file

@ -4,11 +4,11 @@ name = Fine
definition = abax_pri3 definition = abax_pri3
[metadata] [metadata]
type = quality
material = generic_pla
weight = -1
quality_type = normal
setting_version = 4 setting_version = 4
type = quality
quality_type = normal
weight = -1
material = generic_pla
[values] [values]
layer_height = 0.2 layer_height = 0.2

Some files were not shown because too many files have changed in this diff Show more